1use crate::SheetId;
2
3mod criteria;
4mod exact;
5mod family;
6mod kernels;
7mod lift;
8mod memo;
9use crate::arrow_store::{OverlayFragment, OverlayValue, SheetStore};
10#[cfg(test)]
11use crate::engine::Scheduler;
12use crate::engine::arena::AstNodeId;
13use crate::engine::eval_delta::{
14 DeltaCollector, DeltaMode, EvalDelta, EvalDeltaCompatibilityPolicy,
15};
16use crate::engine::graph::editor::change_log::MutationCapture;
17use crate::engine::graph::prepared_legacy_graph::PreparedLegacyGraphPlan;
18use crate::engine::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
19use crate::engine::live_edges::{LiveEdgeCollector, RecordingContext};
20use crate::engine::live_graph::analyze_live_graph;
21use crate::engine::lookup_index_cache::{
22 BuildOutcome, LookupAxis, LookupIndex, LookupIndexCache, LookupIndexCacheReport,
23 LookupIndexKey, estimate_bytes,
24};
25use crate::engine::named_range::{NameScope, NamedDefinition};
26use crate::engine::range_view::RangeView;
27use crate::engine::row_visibility::RowVisibilityState;
28use crate::engine::spill::{RegionLockManager, SpillMeta, SpillShape};
29use crate::engine::target_preparation::{
30 StagedFormulaIndex, StagedFormulaLease, StagedPackageLease,
31};
32use crate::engine::used_extent::{
33 ExtentPolicy, OpenRangeBounds, resolve_used_extent_with_fallback,
34};
35use crate::engine::virtual_deps::VirtualDepBuilder;
36use family::{LayerUnit, layer_units, unit_members};
37
38#[path = "freshness.rs"]
39mod freshness;
40use crate::engine::template::region::Region;
41use crate::engine::{
42 ChangeLogger, CycleDetection, CyclePolicy, DependencyGraph, EvalConfig, EvaluationRequestKind,
43 EvaluationRequestOutcome, EvaluationResourceBaselineStats, EvaluationResourceRequestStats,
44 FormulaDirtyLeaseOutcome, FormulaIngestBatch, FormulaIngestRecord, FormulaIngestReport,
45 FormulaParseDiagnostic, FormulaParsePolicy, FormulaPlaneMode, ResourceLedger,
46 RowVisibilitySource, ScheduleUnit, VertexId, VertexKind, VisibilityMaskMode,
47};
48use crate::function::FnCaps;
49use crate::interpreter::Interpreter;
50use crate::reference::{CellRef, Coord, RangeRef};
51use crate::traits::FunctionProvider;
52use crate::traits::{EvaluationContext, ReferenceInfo, Resolver};
53use formualizer_common::{
54 CoordBuildHasher, LiteralValue, col_letters_from_1based, parse_a1_1based,
55};
56use formualizer_parse::parser::ReferenceType;
57use formualizer_parse::{ASTNode, ASTNodeType, ExcelError, ExcelErrorKind};
58use rayon::ThreadPoolBuilder;
59use rustc_hash::{FxHashMap, FxHashSet};
60use std::collections::{BTreeMap, BTreeSet, VecDeque};
61use std::sync::Arc;
62use std::sync::atomic::{AtomicBool, Ordering};
63use std::time::Instant;
64
65type StagedFormulaEntry = (u32, u32, String);
66type StagedSheetParts = (
67 Vec<StagedFormulaEntry>,
68 Option<crate::engine::DeferredFormulaPackage>,
69);
70
71#[derive(Default)]
81pub(crate) struct StagedSheet {
82 entries: Vec<StagedFormulaEntry>,
83 index: FxHashMap<(u32, u32), usize>,
84 deferred_package: Option<crate::engine::DeferredFormulaPackage>,
85}
86
87impl StagedSheet {
88 fn invalidate_all_deferred_families(package: &mut crate::engine::DeferredFormulaPackage) {
89 package
90 .invalidated
91 .extend(package.families.iter().map(|family| family.source_id));
92 package.invalidated.extend(
93 package
94 .partitioned_families
95 .iter()
96 .map(|family| family.source_id),
97 );
98 }
99
100 fn invalidate_deferred_at(&mut self, row: u32, col: u32) {
101 let Some(package) = self.deferred_package.as_mut() else {
102 return;
103 };
104 if package.suppressed.contains(&(row, col)) {
105 return;
108 }
109 if package.source_geometry_complete
110 && package.families.is_empty()
111 && package.partitioned_families.is_empty()
112 {
113 if let Some((row0, col0)) = row.checked_sub(1).zip(col.checked_sub(1))
114 && package
115 .source_coordinates
116 .binary_search(&crate::engine::SourceCoord {
117 row: row0,
118 col: col0,
119 })
120 .is_ok()
121 {
122 package.suppressed.insert((row, col));
123 }
124 return;
125 }
126 let family = package
127 .replay
128 .lock()
129 .ok()
130 .and_then(|mut replay| replay.formula_at(row, col).ok())
131 .flatten()
132 .and_then(|record| record.partition_owner.or(record.family));
133 if let Some(family) = family {
134 package.invalidated.insert(family);
135 } else {
136 Self::invalidate_all_deferred_families(package);
140 }
141 package.suppressed.insert((row, col));
142 }
143
144 fn reconcile_attached_deferred_package(&mut self) {
145 let Some(package) = self.deferred_package.as_mut() else {
146 return;
147 };
148 if self.entries.is_empty() {
149 return;
150 }
151
152 let replayed = (|| {
153 let mut disposition = crate::engine::FormulaReplayDisposition::default();
154 for partition in &package.partitioned_families {
155 disposition.register_partition(partition, false)?;
156 }
157 package
158 .replay
159 .lock()
160 .map_err(|_| "deferred formula spool lock poisoned".to_string())?
161 .replay_partitioned(&disposition, &package.partitioned_families)
162 })();
163
164 let Ok(records) = replayed else {
165 Self::invalidate_all_deferred_families(package);
166 package
167 .suppressed
168 .extend(self.entries.iter().map(|(row, col, _)| (*row, *col)));
169 return;
170 };
171 let mut owners = FxHashMap::default();
172 for record in &records {
173 owners
174 .entry((record.row, record.col))
175 .or_insert(record.partition_owner.or(record.family));
176 }
177 for (row, col, _) in &self.entries {
178 if let Some(family) = owners.get(&(*row, *col)).copied().flatten() {
179 package.invalidated.insert(family);
180 } else {
181 Self::invalidate_all_deferred_families(package);
182 }
183 package.suppressed.insert((*row, *col));
184 }
185 package.reconciliation_replay = Some(records);
186 }
187
188 fn stage(&mut self, row: u32, col: u32, text: String) {
189 self.invalidate_deferred_at(row, col);
190 match self.index.entry((row, col)) {
191 std::collections::hash_map::Entry::Occupied(slot) => {
192 self.entries[*slot.get()].2 = text;
193 }
194 std::collections::hash_map::Entry::Vacant(slot) => {
195 slot.insert(self.entries.len());
196 self.entries.push((row, col, text));
197 }
198 }
199 }
200
201 fn remove(&mut self, row: u32, col: u32) -> Option<String> {
202 let deferred_text = self.get(row, col);
203 self.invalidate_deferred_at(row, col);
204 let Some(idx) = self.index.remove(&(row, col)) else {
205 return deferred_text;
206 };
207 let (_, _, text) = self.entries.remove(idx);
208 for slot in self.index.values_mut() {
210 if *slot > idx {
211 *slot -= 1;
212 }
213 }
214 Some(text)
215 }
216
217 fn get_ordinary(&self, row: u32, col: u32) -> Option<&str> {
218 self.index
219 .get(&(row, col))
220 .map(|&i| self.entries[i].2.as_str())
221 }
222
223 fn remove_ordinary(&mut self, row: u32, col: u32) -> Option<String> {
224 let idx = self.index.remove(&(row, col))?;
225 let (_, _, text) = self.entries.remove(idx);
226 for slot in self.index.values_mut() {
227 if *slot > idx {
228 *slot -= 1;
229 }
230 }
231 Some(text)
232 }
233
234 fn get(&self, row: u32, col: u32) -> Option<String> {
235 if let Some(text) = self.get_ordinary(row, col) {
236 return Some(text.to_string());
237 }
238 let package = self.deferred_package.as_ref()?;
239 if package.suppressed.contains(&(row, col)) {
240 return None;
241 }
242 package
243 .replay
244 .lock()
245 .ok()?
246 .formula_at(row, col)
247 .ok()?
248 .map(|record| record.text)
249 }
250
251 fn len(&self) -> usize {
252 self.deferred_package
253 .as_ref()
254 .map_or(self.entries.len(), |package| {
255 (if package.source_geometry_complete
256 && (package.coordinates_cover_families
257 || (package.families.is_empty() && package.partitioned_families.is_empty()))
258 {
259 package.source_coordinates.len()
260 } else {
261 usize::try_from(package.report.source_formula_records_spooled)
262 .unwrap_or(usize::MAX)
263 })
264 .saturating_sub(package.suppressed.len())
265 .saturating_add(self.entries.len())
266 })
267 }
268
269 fn is_empty(&self) -> bool {
270 self.entries.is_empty() && self.deferred_package.is_none()
271 }
272
273 fn into_parts(self) -> StagedSheetParts {
274 (self.entries, self.deferred_package)
275 }
276}
277
278type StagedFormulaMap = std::collections::HashMap<String, StagedSheet>;
279
280#[derive(Clone, Debug, PartialEq, Eq, Hash)]
281struct PreparationRegion {
282 sheet: String,
283 sheet_id: SheetId,
284 start_row: u32,
285 start_col: u32,
286 end_row: u32,
287 end_col: u32,
288}
289
290struct OrderedTargetProducers {
292 ordered: Vec<crate::engine::target_preparation::TargetProducer>,
293 seen: FxHashSet<crate::engine::target_preparation::TargetProducer>,
294}
295
296impl OrderedTargetProducers {
297 fn with_capacity(capacity: usize) -> Result<Self, std::collections::TryReserveError> {
298 let mut ordered = Vec::new();
299 ordered.try_reserve(capacity)?;
300 let mut seen = FxHashSet::default();
301 seen.try_reserve(capacity)?;
302 Ok(Self { ordered, seen })
303 }
304
305 fn from_ordered(
306 ordered: Vec<crate::engine::target_preparation::TargetProducer>,
307 ) -> Result<Self, std::collections::TryReserveError> {
308 let mut seen = FxHashSet::default();
309 seen.try_reserve(ordered.len())?;
310 seen.extend(ordered.iter().copied());
311 Ok(Self { ordered, seen })
312 }
313
314 fn push(
315 &mut self,
316 producer: crate::engine::target_preparation::TargetProducer,
317 ) -> Result<bool, std::collections::TryReserveError> {
318 #[cfg(test)]
319 TARGET_ROOT_DEDUP_PROBES.with(|probes| probes.set(probes.get().saturating_add(1)));
320 if self.seen.contains(&producer) {
321 return Ok(false);
322 }
323 self.seen.try_reserve(1)?;
324 self.ordered.try_reserve(1)?;
325 self.seen.insert(producer);
326 self.ordered.push(producer);
327 Ok(true)
328 }
329
330 fn len(&self) -> usize {
331 self.ordered.len()
332 }
333
334 fn into_vec(self) -> Vec<crate::engine::target_preparation::TargetProducer> {
335 self.ordered
336 }
337}
338
339#[cfg(test)]
340thread_local! {
341 static TARGET_ROOT_DEDUP_PROBES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
342}
343
344fn target_root_allocation_error(observed: usize, request_id: Option<u64>) -> ExcelError {
345 crate::engine::ResourceLedgerError::Exhausted(formualizer_common::ResourceExhaustionDetail {
346 reason: formualizer_common::ResourceExhaustionReason::ScratchMemory,
347 limit: u64::MAX,
348 observed: observed as u64,
349 request_id,
350 })
351 .into_excel_error()
352}
353
354#[derive(Debug)]
355struct PreparedOrdinaryStagedFormula {
356 sheet: String,
357 sheet_id: SheetId,
358 lease: StagedFormulaLease,
359 ast_id: Option<AstNodeId>,
360 plan: Option<DependencyPlanRow>,
361}
362
363struct PreparedTargetSourcePackage {
364 sheet: String,
365 sheet_id: SheetId,
366 lease: StagedPackageLease,
367 selected_points: Option<BTreeSet<(u32, u32)>>,
368 complete_selections: BTreeSet<crate::engine::SourceFamilyId>,
369 deferred_shared: bool,
370 direct_domains: Vec<(
371 crate::engine::SourceFamilyId,
372 crate::engine::PlacementDomainTransport,
373 )>,
374 source_report: crate::engine::FormulaCompressedSourceReport,
375 replay_records: Vec<crate::engine::DeferredReplayFormula>,
376 spool_replays: u64,
377 disposition: crate::engine::FormulaReplayDisposition,
378 legacy: Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
379 direct_families: usize,
380 direct_cells: u64,
381 direct_fragments: u64,
382 direct_complete_families: u64,
383 direct_complete_cells: u64,
384 direct_partition_families: u64,
385 direct_partition_cells: u64,
386 anchor_parses: u64,
387 anchor_asts: u64,
388 anchor_analyses: u64,
389}
390
391impl PreparedTargetSourcePackage {
392 fn empty_selection(sheet: &str, sheet_id: SheetId, lease: StagedPackageLease) -> Self {
393 Self {
394 sheet: sheet.to_owned(),
395 sheet_id,
396 lease,
397 selected_points: Some(BTreeSet::new()),
398 complete_selections: Default::default(),
399 deferred_shared: false,
400 direct_domains: Vec::new(),
401 source_report: Default::default(),
402 replay_records: Vec::new(),
403 spool_replays: 0,
404 disposition: Default::default(),
405 legacy: Vec::new(),
406 direct_families: 0,
407 direct_cells: 0,
408 direct_fragments: 0,
409 direct_complete_families: 0,
410 direct_complete_cells: 0,
411 direct_partition_families: 0,
412 direct_partition_cells: 0,
413 anchor_parses: 0,
414 anchor_asts: 0,
415 anchor_analyses: 0,
416 }
417 }
418
419 fn direct_contains(&self, row: u32, col: u32) -> bool {
420 self.direct_domains.iter().any(|(_, domain)| {
421 let rect = domain.rect();
422 row > rect.start.row
423 && row <= rect.end.row + 1
424 && col > rect.start.col
425 && col <= rect.end.col + 1
426 })
427 }
428
429 fn fallback_records(&self) -> impl Iterator<Item = &crate::engine::DeferredReplayFormula> {
430 self.replay_records.iter().filter(|record| {
431 let Some((row, col)) = record.row.checked_sub(1).zip(record.col.checked_sub(1)) else {
432 return true;
433 };
434 let coord = crate::engine::SourceCoord { row, col };
435 let disposition = match record.family {
436 Some(family) => self.disposition.shared_disposition(family, coord),
437 None => self.disposition.ordinary_disposition(coord).0,
438 };
439 !matches!(
440 disposition,
441 crate::engine::FormulaReplayCoordinateDisposition::Direct
442 | crate::engine::FormulaReplayCoordinateDisposition::Suppressed
443 )
444 })
445 }
446}
447
448type PreparedFormulaBatches = Vec<FormulaIngestBatch>;
449type StagedFormulaBatches = Vec<(String, StagedSheet)>;
450
451type CompressedReplayBatch = (
452 FormulaIngestBatch,
453 crate::engine::FormulaCompressedSourceBatch,
454);
455type PreparedSourceBatch = (
456 FormulaIngestBatch,
457 crate::engine::FormulaCompressedSourceReport,
458 crate::engine::FormulaCompressedPreparation,
459);
460type PreparedStagedFormulaBatches = (
461 PreparedFormulaBatches,
462 Vec<CompressedReplayBatch>,
463 Vec<PreparedSourceBatch>,
464 FxHashSet<crate::engine::arena::AstNodeId>,
466);
467
468#[doc(hidden)]
471pub struct SourceFormulaIngress<'a, R> {
472 engine: &'a mut Engine<R>,
473}
474
475impl<R> SourceFormulaIngress<'_, R>
476where
477 R: EvaluationContext,
478{
479 pub fn prepare_families(
480 &mut self,
481 sheet_name: &str,
482 families: &[crate::engine::SourceFormulaFamily],
483 ) -> Result<crate::engine::FormulaCompressedPreparation, ExcelError> {
484 self.engine.observe_function_semantic_epoch()?;
485 Ok(self
486 .engine
487 .prepare_source_formula_families(sheet_name, families))
488 }
489
490 pub fn prepare_eager_proposals(
491 &mut self,
492 sheet_name: &str,
493 families: &[crate::engine::SourceFormulaFamily],
494 partitions: &[crate::engine::PartitionedSourceFormulaFamily],
495 formula_record_count: u64,
496 replay: Box<dyn crate::engine::DeferredFormulaReplay>,
497 ) -> Result<crate::engine::FormulaCompressedPreparation, ExcelError> {
498 self.engine.observe_function_semantic_epoch()?;
499 let replay = Arc::new(std::sync::Mutex::new(replay));
500 self.engine.prepare_source_formula_proposals(
501 sheet_name,
502 families,
503 partitions,
504 partitions,
505 formula_record_count,
506 replay,
507 &BTreeSet::new(),
508 &Default::default(),
509 None,
510 )
511 }
512
513 pub fn stage_deferred(&mut self, package: crate::engine::DeferredFormulaPackage) {
514 self.engine.stage_deferred_formula_package(package);
515 }
516
517 pub fn ingest_replay_batches(
518 &mut self,
519 batches: Vec<(
520 FormulaIngestBatch,
521 crate::engine::FormulaCompressedSourceBatch,
522 )>,
523 ) -> Result<FormulaIngestReport, ExcelError> {
524 self.engine
525 .ingest_compressed_formula_source_batches(batches)
526 }
527
528 pub fn finish_prepared(
529 &mut self,
530 batches: Vec<(
531 FormulaIngestBatch,
532 crate::engine::FormulaCompressedSourceReport,
533 crate::engine::FormulaCompressedPreparation,
534 )>,
535 ) -> Result<FormulaIngestReport, ExcelError> {
536 self.engine.finish_compressed_formula_sources(batches)
537 }
538}
539
540const COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH: usize = 8;
544
545#[cfg(debug_assertions)]
547fn same_value_bits(a: &LiteralValue, b: &LiteralValue) -> bool {
548 match (a, b) {
549 (LiteralValue::Number(x), LiteralValue::Number(y)) => x.to_bits() == y.to_bits(),
550 _ => a == b,
551 }
552}
553
554fn buffer_layer_writes(layer: &crate::engine::scheduler::Layer) -> bool {
557 !layer.sequential && layer.vertices.len() >= COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH
558}
559const PARALLEL_SCHEDULE_MIN_CANDIDATES: usize = 16 * 1024;
567const BASE_SCHEDULE_MIN_VERTICES: usize = 64;
571const BASE_SCHEDULE_RATIO: usize = 256;
572const BASE_SCHEDULE_MIN_REQUEST: usize = 128;
577const PARALLEL_LAYER_PROBE: std::time::Duration = std::time::Duration::from_micros(300);
578const PARALLEL_LAYER_WORTH: std::time::Duration = std::time::Duration::from_micros(150);
579const EXPENSIVE_VERTEX_NS: u128 = 10_000;
581
582#[derive(Debug, Clone, PartialEq)]
583pub(crate) enum ComputedWrite {
584 Cell {
585 seq: u64,
586 sheet_id: SheetId,
587 row0: u32,
588 col0: u32,
589 value: OverlayValue,
590 format_id: Option<crate::format::FormatId>,
591 },
592 Rect {
593 seq: u64,
594 sheet_id: SheetId,
595 sr0: u32,
596 sc0: u32,
597 values: Vec<Vec<OverlayValue>>,
598 },
599 Run {
602 seq: u64,
603 sheet_id: SheetId,
604 row0: u32,
605 col0: u32,
606 entries: Vec<(OverlayValue, Option<crate::format::FormatId>)>,
607 },
608}
609
610impl ComputedWrite {
611 #[inline]
612 pub(crate) fn seq(&self) -> u64 {
613 match self {
614 ComputedWrite::Cell { seq, .. }
615 | ComputedWrite::Rect { seq, .. }
616 | ComputedWrite::Run { seq, .. } => *seq,
617 }
618 }
619}
620
621#[derive(Debug, Default)]
622pub(crate) struct ComputedWriteBuffer {
623 writes: Vec<ComputedWrite>,
624 next_seq: u64,
625 estimated_bytes: usize,
626 formats_present: bool,
627}
628
629impl ComputedWriteBuffer {
630 const ENTRY_BASE_BYTES: usize = 32;
631
632 #[inline]
633 pub(crate) fn is_empty(&self) -> bool {
634 self.writes.is_empty()
635 }
636
637 #[inline]
638 pub(crate) fn len(&self) -> usize {
639 self.writes.len()
640 }
641
642 #[inline]
643 pub(crate) fn writes(&self) -> &[ComputedWrite] {
644 &self.writes
645 }
646
647 #[inline]
648 pub(crate) fn estimated_bytes(&self) -> usize {
649 self.estimated_bytes
650 }
651
652 #[cfg(feature = "tracing")]
653 fn traced_cell_count(&self) -> usize {
654 self.writes
655 .iter()
656 .map(|write| match write {
657 ComputedWrite::Cell { .. } => 1,
658 ComputedWrite::Rect { values, .. } => values.iter().map(Vec::len).sum(),
659 ComputedWrite::Run { entries, .. } => entries.len(),
660 })
661 .sum()
662 }
663
664 pub(crate) fn push_cell(
665 &mut self,
666 sheet_id: SheetId,
667 row0: u32,
668 col0: u32,
669 value: OverlayValue,
670 ) {
671 self.push_cell_with_format(sheet_id, row0, col0, value, None);
672 }
673
674 pub(crate) fn push_cell_with_format(
675 &mut self,
676 sheet_id: SheetId,
677 row0: u32,
678 col0: u32,
679 value: OverlayValue,
680 format_id: Option<crate::format::FormatId>,
681 ) {
682 let format_id = format_id.filter(|id| *id != crate::format::FormatId::GENERAL);
683 self.formats_present |= format_id.is_some();
684 let seq = self.next_sequence();
685 self.estimated_bytes = self
686 .estimated_bytes
687 .saturating_add(Self::estimate_value_bytes(&value));
688 self.writes.push(ComputedWrite::Cell {
689 seq,
690 sheet_id,
691 row0,
692 col0,
693 value,
694 format_id,
695 });
696 }
697
698 pub(crate) fn push_column_run(
699 &mut self,
700 sheet_id: SheetId,
701 row0: u32,
702 col0: u32,
703 mut entries: Vec<(OverlayValue, Option<crate::format::FormatId>)>,
704 ) {
705 let seq = self.next_sequence();
706 let mut added = 0usize;
707 for (value, format_id) in entries.iter_mut() {
708 *format_id = format_id.filter(|id| *id != crate::format::FormatId::GENERAL);
709 self.formats_present |= format_id.is_some();
710 added = added.saturating_add(Self::estimate_value_bytes(value));
711 }
712 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
713 self.writes.push(ComputedWrite::Run {
714 seq,
715 sheet_id,
716 row0,
717 col0,
718 entries,
719 });
720 }
721
722 pub(crate) fn push_rect(
723 &mut self,
724 sheet_id: SheetId,
725 sr0: u32,
726 sc0: u32,
727 values: Vec<Vec<OverlayValue>>,
728 ) {
729 let seq = self.next_sequence();
730 let added = values
731 .iter()
732 .flat_map(|row| row.iter())
733 .map(Self::estimate_value_bytes)
734 .fold(0usize, usize::saturating_add);
735 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
736 self.writes.push(ComputedWrite::Rect {
737 seq,
738 sheet_id,
739 sr0,
740 sc0,
741 values,
742 });
743 }
744
745 pub(crate) fn clear(&mut self) {
746 self.writes.clear();
747 self.estimated_bytes = 0;
748 self.formats_present = false;
749 }
750
751 fn take_writes(&mut self) -> (Vec<ComputedWrite>, bool) {
752 self.estimated_bytes = 0;
753 let formats_present = std::mem::take(&mut self.formats_present);
754 (std::mem::take(&mut self.writes), formats_present)
755 }
756
757 fn next_sequence(&mut self) -> u64 {
758 let seq = self.next_seq;
759 self.next_seq = self.next_seq.wrapping_add(1);
760 seq
761 }
762
763 #[inline]
764 fn estimate_value_bytes(value: &OverlayValue) -> usize {
765 Self::ENTRY_BASE_BYTES.saturating_add(value.estimated_payload_bytes())
766 }
767}
768
769#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
770struct ComputedWriteChunkKey {
771 sheet_id: SheetId,
772 col0: u32,
773 chunk_idx: usize,
774 chunk_start_row0: u32,
775}
776
777#[derive(Debug, Clone, PartialEq)]
778pub(crate) struct ComputedWriteChunkEntryPlan {
779 pub(crate) row_in_chunk: usize,
780 pub(crate) seq: u64,
781 pub(crate) value: OverlayValue,
782 pub(crate) format_id: Option<crate::format::FormatId>,
783}
784
785#[derive(Debug, Clone, PartialEq, Eq)]
786pub(crate) enum ComputedWriteChunkPlanShape {
787 Point,
788 SparseOffsets {
789 entries: usize,
790 span_len: usize,
791 },
792 DenseRange {
793 start: usize,
794 len: usize,
795 },
796 RunRange {
797 start: usize,
798 len: usize,
799 runs: usize,
800 },
801}
802
803#[derive(Debug, Clone, PartialEq, Eq)]
804pub(crate) enum ComputedWriteFormatClear {
805 Range { start: usize, end: usize },
806 Offsets(Vec<usize>),
807}
808
809#[derive(Debug, Clone, PartialEq)]
810pub(crate) enum ComputedWriteChunkFormatEffect {
811 NoFormatWork,
812 ClearStale(ComputedWriteFormatClear),
813 SetExplicit(Vec<(usize, Option<crate::format::FormatId>)>),
814}
815
816#[derive(Debug, Clone, PartialEq)]
817pub(crate) struct ComputedWriteChunkPlan {
818 pub(crate) sheet_id: SheetId,
819 pub(crate) col0: u32,
820 pub(crate) chunk_idx: usize,
821 pub(crate) chunk_start_row0: u32,
822 pub(crate) entries: Vec<ComputedWriteChunkEntryPlan>,
823 pub(crate) shape: ComputedWriteChunkPlanShape,
824 pub(crate) format_effect: ComputedWriteChunkFormatEffect,
825}
826
827#[derive(Debug, Clone, Default, PartialEq)]
828pub(crate) struct ComputedWriteCoalescingPlan {
829 pub(crate) chunks: Vec<ComputedWriteChunkPlan>,
830 pub(crate) input_cells: usize,
831 pub(crate) coalesced_cells: usize,
832 pub(crate) overwritten_cells: usize,
833}
834
835impl ComputedWriteCoalescingPlan {
836 #[inline]
837 pub(crate) fn is_empty(&self) -> bool {
838 self.chunks.is_empty()
839 }
840}
841
842impl ComputedWriteChunkPlan {
843 fn from_group(
844 key: ComputedWriteChunkKey,
845 mut entries: Vec<ComputedWriteChunkEntryPlan>,
846 formats_present: bool,
847 computed_lane_has_formats: bool,
848 ) -> (Self, usize) {
849 entries.sort_by_key(|entry| (entry.row_in_chunk, entry.seq));
850 let input_len = entries.len();
851 let mut coalesced: Vec<ComputedWriteChunkEntryPlan> = Vec::with_capacity(input_len);
852 for entry in entries {
853 if let Some(prev) = coalesced.last_mut()
854 && prev.row_in_chunk == entry.row_in_chunk
855 {
856 *prev = entry;
857 continue;
858 }
859 coalesced.push(entry);
860 }
861 let overwritten = input_len.saturating_sub(coalesced.len());
862 let shape = Self::classify_shape(&coalesced);
863 let format_effect = Self::classify_format_effect(
864 &coalesced,
865 &shape,
866 formats_present,
867 computed_lane_has_formats,
868 );
869 (
870 Self {
871 sheet_id: key.sheet_id,
872 col0: key.col0,
873 chunk_idx: key.chunk_idx,
874 chunk_start_row0: key.chunk_start_row0,
875 entries: coalesced,
876 shape,
877 format_effect,
878 },
879 overwritten,
880 )
881 }
882
883 fn classify_format_effect(
884 entries: &[ComputedWriteChunkEntryPlan],
885 shape: &ComputedWriteChunkPlanShape,
886 formats_present: bool,
887 computed_lane_has_formats: bool,
888 ) -> ComputedWriteChunkFormatEffect {
889 if !formats_present {
890 return if computed_lane_has_formats {
891 ComputedWriteChunkFormatEffect::ClearStale(Self::format_clear_spec(entries, shape))
892 } else {
893 ComputedWriteChunkFormatEffect::NoFormatWork
894 };
895 }
896
897 if entries.iter().all(|entry| entry.format_id.is_none()) {
898 return if computed_lane_has_formats {
899 ComputedWriteChunkFormatEffect::ClearStale(Self::format_clear_spec(entries, shape))
900 } else {
901 ComputedWriteChunkFormatEffect::NoFormatWork
902 };
903 }
904
905 ComputedWriteChunkFormatEffect::SetExplicit(
906 entries
907 .iter()
908 .map(|entry| (entry.row_in_chunk, entry.format_id))
909 .collect(),
910 )
911 }
912
913 fn format_clear_spec(
914 entries: &[ComputedWriteChunkEntryPlan],
915 shape: &ComputedWriteChunkPlanShape,
916 ) -> ComputedWriteFormatClear {
917 match shape {
918 ComputedWriteChunkPlanShape::DenseRange { start, len }
919 | ComputedWriteChunkPlanShape::RunRange { start, len, .. } => {
920 ComputedWriteFormatClear::Range {
921 start: *start,
922 end: start.saturating_add(*len),
923 }
924 }
925 ComputedWriteChunkPlanShape::Point
926 | ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
927 ComputedWriteFormatClear::Offsets(
928 entries.iter().map(|entry| entry.row_in_chunk).collect(),
929 )
930 }
931 }
932 }
933
934 fn classify_shape(entries: &[ComputedWriteChunkEntryPlan]) -> ComputedWriteChunkPlanShape {
935 debug_assert!(!entries.is_empty());
936 if entries.len() == 1 {
937 return ComputedWriteChunkPlanShape::Point;
938 }
939
940 let start = entries[0].row_in_chunk;
941 let end = entries[entries.len() - 1].row_in_chunk;
942 let span_len = end.saturating_sub(start).saturating_add(1);
943 if span_len != entries.len() {
944 return ComputedWriteChunkPlanShape::SparseOffsets {
945 entries: entries.len(),
946 span_len,
947 };
948 }
949
950 let runs = Self::run_count(entries);
951 if runs < entries.len() {
952 ComputedWriteChunkPlanShape::RunRange {
953 start,
954 len: entries.len(),
955 runs,
956 }
957 } else {
958 ComputedWriteChunkPlanShape::DenseRange {
959 start,
960 len: entries.len(),
961 }
962 }
963 }
964
965 fn run_count(entries: &[ComputedWriteChunkEntryPlan]) -> usize {
966 let mut runs = 0usize;
967 let mut prev: Option<&OverlayValue> = None;
968 for entry in entries {
969 if prev != Some(&entry.value) {
970 runs = runs.saturating_add(1);
971 prev = Some(&entry.value);
972 }
973 }
974 runs
975 }
976}
977
978#[cfg(feature = "tracing")]
979#[derive(Default)]
980struct TraceEvaluationCounters {
981 computed_vertices: usize,
982 cycles: usize,
983}
984
985pub struct Engine<R> {
986 pub(crate) graph: DependencyGraph,
987 resolver: R,
988 pub config: EvalConfig,
989 workbook_load_limits: crate::engine::WorkbookLoadLimits,
990 clock: crate::timezone::SnapshotClock,
995 thread_pool: Option<Arc<rayon::ThreadPool>>,
996 pub recalc_epoch: u64,
997 snapshot_id: std::sync::atomic::AtomicU64,
998 topology_epoch: u64,
999 cached_static_schedule: Option<CachedScheduleEntry>,
1000 recent_schedules: Vec<CachedScheduleEntry>,
1005 base_schedule: Option<CachedScheduleEntry>,
1010 #[cfg(any(test, feature = "benchmark_internal"))]
1011 recalc_reuse_probe: std::sync::Mutex<RecalcReuseProbe>,
1012 spill_mgr: ShimSpillManager,
1013 arrow_sheets: SheetStore,
1015 format_registry: crate::format::FormatRegistry,
1017 derived_formats: crate::engine::derived_formats::DerivedFormats,
1019 #[cfg(test)]
1020 derived_format_operations_for_test: std::sync::atomic::AtomicU64,
1021 #[cfg(test)]
1022 family_members_for_test: std::sync::atomic::AtomicU64,
1023 #[cfg(test)]
1024 invariant_bound_members_for_test: std::sync::atomic::AtomicU64,
1025 #[cfg(test)]
1026 lifted_members_for_test: std::sync::atomic::AtomicU64,
1027 #[cfg(test)]
1028 chained_members_for_test: std::sync::atomic::AtomicU64,
1029 #[cfg(test)]
1030 lane_clean_reads_for_test: std::sync::atomic::AtomicU64,
1031 #[cfg(test)]
1032 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64,
1033 #[cfg(test)]
1034 memo_hits_for_test: std::sync::atomic::AtomicU64,
1035 compressed_at_build: Option<u64>,
1037 #[cfg(test)]
1038 computed_overlay_set_explicit_entry_operations_for_test: u64,
1039 #[cfg(test)]
1040 computed_overlay_stale_clear_range_effects_for_test: u64,
1041 #[cfg(test)]
1042 computed_overlay_stale_clear_offset_attempts_for_test: u64,
1043 #[cfg(test)]
1044 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64,
1045 has_edited: bool,
1047 overlay_compactions: u64,
1049
1050 computed_overlay_bytes_estimate: usize,
1052 computed_overlay_mirroring_disabled: bool,
1053 pub(crate) force_materialize_range_views: bool,
1056 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
1058 used_axis_bounds_cache: std::sync::RwLock<Option<UsedAxisBoundsCache>>,
1060 lookup_index_cache: LookupIndexCache,
1061 source_cache: Arc<std::sync::RwLock<SourceCache>>,
1062 source_formula_token: Arc<()>,
1064 recalc_plan_token: Arc<()>,
1066 staged_formulas: StagedFormulaMap,
1068 staged_formula_index: StagedFormulaIndex,
1070 blocked_pending_spills: Vec<(VertexId, CellRef, Region)>,
1072 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
1074 row_visibility_mask_cache: std::sync::RwLock<
1076 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
1077 >,
1078 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
1080 last_formula_ingest_report: Option<FormulaIngestReport>,
1082 formula_ingest_report_total: FormulaIngestReport,
1084 active_cancel_flag: Option<crate::engine::CancelToken>,
1086 active_evaluation_deadline: Option<Instant>,
1088
1089 action_depth: u32,
1094
1095 last_virtual_dep_telemetry: VirtualDepTelemetry,
1097 virtual_dep_fallback_activations: u64,
1098
1099 last_cycle_telemetry: CycleTelemetry,
1101
1102 next_evaluation_resource_request_id: u64,
1104 evaluation_resource_request_depth: usize,
1105 active_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1106 last_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1107 evaluation_resource_baseline: EvaluationResourceBaselineStats,
1108 evaluation_resource_request_started_at: Option<crate::instant::FzInstant>,
1109 evaluation_resource_budgets: crate::engine::EvaluationBudgets,
1110 evaluation_resource_config_diagnostic:
1111 Option<crate::engine::EvaluationResourceConfigDiagnostic>,
1112 active_resource_ledger: Option<ResourceLedger>,
1113 source_cache_footprints: Vec<std::sync::Weak<std::sync::atomic::AtomicU64>>,
1114 source_cache_accounted: u64,
1115
1116 pending_iterative_redirty: Vec<VertexId>,
1135 retained_scc_members: FxHashMap<VertexId, u64>,
1153 next_retained_scc_id: u64,
1154 retained_scc_config_fingerprint: u64,
1161 retained_scc_function_epoch_seen: u64,
1166 retained_scc_provider_revision_seen: Option<u64>,
1167 retained_scc_dirty_at_begin: Vec<(VertexId, u64)>,
1174
1175 iterative_state_values: FxHashMap<VertexId, LiteralValue>,
1188
1189 function_semantic_epoch_seen: u64,
1191 function_provider_revision_seen: Option<u64>,
1193
1194 #[cfg(feature = "tracing")]
1195 trace_evaluation_counters: TraceEvaluationCounters,
1196 #[cfg(test)]
1197 evaluation_request_begin_count_for_test: u64,
1198 #[cfg(any(test, feature = "test-support"))]
1199 before_prepared_span_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1200 #[cfg(test)]
1201 before_target_preparation_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1202 #[cfg(test)]
1203 before_target_planning_snapshot_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1204 #[cfg(test)]
1205 inject_target_semantic_stale_once_for_test: bool,
1206 #[cfg(test)]
1207 force_virtual_dep_changes_remaining_for_test: usize,
1208 freshness: freshness::Freshness,
1210 #[cfg(test)]
1211 fail_evaluation_commit_preflight_once_for_test: bool,
1212 #[cfg(test)]
1213 target_preparation_fault_for_test:
1214 Option<crate::engine::target_preparation::TargetPreparationFault>,
1215 #[cfg(test)]
1216 force_non_cycle_schedule_fallback_for_test: bool,
1217 #[cfg(test)]
1218 before_legacy_fallback_final_provider_sample_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1219 #[cfg(test)]
1220 after_eager_proposal_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1221}
1222
1223impl<R: EvaluationContext> Engine<R> {
1226 pub(crate) fn ingest_pipeline(&mut self) -> crate::engine::ingest_pipeline::IngestPipeline<'_> {
1227 self.graph.ingest_pipeline(&self.resolver)
1228 }
1229}
1230
1231pub struct EngineAction<'a, R>
1232where
1233 R: EvaluationContext,
1234{
1235 engine: &'a mut Engine<R>,
1236 name: String,
1237 capture: Option<*mut MutationCapture>,
1240 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
1243 atomic_policy: bool,
1245}
1246
1247impl<'a, R> EngineAction<'a, R>
1248where
1249 R: EvaluationContext,
1250{
1251 #[inline]
1252 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
1253 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1254 let coord = crate::reference::Coord::from_excel(row, col, true, true);
1255 crate::reference::CellRef::new(sheet_id, coord)
1256 }
1257
1258 #[inline]
1259 pub fn name(&self) -> &str {
1260 &self.name
1261 }
1262
1263 #[inline]
1264 pub fn set_cell_value(
1265 &mut self,
1266 sheet: &str,
1267 row: u32,
1268 col: u32,
1269 value: LiteralValue,
1270 ) -> Result<(), crate::engine::EditorError> {
1271 if self.capture.is_some() {
1272 let old_value = self.engine.read_cell_value(sheet, row, col);
1273 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1274 let addr = self.addr_for(sheet, row, col);
1275 let Some(capture_ptr) = self.capture else {
1276 return Err(crate::engine::EditorError::TransactionFailed {
1277 reason: "action_with_logger: missing mutation capture".to_string(),
1278 });
1279 };
1280
1281 let old_comp = if self.arrow_undo.is_some() {
1284 self.engine.read_computed_overlay_cell(sheet, row, col)
1285 } else {
1286 None
1287 };
1288
1289 if self.engine.graph_admission_enabled() {
1290 let admission =
1291 self.engine
1292 .graph
1293 .preview_value_mutation(addr.sheet_id, row, col)?;
1294 self.engine.preflight_graph_admission(admission)?;
1295 }
1296 if old_formula.is_none() {
1297 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1298 }
1299
1300 let delta_old_sem = if old_formula.is_some() {
1301 None
1302 } else {
1303 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1304 };
1305
1306 let start_len = unsafe { (&*capture_ptr).len() };
1307
1308 let capture = unsafe { &mut *capture_ptr };
1310 self.engine.edit_with_capture(capture, |editor| {
1311 editor.set_cell_value_with_old_state(
1312 addr,
1313 value.clone(),
1314 old_value.clone(),
1315 old_formula.clone(),
1316 );
1317 })?;
1318 self.engine.record_structural_change(StructuralScope::Cell {
1319 sheet: addr.sheet_id,
1320 row: addr.coord.row(),
1321 col: addr.coord.col(),
1322 });
1323
1324 if let Some(undo_ptr) = self.arrow_undo {
1325 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1327 let undo = unsafe { &mut *undo_ptr };
1328 self.engine
1329 .record_spill_ops_into_arrow_undo(undo, new_events);
1330
1331 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
1333 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1334 let row0 = row.saturating_sub(1);
1335 let col0 = col.saturating_sub(1);
1336 let delta_new_sem = Some(value.clone());
1337 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
1338 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
1339 }
1340 Ok(())
1341 } else {
1342 self.engine
1343 .set_cell_value(sheet, row, col, value)
1344 .map_err(crate::engine::EditorError::from)
1345 }
1346 }
1347
1348 #[inline]
1349 pub fn set_cell_formula(
1350 &mut self,
1351 sheet: &str,
1352 row: u32,
1353 col: u32,
1354 ast: ASTNode,
1355 ) -> Result<(), crate::engine::EditorError> {
1356 if self.capture.is_some() {
1357 let old_value = self.engine.read_cell_value(sheet, row, col);
1358 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1359 let addr = self.addr_for(sheet, row, col);
1360 let Some(capture_ptr) = self.capture else {
1361 return Err(crate::engine::EditorError::TransactionFailed {
1362 reason: "action_with_logger: missing mutation capture".to_string(),
1363 });
1364 };
1365
1366 let admitted_formula = if self.engine.graph_admission_enabled() {
1367 let placement =
1368 CellRef::new(addr.sheet_id, Coord::from_excel(row, col, true, true));
1369 let ingested = self.engine.ingest_pipeline().ingest_formula(
1370 FormulaAstInput::Tree(ast.clone()),
1371 placement,
1372 None,
1373 )?;
1374 let admission = self.engine.graph.preview_formula_mutations(&[(
1375 addr.sheet_id,
1376 row,
1377 col,
1378 ingested.dep_plan.clone(),
1379 )])?;
1380 self.engine.preflight_graph_admission(admission)?;
1381 Some((ingested.ast_id, ingested.dep_plan))
1382 } else {
1383 None
1384 };
1385 if old_formula.is_none() {
1386 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1387 }
1388 let delta_old = if self.arrow_undo.is_some() {
1389 if old_formula.is_some() {
1390 None
1391 } else {
1392 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1393 }
1394 } else {
1395 None
1396 };
1397 let start_len = unsafe { (&*capture_ptr).len() };
1398
1399 let capture = unsafe { &mut *capture_ptr };
1401 self.engine.edit_with_capture(capture, |editor| {
1402 if let Some((ast_id, plan)) = admitted_formula {
1403 editor.set_cell_formula_with_prepared_plan(
1404 addr,
1405 ast.clone(),
1406 old_value,
1407 old_formula,
1408 ast_id,
1409 plan,
1410 );
1411 } else {
1412 editor.set_cell_formula_with_old_state(
1413 addr,
1414 ast.clone(),
1415 old_value,
1416 old_formula,
1417 );
1418 }
1419 })?;
1420 self.engine.record_structural_change(StructuralScope::Cell {
1421 sheet: addr.sheet_id,
1422 row: addr.coord.row(),
1423 col: addr.coord.col(),
1424 });
1425
1426 if let Some(undo_ptr) = self.arrow_undo {
1427 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1428 let undo = unsafe { &mut *undo_ptr };
1429 self.engine
1430 .record_spill_ops_into_arrow_undo(undo, new_events);
1431 let delta_new: Option<LiteralValue> = None;
1432 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1433 let row0 = row.saturating_sub(1);
1434 let col0 = col.saturating_sub(1);
1435 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
1436 }
1437 Ok(())
1438 } else {
1439 self.engine
1440 .set_cell_formula(sheet, row, col, ast)
1441 .map_err(crate::engine::EditorError::from)
1442 }
1443 }
1444
1445 #[inline]
1446 pub fn set_row_hidden(
1447 &mut self,
1448 sheet: &str,
1449 row_1based: u32,
1450 hidden: bool,
1451 source: RowVisibilitySource,
1452 ) -> Result<(), crate::engine::EditorError> {
1453 if self.capture.is_some() {
1454 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1455 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
1456 let old_hidden = self
1457 .engine
1458 .row_visibility
1459 .get(&sheet_id)
1460 .map(|state| state.is_row_hidden(row0, Some(source)))
1461 .unwrap_or(false);
1462 if old_hidden == hidden {
1463 return Ok(());
1464 }
1465
1466 let _ = self
1467 .engine
1468 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1469
1470 let Some(capture_ptr) = self.capture else {
1471 return Err(crate::engine::EditorError::TransactionFailed {
1472 reason: "action_with_logger: missing mutation capture".to_string(),
1473 });
1474 };
1475 unsafe { &mut *capture_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
1476 sheet_id,
1477 row0,
1478 source,
1479 old_hidden,
1480 new_hidden: hidden,
1481 });
1482
1483 Ok(())
1484 } else {
1485 self.engine
1486 .set_row_hidden(sheet, row_1based, hidden, source)
1487 }
1488 }
1489
1490 #[inline]
1491 pub fn set_rows_hidden(
1492 &mut self,
1493 sheet: &str,
1494 start_row_1based: u32,
1495 end_row_1based: u32,
1496 hidden: bool,
1497 source: RowVisibilitySource,
1498 ) -> Result<(), crate::engine::EditorError> {
1499 if self.capture.is_some() {
1500 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1501 let (start_row0, end_row0) =
1502 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
1503
1504 let Some(capture_ptr) = self.capture else {
1505 return Err(crate::engine::EditorError::TransactionFailed {
1506 reason: "action_with_logger: missing mutation capture".to_string(),
1507 });
1508 };
1509 let capture = unsafe { &mut *capture_ptr };
1510
1511 for row0 in start_row0..=end_row0 {
1512 let old_hidden = self
1513 .engine
1514 .row_visibility
1515 .get(&sheet_id)
1516 .map(|state| state.is_row_hidden(row0, Some(source)))
1517 .unwrap_or(false);
1518 if old_hidden == hidden {
1519 continue;
1520 }
1521
1522 let _ = self
1523 .engine
1524 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1525
1526 capture.record(crate::engine::ChangeEvent::SetRowVisibility {
1527 sheet_id,
1528 row0,
1529 source,
1530 old_hidden,
1531 new_hidden: hidden,
1532 });
1533 }
1534
1535 Ok(())
1536 } else {
1537 self.engine
1538 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
1539 }
1540 }
1541
1542 #[inline]
1543 pub fn insert_rows(
1544 &mut self,
1545 sheet: &str,
1546 before: u32,
1547 count: u32,
1548 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1549 if count == 0 {
1550 return Ok(crate::engine::ShiftSummary::default());
1551 }
1552 if self.capture.is_some() {
1553 let Some(capture_ptr) = self.capture else {
1554 return Err(crate::engine::EditorError::TransactionFailed {
1555 reason: "action_atomic: missing mutation capture".to_string(),
1556 });
1557 };
1558
1559 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1560 let before0 = before.saturating_sub(1);
1561 let occupancy = self.engine.structural_row_occupancy(sheet, sheet_id);
1562 let affected_region = Engine::<R>::structural_row_region(sheet_id, before0);
1563
1564 let summary = {
1566 let capture = unsafe { &mut *capture_ptr };
1567 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1568 Ok(crate::engine::ShiftSummary::default());
1569 self.engine.edit_with_capture(capture, |editor| {
1570 editor.set_structural_occupancy(occupancy);
1571 out = editor.insert_rows(sheet_id, before0, count);
1572 })?;
1573 out?
1574 };
1575
1576 self.engine.ensure_arrow_sheet(sheet);
1578 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1579 asheet.insert_rows(before0 as usize, count as usize);
1580 }
1581 self.engine
1582 .purge_derived_formats_after_row(sheet_id, before0);
1583 self.engine
1584 .shift_row_visibility_insert(sheet_id, before0, count);
1585 self.engine.mark_moved_formula_vertices_dirty(&summary);
1586 self.engine
1587 .clear_computed_overlay_after_row(sheet, before0 as usize);
1588 self.engine
1589 .record_structural_change(StructuralScope::Region(affected_region));
1590 if let Some(undo_ptr) = self.arrow_undo {
1591 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
1592 }
1593 Ok(summary)
1594 } else {
1595 self.engine.insert_rows(sheet, before, count)
1596 }
1597 }
1598
1599 #[inline]
1600 pub fn delete_rows(
1601 &mut self,
1602 sheet: &str,
1603 start: u32,
1604 count: u32,
1605 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1606 if count == 0 {
1607 return Ok(crate::engine::ShiftSummary::default());
1608 }
1609 if self.atomic_policy {
1610 return Err(crate::engine::EditorError::TransactionUnsupported {
1611 reason:
1612 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
1613 .to_string(),
1614 });
1615 }
1616 self.engine.delete_rows(sheet, start, count)
1617 }
1618
1619 #[inline]
1620 pub fn insert_columns(
1621 &mut self,
1622 sheet: &str,
1623 before: u32,
1624 count: u32,
1625 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1626 if count == 0 {
1627 return Ok(crate::engine::ShiftSummary::default());
1628 }
1629 if self.capture.is_some() {
1630 let Some(capture_ptr) = self.capture else {
1631 return Err(crate::engine::EditorError::TransactionFailed {
1632 reason: "action_atomic: missing mutation capture".to_string(),
1633 });
1634 };
1635
1636 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1637 let before0 = before.saturating_sub(1);
1638 let occupancy = self.engine.structural_column_occupancy();
1639 let affected_region = Engine::<R>::structural_col_region(sheet_id, before0);
1640
1641 let summary = {
1642 let capture = unsafe { &mut *capture_ptr };
1643 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1644 Ok(crate::engine::ShiftSummary::default());
1645 self.engine.edit_with_capture(capture, |editor| {
1646 editor.set_structural_occupancy(occupancy);
1647 out = editor.insert_columns(sheet_id, before0, count);
1648 })?;
1649 out?
1650 };
1651
1652 self.engine.ensure_arrow_sheet(sheet);
1653 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1654 asheet.insert_columns(before0 as usize, count as usize);
1655 }
1656 self.engine
1657 .purge_derived_formats_after_col(sheet_id, before0);
1658 self.engine.mark_moved_formula_vertices_dirty(&summary);
1659 self.engine
1660 .clear_computed_overlay_after_col(sheet, before0 as usize);
1661 self.engine
1662 .record_structural_change(StructuralScope::Region(affected_region));
1663 if let Some(undo_ptr) = self.arrow_undo {
1664 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
1665 }
1666 Ok(summary)
1667 } else {
1668 self.engine.insert_columns(sheet, before, count)
1669 }
1670 }
1671
1672 #[inline]
1673 pub fn delete_columns(
1674 &mut self,
1675 sheet: &str,
1676 start: u32,
1677 count: u32,
1678 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1679 if count == 0 {
1680 return Ok(crate::engine::ShiftSummary::default());
1681 }
1682 if self.atomic_policy {
1683 return Err(crate::engine::EditorError::TransactionUnsupported {
1684 reason:
1685 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
1686 .to_string(),
1687 });
1688 }
1689 self.engine.delete_columns(sheet, start, count)
1690 }
1691
1692 #[inline]
1697 pub fn action<T>(
1698 &mut self,
1699 name: impl AsRef<str>,
1700 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1701 ) -> Result<T, crate::engine::EditorError> {
1702 self.engine.action(name, f)
1703 }
1704}
1705
1706struct ActionDepthGuard<'a, R> {
1707 engine: *mut Engine<R>,
1708 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
1709}
1710
1711impl<'a, R> Drop for ActionDepthGuard<'a, R> {
1712 fn drop(&mut self) {
1713 unsafe {
1716 let e = &mut *self.engine;
1717 e.action_depth = e.action_depth.saturating_sub(1);
1718 }
1719 }
1720}
1721
1722#[derive(Default)]
1723struct SourceCache {
1724 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
1725 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
1726}
1727
1728#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1729struct VisibilityMaskCacheKey {
1730 sheet_id: SheetId,
1731 start_row0: u32,
1732 end_row0: u32,
1733 mode: VisibilityMaskMode,
1734 version: u64,
1735}
1736
1737#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1738enum StructuralScope {
1739 Cell { sheet: SheetId, row: u32, col: u32 },
1740 Region(Region),
1741 Sheet(SheetId),
1742 RemovedSheet(SheetId),
1743 OpaqueGlobal,
1744 AllSheets,
1745}
1746
1747#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1748enum LoggedEditImpact {
1749 NoOp,
1750 DataOnly,
1751 Topology,
1752}
1753
1754#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1755enum LoggedEditDirection {
1756 Original,
1757 InverseReplay,
1758 ForwardReplay,
1759}
1760
1761#[derive(Clone, Copy)]
1762struct InvalidationBaseline {
1763 snapshot_id: u64,
1764 topology_epoch: u64,
1765}
1766
1767struct SourceCacheSession {
1768 cache: Arc<std::sync::RwLock<SourceCache>>,
1769}
1770
1771impl Drop for SourceCacheSession {
1772 fn drop(&mut self) {
1773 if let Ok(mut g) = self.cache.write() {
1774 *g = SourceCache::default();
1775 }
1776 }
1777}
1778
1779#[derive(Debug)]
1780#[non_exhaustive]
1781pub struct EvalResult {
1782 pub computed_vertices: usize,
1783 pub cycle_errors: usize,
1784 pub elapsed: std::time::Duration,
1785}
1786
1787#[derive(Clone, Debug, PartialEq, Eq)]
1788#[non_exhaustive]
1789pub struct TableMetadata {
1790 pub name: String,
1791 pub sheet: String,
1792 pub start_row: u32,
1793 pub start_col: u32,
1794 pub end_row: u32,
1795 pub end_col: u32,
1796 pub header_row: bool,
1797 pub headers: Vec<String>,
1798 pub totals_row: bool,
1799}
1800
1801#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1809#[non_exhaustive]
1810pub struct EngineBaselineStats {
1811 pub graph_vertex_count: usize,
1812 pub graph_formula_vertex_count: usize,
1813 pub graph_edge_count: usize,
1814 pub dirty_vertex_count: usize,
1815 pub evaluation_vertex_count: usize,
1816 pub formula_ast_root_count: usize,
1817 pub formula_ast_node_count: usize,
1818 pub staged_formula_count: usize,
1819 pub formula_plane_active_span_count: usize,
1820 pub formula_plane_producer_result_entries: usize,
1821 pub formula_plane_consumer_read_entries: usize,
1822 pub formula_plane_mixed_topology_cache_builds: u64,
1823 pub formula_plane_mixed_topology_cache_hits: u64,
1824 pub formula_plane_mixed_topology_cache_overflows: u64,
1825 pub formula_plane_dirty_pending_events: usize,
1826 pub formula_plane_dirty_region_events_recorded: u64,
1827 pub formula_plane_dirty_span_region_events_recorded: u64,
1828 pub formula_plane_dirty_whole_span_seeds_recorded: u64,
1829 pub formula_plane_dirty_global_invalidations: u64,
1830 pub formula_plane_structural_span_candidates: u64,
1831 pub formula_plane_cycle_member_span_demotions: u64,
1832 pub formula_plane_array_result_span_demotions: u64,
1833 pub retained_scc_members: usize,
1836}
1837
1838#[derive(Debug, Clone, Default)]
1839#[non_exhaustive]
1840pub struct VirtualDepTelemetry {
1841 pub candidate_vertices_total: usize,
1842 pub vdeps_vertices_total: usize,
1843 pub vdeps_edges_total: usize,
1844 pub builder_elapsed_ms_total: u128,
1845 pub schedule_virtual_passes: usize,
1846 pub schedule_static_passes: usize,
1847 pub schedule_cache_hits: usize,
1848 pub schedule_cache_misses: usize,
1849 pub reused_schedule_vertices_total: usize,
1850 pub replan_iterations: usize,
1851 pub changed_vdeps_total: usize,
1852 pub bailout_reason: Option<&'static str>,
1853 pub fallback_mode_activations: u64,
1854}
1855
1856#[derive(Debug, Clone, Default, PartialEq)]
1865#[non_exhaustive]
1866pub struct CycleTelemetry {
1867 pub static_sccs: usize,
1869 pub phantom_sccs: usize,
1871 pub live_cycles_witnessed: usize,
1873 pub circ_cells_stamped: usize,
1875 pub settle_passes_total: usize,
1878 pub max_passes_single_scc: usize,
1880 pub iterated_sccs: usize,
1883 pub converged_sccs: usize,
1886 pub capped_sccs: usize,
1892 pub max_abs_delta_at_stop: f64,
1896 pub nan_converged: usize,
1899 pub reused_sccs: usize,
1904 pub reused_scc_members: usize,
1906 pub elapsed_ms: u128,
1908}
1909
1910#[derive(Debug, Clone, Copy)]
1911struct ScheduleBuildMeta {
1912 candidate_vertices: usize,
1913 vdeps_vertices: usize,
1914 vdeps_edges: usize,
1915 builder_elapsed_ms: u128,
1916 used_virtual_schedule: bool,
1917 schedule_cache_hit: bool,
1918 schedule_cache_eligible: bool,
1919}
1920
1921#[cfg(any(test, feature = "benchmark_internal"))]
1922#[doc(hidden)]
1923#[derive(Debug, Clone, Default)]
1924pub struct RecalcReuseProbe {
1925 pub schedule_requests: usize,
1926 pub schedule_cache_hits: usize,
1927 pub schedule_cache_misses: usize,
1928 pub schedule_cache_ineligible: usize,
1929 pub schedule_builds: usize,
1930 pub schedule_base_restrictions: usize,
1932 pub schedule_shared_handles: usize,
1933 pub schedule_retained_bytes: usize,
1934 pub legacy_target_requests: usize,
1935 pub target_schedule_builds: usize,
1936 pub demand_builds: usize,
1937 pub demand_vertices: usize,
1938 pub demand_clean_formulas: usize,
1939 pub demand_explicit_edges: usize,
1940 pub demand_virtual_builder_calls: usize,
1941}
1942
1943#[cfg(any(test, feature = "benchmark_internal"))]
1944fn schedule_probe_retained_bytes(schedule: &crate::engine::Schedule) -> usize {
1945 fn vector_bytes<T>(values: &Vec<T>) -> usize {
1946 values.capacity() * std::mem::size_of::<T>()
1947 }
1948
1949 [
1950 vector_bytes(&schedule.units),
1951 vector_bytes(&schedule.layers),
1952 vector_bytes(&schedule.cycles),
1953 ]
1954 .into_iter()
1955 .chain(
1956 schedule
1957 .layers
1958 .iter()
1959 .map(|layer| vector_bytes(&layer.vertices)),
1960 )
1961 .chain(schedule.cycles.iter().map(vector_bytes))
1962 .sum()
1963}
1964
1965#[derive(Debug, Clone)]
1966struct CachedScheduleEntry {
1967 topology_epoch: u64,
1968 authority_revision: (u64, u64),
1971 candidate_vertices: VertexIdRuns,
1974 schedule: Arc<crate::engine::scheduler::Schedule>,
1975}
1976
1977#[derive(Debug, Clone, Default)]
1980struct VertexIdRuns(Vec<(u32, u32)>);
1981
1982impl VertexIdRuns {
1983 fn from_slice(ids: &[VertexId]) -> Self {
1984 let mut runs: Vec<(u32, u32)> = Vec::new();
1985 for v in ids {
1986 match runs.last_mut() {
1987 Some((first, len)) if first.checked_add(*len) == Some(v.0) => *len += 1,
1988 _ => runs.push((v.0, 1)),
1989 }
1990 }
1991 runs.shrink_to_fit();
1992 Self(runs)
1993 }
1994
1995 fn equals(&self, ids: &[VertexId]) -> bool {
1996 let mut rest = ids;
1997 for &(first, len) in &self.0 {
1998 let len = len as usize;
1999 if rest.len() < len {
2000 return false;
2001 }
2002 let (head, tail) = rest.split_at(len);
2003 if head
2004 .iter()
2005 .enumerate()
2006 .any(|(i, v)| v.0 != first.wrapping_add(i as u32))
2007 {
2008 return false;
2009 }
2010 rest = tail;
2011 }
2012 rest.is_empty()
2013 }
2014
2015 fn heap_bytes(&self) -> usize {
2016 self.0.capacity() * std::mem::size_of::<(u32, u32)>()
2017 }
2018
2019 fn len(&self) -> usize {
2021 self.0.iter().map(|&(_, len)| len as usize).sum()
2022 }
2023}
2024
2025#[cfg(test)]
2026mod vertex_id_runs_tests {
2027 use super::{VertexId, VertexIdRuns};
2028
2029 #[test]
2030 fn runs_compare_like_the_list() {
2031 let ids = |v: &[u32]| v.iter().map(|&i| VertexId(i)).collect::<Vec<_>>();
2032 let list = ids(&[5, 6, 7, 2, 3, 9, 10, 10]);
2033 let runs = VertexIdRuns::from_slice(&list);
2034 assert_eq!(runs.0, vec![(5, 3), (2, 2), (9, 2), (10, 1)]);
2035 assert!(runs.equals(&list));
2036 assert!(!runs.equals(&list[..7]));
2037 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 11])));
2038 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 10, 11])));
2039 assert!(VertexIdRuns::from_slice(&[]).equals(&[]));
2040 assert!(!VertexIdRuns::from_slice(&[]).equals(&list));
2041 let edge = ids(&[u32::MAX - 1, u32::MAX, 0]);
2042 assert!(VertexIdRuns::from_slice(&edge).equals(&edge));
2043 }
2044}
2045
2046enum EvaluationSchedule {
2048 Owned(crate::engine::scheduler::Schedule),
2049 Shared(Arc<crate::engine::scheduler::Schedule>),
2050}
2051
2052impl std::ops::Deref for EvaluationSchedule {
2053 type Target = crate::engine::scheduler::Schedule;
2054
2055 fn deref(&self) -> &Self::Target {
2056 match self {
2057 Self::Owned(schedule) => schedule,
2058 Self::Shared(schedule) => schedule,
2059 }
2060 }
2061}
2062
2063type ScheduleBuildOutput = (
2064 crate::engine::scheduler::Schedule,
2065 FxHashMap<VertexId, Vec<VertexId>>,
2066 ScheduleBuildMeta,
2067);
2068
2069type EvaluationScheduleBuildOutput = (
2070 EvaluationSchedule,
2071 FxHashMap<VertexId, Vec<VertexId>>,
2072 ScheduleBuildMeta,
2073);
2074
2075#[derive(Debug)]
2077pub struct RecalcPlan {
2078 key: RecalcPlanKey,
2079 kind: RecalcPlanKind,
2080}
2081
2082#[derive(Debug)]
2083struct RecalcPlanKey {
2084 engine_token: Arc<()>,
2085 revisions: PlanningRevisionSnapshot,
2086}
2087
2088#[derive(Clone, Debug, PartialEq, Eq)]
2089struct PlanningRevisionSnapshot {
2090 engine_topology_epoch: u64,
2091 graph_topology_revision: u64,
2092 staged: u64,
2093 symbols: u64,
2094 semantic: u64,
2095 provider: Option<u64>,
2096 deterministic_mode: crate::engine::DeterministicMode,
2097 budgets: crate::engine::EvaluationBudgets,
2098}
2099
2100#[derive(Debug)]
2101enum RecalcPlanKind {
2102 CompatibilityFull {
2103 schedule: crate::engine::Schedule,
2104 has_dynamic_refs: bool,
2105 },
2106 Target {
2107 targets: Vec<crate::engine::EvaluationTarget>,
2108 scope: crate::engine::PrepareScope,
2109 topology: RecalcTopology,
2110 dynamic_policy: DynamicPlanPolicy,
2111 },
2112}
2113
2114#[derive(Debug)]
2115enum RecalcTopology {
2116 RunLocalRecipe,
2117 Workbook,
2118}
2119
2120#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2121enum DynamicPlanPolicy {
2122 BoundedTargetReplan,
2123}
2124
2125impl RecalcPlan {
2126 pub fn layer_count(&self) -> usize {
2129 match &self.kind {
2130 RecalcPlanKind::CompatibilityFull { schedule, .. } => schedule.layers.len(),
2131 RecalcPlanKind::Target { .. } => 0,
2132 }
2133 }
2134
2135 pub fn has_dynamic_refs(&self) -> bool {
2136 match &self.kind {
2137 RecalcPlanKind::CompatibilityFull {
2138 has_dynamic_refs, ..
2139 } => *has_dynamic_refs,
2140 RecalcPlanKind::Target { .. } => false,
2141 }
2142 }
2143
2144 #[cfg(test)]
2145 pub(crate) fn force_stale_reasons_for_test(
2146 &mut self,
2147 reasons: &[formualizer_common::PlanStaleReason],
2148 ) {
2149 use formualizer_common::PlanStaleReason;
2150 for reason in reasons {
2151 match reason {
2152 PlanStaleReason::Engine => {
2153 self.key.engine_token = Arc::new(());
2154 }
2155 PlanStaleReason::Provider => {
2156 self.key.revisions.provider = Some(
2157 self.key
2158 .revisions
2159 .provider
2160 .unwrap_or_default()
2161 .wrapping_add(1),
2162 );
2163 }
2164 PlanStaleReason::Semantic => {
2165 self.key.revisions.semantic = self.key.revisions.semantic.wrapping_add(1);
2166 }
2167 PlanStaleReason::Budget => {
2168 let current = self.key.revisions.budgets.work.max_work_units;
2169 self.key.revisions.budgets.work.max_work_units =
2170 Some(current.unwrap_or_default().wrapping_add(1));
2171 }
2172 PlanStaleReason::Staged => {
2173 self.key.revisions.staged = self.key.revisions.staged.wrapping_add(1);
2174 }
2175 PlanStaleReason::Symbols => {
2176 self.key.revisions.symbols = self.key.revisions.symbols.wrapping_add(1);
2177 }
2178 PlanStaleReason::Graph => {
2179 self.key.revisions.graph_topology_revision =
2180 self.key.revisions.graph_topology_revision.wrapping_add(1);
2181 }
2182 _ => {}
2183 }
2184 }
2185 }
2186}
2187
2188#[cfg(any(test, feature = "test-support"))]
2189pub(crate) mod criteria_mask_test_hooks {
2190 use std::cell::Cell;
2191
2192 thread_local! {
2193 static MASK_CALLS_ROWS: Cell<(usize, usize)> = const { Cell::new((0, 0)) };
2194 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
2195 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
2196 }
2197
2198 pub(crate) fn take_mask_work() -> (usize, usize) {
2199 MASK_CALLS_ROWS.with(|c| c.replace((0, 0)))
2200 }
2201
2202 pub(crate) fn note_mask(rows: usize) {
2203 MASK_CALLS_ROWS.with(|c| {
2204 let (calls, work) = c.get();
2205 c.set((calls + 1, work + rows));
2206 });
2207 }
2208
2209 pub fn reset_text_segment_counters() {
2210 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
2211 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
2212 }
2213
2214 pub fn text_segment_counters() -> (usize, usize) {
2215 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
2216 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
2217 (a, b)
2218 }
2219
2220 pub(crate) fn inc_total() {
2221 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
2222 }
2223 pub(crate) fn inc_all_null() {
2224 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
2225 }
2226}
2227
2228#[cfg(test)]
2229pub(crate) mod visibility_mask_test_hooks {
2230 use std::cell::Cell;
2231
2232 thread_local! {
2233 static HITS: Cell<usize> = const { Cell::new(0) };
2234 static MISSES: Cell<usize> = const { Cell::new(0) };
2235 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
2236 }
2237
2238 pub fn reset() {
2239 HITS.with(|c| c.set(0));
2240 MISSES.with(|c| c.set(0));
2241 EVICTIONS.with(|c| c.set(0));
2242 }
2243
2244 pub fn counters() -> (usize, usize, usize) {
2245 let hits = HITS.with(|c| c.get());
2246 let misses = MISSES.with(|c| c.get());
2247 let evictions = EVICTIONS.with(|c| c.get());
2248 (hits, misses, evictions)
2249 }
2250
2251 pub(crate) fn inc_hit() {
2252 HITS.with(|c| c.set(c.get() + 1));
2253 }
2254
2255 pub(crate) fn inc_miss() {
2256 MISSES.with(|c| c.set(c.get() + 1));
2257 }
2258
2259 pub(crate) fn inc_eviction() {
2260 EVICTIONS.with(|c| c.set(c.get() + 1));
2261 }
2262}
2263
2264fn is_numeric_text_equality(pred: &crate::args::CriteriaPredicate) -> bool {
2265 match pred {
2266 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(text)) => {
2267 text.trim().parse::<f64>().is_ok_and(f64::is_finite)
2268 }
2269 _ => false,
2270 }
2271}
2272
2273fn compute_criteria_mask(
2274 view: &RangeView<'_>,
2275 col_in_view: usize,
2276 pred: &crate::args::CriteriaPredicate,
2277) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
2278 use crate::compute_prelude::{boolean, cmp, concat_arrays};
2279 use arrow::compute::kernels::comparison::{ilike, nilike};
2280 use arrow_array::{
2281 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
2282 };
2283
2284 fn apply_numeric_pred(
2286 chunk: &Float64Array,
2287 pred: &crate::args::CriteriaPredicate,
2288 ) -> Option<BooleanArray> {
2289 match pred {
2290 crate::args::CriteriaPredicate::Gt(n) => {
2291 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
2292 }
2293 crate::args::CriteriaPredicate::Ge(n) => {
2294 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2295 }
2296 crate::args::CriteriaPredicate::Lt(n) => {
2297 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
2298 }
2299 crate::args::CriteriaPredicate::Le(n) => {
2300 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2301 }
2302 crate::args::CriteriaPredicate::Eq(v) => match v {
2303 formualizer_common::LiteralValue::Number(x) => {
2304 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
2305 }
2306 formualizer_common::LiteralValue::Int(i) => {
2307 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2308 }
2309 _ => None,
2310 },
2311 crate::args::CriteriaPredicate::Ne(v) => match v {
2312 formualizer_common::LiteralValue::Number(x) => {
2313 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
2314 }
2315 formualizer_common::LiteralValue::Int(i) => {
2316 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2317 }
2318 _ => None,
2319 },
2320 _ => None,
2321 }
2322 }
2323
2324 let is_numeric_pred = matches!(
2326 pred,
2327 crate::args::CriteriaPredicate::Gt(_)
2328 | crate::args::CriteriaPredicate::Ge(_)
2329 | crate::args::CriteriaPredicate::Lt(_)
2330 | crate::args::CriteriaPredicate::Le(_)
2331 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
2332 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
2333 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
2334 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
2335 );
2336
2337 if is_numeric_pred {
2341 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2342 for res in view.numbers_slices() {
2343 let (_rs, _rl, cols_seg) = res.ok()?;
2344 if col_in_view < cols_seg.len() {
2345 let chunk = cols_seg[col_in_view].as_ref();
2346 let mask = apply_numeric_pred(chunk, pred)?;
2347 bool_parts.push(mask);
2348 }
2349 }
2350
2351 if bool_parts.is_empty() {
2352 return None;
2353 } else if bool_parts.len() == 1 {
2354 return Some(std::sync::Arc::new(bool_parts.remove(0)));
2355 } else {
2356 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2358 .iter()
2359 .map(|a| a as &dyn arrow_array::Array)
2360 .collect();
2361 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2362 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2363 return Some(std::sync::Arc::new(ba));
2364 }
2365 }
2366
2367 if is_numeric_text_equality(pred)
2371 || matches!(pred, crate::args::CriteriaPredicate::TextLike { .. })
2372 {
2373 for tags in view.type_tags_slices() {
2374 let (_, _, cols) = tags.ok()?;
2375 let tags = cols.get(col_in_view)?;
2376 if tags.values().iter().any(|tag| {
2377 *tag == crate::arrow_store::TypeTag::Empty as u8
2378 || *tag == crate::arrow_store::TypeTag::Number as u8
2379 || *tag == crate::arrow_store::TypeTag::Boolean as u8
2380 }) {
2381 let mut mask = BooleanBuilder::new();
2382 for chunk in view.iter_row_chunks() {
2383 let chunk = chunk.ok()?;
2384 for row in chunk.row_start..chunk.row_start + chunk.row_len {
2385 mask.append_value(crate::builtins::criteria_match(
2386 pred,
2387 &view.get_cell(row, col_in_view),
2388 ));
2389 }
2390 }
2391 return Some(std::sync::Arc::new(mask.finish()));
2392 }
2393 }
2394 }
2395
2396 let (text_kind, text_pat, empty_special) = match pred {
2399 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
2400 (0u8, t.to_lowercase(), t.is_empty())
2401 }
2402 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
2403 (1u8, t.to_lowercase(), false)
2404 }
2405 crate::args::CriteriaPredicate::TextLike {
2406 pattern,
2407 case_insensitive,
2408 } => {
2409 let p = if *case_insensitive {
2410 pattern.to_lowercase()
2411 } else {
2412 pattern.clone()
2413 };
2414 (2u8, p.replace('*', "%").replace('?', "_"), false)
2415 }
2416 _ => return None,
2417 };
2418
2419 let text_pat_is_empty = text_pat.is_empty();
2420 let ne_matches_blank = text_kind == 1 && !text_pat_is_empty;
2421 let pat = StringArray::new_scalar(text_pat);
2422 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2423
2424 let mut tag_slices = view.type_tags_slices();
2425 for res in view.iter_row_chunks() {
2426 let cs = res.ok()?;
2427 if cs.row_len == 0 {
2428 continue;
2429 }
2430 #[cfg(test)]
2431 criteria_mask_test_hooks::inc_total();
2432
2433 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
2434 if col_in_view >= slices.len() {
2435 return None;
2436 }
2437
2438 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
2439 if empty_special || (text_kind == 1 && text_pat_is_empty) {
2440 let (tag_start, tag_len, tags) = tag_slices.next()?.ok()?;
2441 if tag_start != cs.row_start || tag_len != cs.row_len {
2442 return None;
2443 }
2444 let tags = tags.get(col_in_view)?;
2445 let strings = seg_opt.and_then(|a| a.as_any().downcast_ref::<StringArray>());
2449 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2450 for i in 0..cs.row_len {
2451 let blank = tags.value(i) == crate::arrow_store::TypeTag::Empty as u8
2452 || (tags.value(i) == crate::arrow_store::TypeTag::Text as u8
2453 && strings.is_some_and(|s| s.is_valid(i) && s.value(i).is_empty()));
2454 bb.append_value(if text_kind == 0 { blank } else { !blank });
2455 }
2456 #[cfg(test)]
2457 if seg_opt.is_none() {
2458 criteria_mask_test_hooks::inc_all_null();
2459 }
2460 bool_parts.push(bb.finish());
2461 continue;
2462 }
2463 let seg = match seg_opt {
2464 Some(s) => s,
2465 None => {
2466 #[cfg(test)]
2467 criteria_mask_test_hooks::inc_all_null();
2468 if (text_kind == 0 && empty_special) || ne_matches_blank {
2469 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2471 bb.append_n(cs.row_len, true);
2472 bool_parts.push(bb.finish());
2473 } else {
2474 bool_parts.push(BooleanArray::new_null(cs.row_len));
2476 }
2477 continue;
2478 }
2479 };
2480
2481 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
2482 let mut m = match text_kind {
2483 0 => ilike(seg_sa, &pat).ok()?,
2484 1 => nilike(seg_sa, &pat).ok()?,
2485 2 => ilike(seg_sa, &pat).ok()?,
2486 _ => return None,
2487 };
2488
2489 if ((text_kind == 0 && empty_special) || ne_matches_blank) && seg_sa.null_count() > 0 {
2495 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
2497 for i in 0..seg_sa.len() {
2498 bb.append_value(seg_sa.is_null(i));
2499 }
2500 let nulls = bb.finish();
2501 m = boolean::or_kleene(&m, &nulls).ok()?;
2502 }
2503
2504 bool_parts.push(m);
2505 }
2506
2507 if bool_parts.is_empty() {
2508 None
2509 } else if bool_parts.len() == 1 {
2510 Some(std::sync::Arc::new(bool_parts.remove(0)))
2511 } else {
2512 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2513 .iter()
2514 .map(|a| a as &dyn arrow_array::Array)
2515 .collect();
2516 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2517 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2518 Some(std::sync::Arc::new(ba))
2519 }
2520}
2521
2522#[derive(Debug, Clone)]
2523pub struct LayerInfo {
2524 pub vertex_count: usize,
2525 pub parallel_eligible: bool,
2526 pub sample_cells: Vec<String>, }
2528
2529#[derive(Debug, Clone)]
2530pub struct EvalPlan {
2531 pub total_vertices_to_evaluate: usize,
2532 pub layers: Vec<LayerInfo>,
2533 pub cycles_detected: usize,
2534 pub dirty_count: usize,
2535 pub volatile_count: usize,
2536 pub parallel_enabled: bool,
2537 pub estimated_parallel_layers: usize,
2538 pub target_cells: Vec<String>,
2539}
2540
2541#[inline]
2544fn plane_mode_ignored() -> bool {
2545 true
2546}
2547
2548#[cfg(feature = "test-support")]
2551#[doc(hidden)]
2552pub fn formula_plane_mode_ignored_for_test() -> bool {
2553 plane_mode_ignored()
2554}
2555
2556impl<R> Engine<R>
2557where
2558 R: EvaluationContext,
2559{
2560 pub fn new(resolver: R, config: EvalConfig) -> Self {
2567 let config = if plane_mode_ignored() {
2572 EvalConfig {
2573 formula_plane_mode: FormulaPlaneMode::Off,
2574 ..config
2575 }
2576 } else {
2577 config
2578 };
2579 if let Err(msg) = config.cycle.validate() {
2580 panic!("invalid CycleConfig: {msg}");
2581 }
2582 crate::builtins::load_builtins();
2583 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2584 &config.evaluation_budgets,
2585 config.max_vertices,
2586 config.max_memory_mb,
2587 config.max_eval_time,
2588 );
2589
2590 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2591 #[cfg(feature = "system-clock")]
2592 {
2593 Arc::new(crate::timezone::SystemClock::new(
2594 crate::timezone::TimeZoneSpec::default(),
2595 ))
2596 }
2597 #[cfg(not(feature = "system-clock"))]
2598 {
2599 Arc::new(crate::timezone::FixedClock::new(
2600 chrono::DateTime::UNIX_EPOCH,
2601 crate::timezone::TimeZoneSpec::Utc,
2602 ))
2603 }
2604 });
2605
2606 let thread_pool = if config.enable_parallel {
2608 let mut builder = ThreadPoolBuilder::new();
2609 if let Some(max_threads) = config.max_threads {
2610 builder = builder.num_threads(max_threads);
2611 }
2612
2613 match builder.build() {
2614 Ok(pool) => Some(Arc::new(pool)),
2615 Err(_) => {
2616 None
2618 }
2619 }
2620 } else {
2621 None
2622 };
2623
2624 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
2626 let function_provider_revision_seen = resolver.planning_semantic_revision();
2627 let mut engine = Self {
2628 graph: DependencyGraph::new_with_config(config.clone()),
2629 resolver,
2630 config,
2631 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
2632 clock: crate::timezone::SnapshotClock::new(clock),
2633 thread_pool,
2634 recalc_epoch: 0,
2635 snapshot_id: std::sync::atomic::AtomicU64::new(1),
2636 topology_epoch: 0,
2637 cached_static_schedule: None,
2638 recent_schedules: Vec::new(),
2639 base_schedule: None,
2640 #[cfg(any(test, feature = "benchmark_internal"))]
2641 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
2642 spill_mgr: ShimSpillManager::default(),
2643 arrow_sheets: SheetStore::default(),
2644 format_registry: crate::format::FormatRegistry::default(),
2645 derived_formats: Default::default(),
2646 #[cfg(test)]
2647 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
2648 #[cfg(test)]
2649 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
2650 #[cfg(test)]
2651 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
2652 #[cfg(test)]
2653 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
2654 #[cfg(test)]
2655 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
2656 #[cfg(test)]
2657 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
2658 #[cfg(test)]
2659 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
2660 #[cfg(test)]
2661 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
2662 compressed_at_build: None,
2663 #[cfg(test)]
2664 computed_overlay_set_explicit_entry_operations_for_test: 0,
2665 #[cfg(test)]
2666 computed_overlay_stale_clear_range_effects_for_test: 0,
2667 #[cfg(test)]
2668 computed_overlay_stale_clear_offset_attempts_for_test: 0,
2669 #[cfg(test)]
2670 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
2671 has_edited: false,
2672 overlay_compactions: 0,
2673 computed_overlay_bytes_estimate: 0,
2674 computed_overlay_mirroring_disabled: false,
2675 force_materialize_range_views: false,
2676 row_bounds_cache: std::sync::RwLock::new(None),
2677 used_axis_bounds_cache: std::sync::RwLock::new(None),
2678 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
2679 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
2680 source_formula_token: Arc::new(()),
2681 recalc_plan_token: Arc::new(()),
2682 staged_formulas: std::collections::HashMap::new(),
2683 staged_formula_index: StagedFormulaIndex::default(),
2684 blocked_pending_spills: Vec::new(),
2685 row_visibility: FxHashMap::default(),
2686 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
2687 formula_parse_diagnostics: Vec::new(),
2688 last_formula_ingest_report: None,
2689 formula_ingest_report_total: FormulaIngestReport::default(),
2690 active_cancel_flag: None,
2691 active_evaluation_deadline: None,
2692 action_depth: 0,
2693 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
2694 virtual_dep_fallback_activations: 0,
2695 last_cycle_telemetry: CycleTelemetry::default(),
2696 next_evaluation_resource_request_id: 1,
2697 evaluation_resource_request_depth: 0,
2698 active_evaluation_resource_request: None,
2699 last_evaluation_resource_request: None,
2700 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
2701 evaluation_resource_request_started_at: None,
2702 evaluation_resource_budgets: resolved_resources.budgets,
2703 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
2704 active_resource_ledger: None,
2705 source_cache_footprints: Vec::new(),
2706 source_cache_accounted: 0,
2707 pending_iterative_redirty: Vec::new(),
2708 retained_scc_members: FxHashMap::default(),
2709 next_retained_scc_id: 0,
2710 retained_scc_config_fingerprint: 0,
2711 retained_scc_function_epoch_seen: 0,
2712 retained_scc_provider_revision_seen: None,
2713 retained_scc_dirty_at_begin: Vec::new(),
2714 iterative_state_values: FxHashMap::default(),
2715 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
2716 function_provider_revision_seen,
2717 #[cfg(feature = "tracing")]
2718 trace_evaluation_counters: TraceEvaluationCounters::default(),
2719 #[cfg(test)]
2720 evaluation_request_begin_count_for_test: 0,
2721 #[cfg(any(test, feature = "test-support"))]
2722 before_prepared_span_commit_hook: None,
2723 #[cfg(test)]
2724 before_target_preparation_commit_hook: None,
2725 #[cfg(test)]
2726 before_target_planning_snapshot_hook: None,
2727 #[cfg(test)]
2728 inject_target_semantic_stale_once_for_test: false,
2729 #[cfg(test)]
2730 force_virtual_dep_changes_remaining_for_test: 0,
2731 freshness: Default::default(),
2732 #[cfg(test)]
2733 fail_evaluation_commit_preflight_once_for_test: false,
2734 #[cfg(test)]
2735 target_preparation_fault_for_test: None,
2736 #[cfg(test)]
2737 force_non_cycle_schedule_fallback_for_test: false,
2738 #[cfg(test)]
2739 before_legacy_fallback_final_provider_sample_hook: None,
2740 #[cfg(test)]
2741 after_eager_proposal_commit_hook: None,
2742 };
2743 engine.config.arrow_storage_enabled = true;
2745 engine.config.delta_overlay_enabled = true;
2746 engine.config.write_formula_overlay_enabled = true;
2747 let default_sheet = engine.graph.default_sheet_name().to_string();
2748 engine.ensure_arrow_sheet(&default_sheet);
2749 engine
2750 }
2751
2752 pub fn with_thread_pool(
2757 resolver: R,
2758 config: EvalConfig,
2759 thread_pool: Arc<rayon::ThreadPool>,
2760 ) -> Self {
2761 if let Err(msg) = config.cycle.validate() {
2762 panic!("invalid CycleConfig: {msg}");
2763 }
2764 crate::builtins::load_builtins();
2765 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2766 &config.evaluation_budgets,
2767 config.max_vertices,
2768 config.max_memory_mb,
2769 config.max_eval_time,
2770 );
2771 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2772 #[cfg(feature = "system-clock")]
2773 {
2774 Arc::new(crate::timezone::SystemClock::new(
2775 crate::timezone::TimeZoneSpec::default(),
2776 ))
2777 }
2778 #[cfg(not(feature = "system-clock"))]
2779 {
2780 Arc::new(crate::timezone::FixedClock::new(
2781 chrono::DateTime::UNIX_EPOCH,
2782 crate::timezone::TimeZoneSpec::Utc,
2783 ))
2784 }
2785 });
2786 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
2788 let function_provider_revision_seen = resolver.planning_semantic_revision();
2789 let mut engine = Self {
2790 graph: DependencyGraph::new_with_config(config.clone()),
2791 resolver,
2792 config,
2793 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
2794 clock: crate::timezone::SnapshotClock::new(clock),
2795 thread_pool: Some(thread_pool),
2796 recalc_epoch: 0,
2797 snapshot_id: std::sync::atomic::AtomicU64::new(1),
2798 topology_epoch: 0,
2799 cached_static_schedule: None,
2800 recent_schedules: Vec::new(),
2801 base_schedule: None,
2802 #[cfg(any(test, feature = "benchmark_internal"))]
2803 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
2804 spill_mgr: ShimSpillManager::default(),
2805 arrow_sheets: SheetStore::default(),
2806 format_registry: crate::format::FormatRegistry::default(),
2807 derived_formats: Default::default(),
2808 #[cfg(test)]
2809 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
2810 #[cfg(test)]
2811 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
2812 #[cfg(test)]
2813 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
2814 #[cfg(test)]
2815 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
2816 #[cfg(test)]
2817 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
2818 #[cfg(test)]
2819 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
2820 #[cfg(test)]
2821 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
2822 #[cfg(test)]
2823 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
2824 compressed_at_build: None,
2825 #[cfg(test)]
2826 computed_overlay_set_explicit_entry_operations_for_test: 0,
2827 #[cfg(test)]
2828 computed_overlay_stale_clear_range_effects_for_test: 0,
2829 #[cfg(test)]
2830 computed_overlay_stale_clear_offset_attempts_for_test: 0,
2831 #[cfg(test)]
2832 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
2833 has_edited: false,
2834 overlay_compactions: 0,
2835 computed_overlay_bytes_estimate: 0,
2836 computed_overlay_mirroring_disabled: false,
2837 force_materialize_range_views: false,
2838 row_bounds_cache: std::sync::RwLock::new(None),
2839 used_axis_bounds_cache: std::sync::RwLock::new(None),
2840 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
2841 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
2842 source_formula_token: Arc::new(()),
2843 recalc_plan_token: Arc::new(()),
2844 staged_formulas: std::collections::HashMap::new(),
2845 staged_formula_index: StagedFormulaIndex::default(),
2846 blocked_pending_spills: Vec::new(),
2847 row_visibility: FxHashMap::default(),
2848 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
2849 formula_parse_diagnostics: Vec::new(),
2850 last_formula_ingest_report: None,
2851 formula_ingest_report_total: FormulaIngestReport::default(),
2852 active_cancel_flag: None,
2853 active_evaluation_deadline: None,
2854 action_depth: 0,
2855 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
2856 virtual_dep_fallback_activations: 0,
2857 last_cycle_telemetry: CycleTelemetry::default(),
2858 next_evaluation_resource_request_id: 1,
2859 evaluation_resource_request_depth: 0,
2860 active_evaluation_resource_request: None,
2861 last_evaluation_resource_request: None,
2862 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
2863 evaluation_resource_request_started_at: None,
2864 evaluation_resource_budgets: resolved_resources.budgets,
2865 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
2866 active_resource_ledger: None,
2867 source_cache_footprints: Vec::new(),
2868 source_cache_accounted: 0,
2869 pending_iterative_redirty: Vec::new(),
2870 retained_scc_members: FxHashMap::default(),
2871 next_retained_scc_id: 0,
2872 retained_scc_config_fingerprint: 0,
2873 retained_scc_function_epoch_seen: 0,
2874 retained_scc_provider_revision_seen: None,
2875 retained_scc_dirty_at_begin: Vec::new(),
2876 iterative_state_values: FxHashMap::default(),
2877 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
2878 function_provider_revision_seen,
2879 #[cfg(feature = "tracing")]
2880 trace_evaluation_counters: TraceEvaluationCounters::default(),
2881 #[cfg(test)]
2882 evaluation_request_begin_count_for_test: 0,
2883 #[cfg(any(test, feature = "test-support"))]
2884 before_prepared_span_commit_hook: None,
2885 #[cfg(test)]
2886 before_target_preparation_commit_hook: None,
2887 #[cfg(test)]
2888 before_target_planning_snapshot_hook: None,
2889 #[cfg(test)]
2890 inject_target_semantic_stale_once_for_test: false,
2891 #[cfg(test)]
2892 force_virtual_dep_changes_remaining_for_test: 0,
2893 freshness: Default::default(),
2894 #[cfg(test)]
2895 fail_evaluation_commit_preflight_once_for_test: false,
2896 #[cfg(test)]
2897 target_preparation_fault_for_test: None,
2898 #[cfg(test)]
2899 force_non_cycle_schedule_fallback_for_test: false,
2900 #[cfg(test)]
2901 before_legacy_fallback_final_provider_sample_hook: None,
2902 #[cfg(test)]
2903 after_eager_proposal_commit_hook: None,
2904 };
2905 engine.config.arrow_storage_enabled = true;
2907 engine.config.delta_overlay_enabled = true;
2908 engine.config.write_formula_overlay_enabled = true;
2909 let default_sheet = engine.graph.default_sheet_name().to_string();
2910 engine.ensure_arrow_sheet(&default_sheet);
2911 engine
2912 }
2913
2914 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
2915 &self.workbook_load_limits
2916 }
2917
2918 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
2919 self.workbook_load_limits = limits;
2920 }
2921
2922 fn clear_source_cache(&self) {
2923 if let Ok(mut g) = self.source_cache.write() {
2924 *g = SourceCache::default();
2925 }
2926 }
2927
2928 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
2929 &self.last_virtual_dep_telemetry
2930 }
2931
2932 pub fn last_cycle_telemetry(&self) -> &CycleTelemetry {
2936 &self.last_cycle_telemetry
2937 }
2938
2939 pub fn last_evaluation_resource_request_stats(
2941 &self,
2942 ) -> Option<&EvaluationResourceRequestStats> {
2943 self.last_evaluation_resource_request.as_ref()
2944 }
2945
2946 pub fn evaluation_resource_baseline_stats(&self) -> EvaluationResourceBaselineStats {
2948 self.evaluation_resource_baseline
2949 }
2950
2951 pub fn evaluation_resource_budgets(&self) -> &crate::engine::EvaluationBudgets {
2952 &self.evaluation_resource_budgets
2953 }
2954
2955 pub fn evaluation_resource_config_diagnostic(
2957 &self,
2958 ) -> Option<&crate::engine::EvaluationResourceConfigDiagnostic> {
2959 self.evaluation_resource_config_diagnostic.as_ref()
2960 }
2961
2962 pub fn reset_evaluation_resource_telemetry(&mut self) {
2964 self.evaluation_resource_baseline = EvaluationResourceBaselineStats::default();
2965 self.last_evaluation_resource_request = None;
2966 }
2967
2968 fn reconcile_source_cache_footprints(&mut self) -> Result<(), ExcelError> {
2971 self.blocked_pending_spills.retain(|&(vertex, anchor, _)| {
2972 self.graph.vertex_exists(vertex)
2973 && self.graph.get_cell_ref(vertex) == Some(anchor)
2974 && matches!(
2975 self.graph.get_vertex_kind(vertex),
2976 VertexKind::FormulaScalar | VertexKind::FormulaArray
2977 )
2978 });
2979 if self.blocked_pending_spills.is_empty() {
2980 self.blocked_pending_spills = Vec::new();
2981 }
2982 let mut bytes = (self.blocked_pending_spills.capacity()
2983 * std::mem::size_of::<(VertexId, CellRef, Region)>()) as u64;
2984 self.source_cache_footprints.retain(|weak| {
2985 let Some(footprint) = weak.upgrade() else {
2986 return false;
2987 };
2988 bytes = bytes.saturating_add(footprint.load(std::sync::atomic::Ordering::Acquire));
2989 true
2990 });
2991 if let Some(ledger) = self.active_resource_ledger.as_mut() {
2992 ledger
2993 .release_retained(self.source_cache_accounted)
2994 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
2995 ledger.observe_retained(bytes);
2997 self.source_cache_accounted = bytes;
2998 ledger
2999 .reserve_retained(0)
3000 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3001 }
3002 Ok(())
3003 }
3004
3005 fn duration_ns(duration: std::time::Duration) -> u64 {
3006 u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
3007 }
3008
3009 fn observe_evaluation_resource_request<T>(
3010 &mut self,
3011 kind: EvaluationRequestKind,
3012 evaluate: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3013 ) -> Result<T, ExcelError> {
3014 let outermost = self.evaluation_resource_request_depth == 0;
3015 #[cfg(feature = "tracing")]
3016 if outermost {
3017 self.trace_evaluation_counters = TraceEvaluationCounters::default();
3018 }
3019 #[cfg(feature = "tracing")]
3020 let request_kind = if matches!(
3021 kind,
3022 EvaluationRequestKind::Full
3023 | EvaluationRequestKind::FullWithDelta
3024 | EvaluationRequestKind::FullCancellable
3025 | EvaluationRequestKind::FullLogged
3026 ) {
3027 "full"
3028 } else {
3029 "targeted"
3030 };
3031 #[cfg(feature = "tracing")]
3032 let _request_span = outermost.then(|| {
3033 crate::engine::trace::fz_span!(
3034 tracing::Level::INFO,
3035 "evaluate",
3036 "evaluate.request",
3037 kind = request_kind,
3038 mode = ?FormulaPlaneMode::Off
3039 )
3040 });
3041 if outermost {
3042 let request_id = self.next_evaluation_resource_request_id;
3043 self.next_evaluation_resource_request_id = request_id
3044 .checked_add(1)
3045 .expect("evaluation resource request ID exhausted");
3046 self.active_evaluation_resource_request = Some(EvaluationResourceRequestStats::new(
3047 request_id,
3048 kind,
3049 FormulaPlaneMode::Off,
3050 self.staged_formula_count(),
3051 ));
3052 self.evaluation_resource_baseline.record_started(request_id);
3053 self.evaluation_resource_request_started_at = Some(crate::instant::FzInstant::now());
3054 self.source_cache_accounted = 0;
3055 self.active_resource_ledger = Some(ResourceLedger::new(
3056 Some(request_id),
3057 self.evaluation_resource_budgets.clone(),
3058 ));
3059 }
3060 self.evaluation_resource_request_depth =
3061 self.evaluation_resource_request_depth.saturating_add(1);
3062 let result = if outermost {
3063 self.reconcile_source_cache_footprints()
3064 .and_then(|()| self.resource_checkpoint(0))
3065 .and_then(|()| evaluate(self))
3066 } else {
3067 evaluate(self)
3068 };
3069 if outermost && result.is_err() {
3070 self.freshness_abort_pass();
3071 }
3072 self.evaluation_resource_request_depth =
3073 self.evaluation_resource_request_depth.saturating_sub(1);
3074
3075 let reconciliation = if outermost {
3076 self.reconcile_source_cache_footprints()
3077 } else {
3078 Ok(())
3079 };
3080 let result = result.and_then(|value| reconciliation.map(|()| value));
3081 if outermost {
3082 let total_ns = self
3083 .evaluation_resource_request_started_at
3084 .take()
3085 .map(|start| Self::duration_ns(start.elapsed()))
3086 .unwrap_or(0);
3087 let mut stats = self
3088 .active_evaluation_resource_request
3089 .take()
3090 .expect("outer evaluation resource request has active stats");
3091 let mut ledger = self
3092 .active_resource_ledger
3093 .take()
3094 .expect("outer evaluation resource request has active ledger");
3095 ledger.release_all_scratch();
3096 stats.ledger.update(ledger.snapshot());
3097 stats.outcome = match &result {
3098 Ok(_) => EvaluationRequestOutcome::Success,
3099 Err(error) if error.kind == ExcelErrorKind::Cancelled => {
3100 EvaluationRequestOutcome::Cancelled
3101 }
3102 Err(_) => EvaluationRequestOutcome::Error,
3103 };
3104 if stats.dirty_lease == FormulaDirtyLeaseOutcome::Acquired {
3105 stats.dirty_lease = if stats.outcome == EvaluationRequestOutcome::Cancelled {
3106 FormulaDirtyLeaseOutcome::RetainedOnCancellation
3107 } else {
3108 FormulaDirtyLeaseOutcome::RetainedOnError
3109 };
3110 }
3111 stats.phases.total_ns = total_ns;
3112 let attributed = stats
3113 .phases
3114 .staged_prepare_ns
3115 .saturating_add(stats.phases.topology_ns)
3116 .saturating_add(stats.phases.materialization_ns);
3117 stats.phases.evaluation_ns = total_ns.saturating_sub(attributed);
3118 self.evaluation_resource_baseline.record_finished(&stats);
3119 self.last_evaluation_resource_request = Some(stats);
3120 crate::engine::trace::fz_event!(
3121 tracing::Level::INFO,
3122 "evaluate",
3123 "evaluate.summary",
3124 computed_vertices = self.trace_evaluation_counters.computed_vertices,
3125 cycles = self.trace_evaluation_counters.cycles,
3126 cancelled = matches!(
3127 &result,
3128 Err(error) if error.kind == ExcelErrorKind::Cancelled
3129 )
3130 );
3131 }
3132 result
3133 }
3134
3135 pub fn set_evaluation_resource_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
3136 self.evaluation_resource_budgets = budgets.clone();
3137 self.config.evaluation_budgets = budgets.clone();
3138 self.graph.set_evaluation_budgets(budgets);
3139 }
3140
3141 #[cfg(test)]
3142 pub(crate) fn set_evaluation_budgets_for_test(
3143 &mut self,
3144 budgets: crate::engine::EvaluationBudgets,
3145 ) {
3146 self.set_evaluation_resource_budgets(budgets);
3147 }
3148
3149 fn preflight_evaluation_commit_window(
3150 &mut self,
3151 bounded_writes: usize,
3152 ) -> Result<crate::instant::FzInstant, ExcelError> {
3153 #[cfg(test)]
3154 if std::mem::take(&mut self.fail_evaluation_commit_preflight_once_for_test) {
3155 return Err(crate::engine::ResourceLedgerError::Exhausted(
3156 formualizer_common::ResourceExhaustionDetail {
3157 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3158 limit: 0,
3159 observed: 1,
3160 request_id: self
3161 .active_evaluation_resource_request
3162 .as_ref()
3163 .map(|stats| stats.request_id),
3164 },
3165 )
3166 .into_excel_error());
3167 }
3168 let estimate = std::time::Duration::from_nanos(
3169 u64::try_from(bounded_writes)
3170 .unwrap_or(u64::MAX)
3171 .saturating_mul(100),
3172 );
3173 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3174 ledger
3175 .preflight_commit_window(estimate)
3176 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3177 }
3178 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3179 stats.evaluation_commit_preflight_count =
3180 stats.evaluation_commit_preflight_count.saturating_add(1);
3181 stats.evaluation_commit_estimated_ns = stats
3182 .evaluation_commit_estimated_ns
3183 .saturating_add(Self::duration_ns(estimate));
3184 }
3185 Ok(crate::instant::FzInstant::now())
3186 }
3187
3188 fn observe_evaluation_commit_window(&mut self, started: crate::instant::FzInstant) {
3189 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3190 stats.evaluation_commit_actual_ns = stats
3191 .evaluation_commit_actual_ns
3192 .saturating_add(Self::duration_ns(started.elapsed()));
3193 }
3194 }
3195
3196 fn cancellation_checkpoint(&self, message: &'static str) -> Result<(), ExcelError> {
3197 if self
3198 .active_cancel_flag
3199 .as_ref()
3200 .is_some_and(|cancel| cancel.is_cancelled())
3201 {
3202 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(message));
3203 }
3204 if self
3205 .active_evaluation_deadline
3206 .is_some_and(|deadline| Instant::now() >= deadline)
3207 {
3208 return Err(crate::engine::ResourceLedgerError::Exhausted(
3209 formualizer_common::ResourceExhaustionDetail {
3210 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3211 limit: 0,
3212 observed: 1,
3213 request_id: self
3214 .active_evaluation_resource_request
3215 .as_ref()
3216 .map(|request| request.request_id),
3217 },
3218 )
3219 .into_excel_error()
3220 .with_message(message));
3221 }
3222 Ok(())
3223 }
3224
3225 fn resource_checkpoint(&mut self, work_units: u64) -> Result<(), ExcelError> {
3226 let Some(ledger) = self.active_resource_ledger.as_mut() else {
3227 return Ok(());
3228 };
3229 ledger
3230 .charge_work(work_units)
3231 .and_then(|()| ledger.checkpoint_deadline())
3232 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3233 }
3234
3235 fn charge_bounded_work(&mut self, mut work_units: u64) -> Result<(), ExcelError> {
3236 if work_units == 0 {
3237 return self.resource_checkpoint(0);
3238 }
3239 while work_units > 0 {
3240 let chunk = work_units.min(256);
3241 self.resource_checkpoint(chunk)?;
3242 work_units -= chunk;
3243 }
3244 Ok(())
3245 }
3246
3247 fn reserve_request_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3248 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3249 ledger.observe_scratch(bytes);
3252 }
3253 Ok(())
3254 }
3255
3256 fn release_request_scratch(&mut self, bytes: u64) {
3257 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3258 let released = ledger.release_scratch(bytes);
3259 debug_assert!(
3260 released.is_ok(),
3261 "request scratch release exceeded the outstanding reservation"
3262 );
3263 }
3264 }
3265
3266 fn reserve_topology_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3267 self.active_resource_ledger
3268 .as_mut()
3269 .map_or(Ok(()), |ledger| ledger.reserve_schedule_discovery(bytes))
3270 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3271 }
3272
3273 fn reserve_graph_source_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3274 self.active_resource_ledger
3275 .as_mut()
3276 .map_or(Ok(()), |ledger| ledger.reserve_graph_source(bytes))
3277 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3278 }
3279
3280 fn with_request_scratch<T>(
3281 &mut self,
3282 bytes: u64,
3283 work: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3284 ) -> Result<T, ExcelError> {
3285 self.reserve_request_scratch(bytes)?;
3286 let result = work(self);
3287 self.release_request_scratch(bytes);
3288 result
3289 }
3290
3291 fn observe_staged_preparation(
3292 &mut self,
3293 selected: usize,
3294 retained: usize,
3295 elapsed: std::time::Duration,
3296 ) {
3297 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3298 stats.staged_selected = stats.staged_selected.saturating_add(selected as u64);
3299 stats.staged_retained = retained as u64;
3300 stats.phases.staged_prepare_ns = stats
3301 .phases
3302 .staged_prepare_ns
3303 .saturating_add(Self::duration_ns(elapsed));
3304 }
3305 }
3306
3307 fn graph_admission_enabled(&self) -> bool {
3308 crate::engine::resource_ledger::graph_admission_enabled(&self.evaluation_resource_budgets)
3309 }
3310
3311 fn preflight_graph_admission(
3312 &mut self,
3313 usage: crate::engine::resource_ledger::GraphAdmission,
3314 ) -> Result<(), ExcelError> {
3315 let request_id = self
3316 .active_evaluation_resource_request
3317 .as_ref()
3318 .map(|stats| stats.request_id);
3319 crate::engine::resource_ledger::preflight_graph_admission(
3320 &self.evaluation_resource_budgets,
3321 usage,
3322 request_id,
3323 )
3324 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3325 }
3326
3327 fn prepared_legacy_admission(
3328 &mut self,
3329 plan: &PreparedLegacyGraphPlan,
3330 materialization_cells: u64,
3331 ) -> Result<(), ExcelError> {
3332 if !self.graph_admission_enabled() {
3333 return Ok(());
3334 }
3335 let stats = self.graph.baseline_stats();
3336 let added_edges = plan.planned_edge_count().ok_or_else(|| {
3337 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3338 })?;
3339 let removed_edges = plan.removed_edge_count().ok_or_else(|| {
3340 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3341 })?;
3342 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
3343 final_vertices: stats
3344 .graph_vertex_count
3345 .checked_add(plan.new_vertex_count())
3346 .ok_or_else(|| {
3347 ExcelError::new(ExcelErrorKind::NImpl)
3348 .with_message("graph vertex count overflow")
3349 })?,
3350 final_edges: stats
3351 .graph_edge_count
3352 .checked_sub(removed_edges)
3353 .and_then(|count| count.checked_add(added_edges))
3354 .ok_or_else(|| {
3355 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3356 })?,
3357 materialization_cells,
3358 added_vertices: plan.new_vertex_count(),
3359 added_edges,
3360 })
3361 }
3362
3363 fn observe_target_admission_failure(
3364 &mut self,
3365 reason: formualizer_common::ResourceExhaustionReason,
3366 ) {
3367 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3368 stats.target_admission_failure = Some(reason);
3369 }
3370 }
3371
3372 fn observe_target_preparation_report(
3373 &mut self,
3374 report: &crate::engine::PreparedTargetGraphReport,
3375 ) {
3376 let reason_bit = |reason: crate::engine::OpaqueReason| -> u64 {
3377 let index = match reason {
3378 crate::engine::OpaqueReason::DynamicReference => 0,
3379 crate::engine::OpaqueReason::RuntimeTextReference => 1,
3380 crate::engine::OpaqueReason::UnknownFunction => 2,
3381 crate::engine::OpaqueReason::UnknownCustomFunction => 3,
3382 crate::engine::OpaqueReason::UnresolvedCrossSheetBinding => 4,
3383 crate::engine::OpaqueReason::UnresolvedName => 5,
3384 crate::engine::OpaqueReason::UnresolvedTable => 6,
3385 crate::engine::OpaqueReason::FormulaName => 7,
3386 crate::engine::OpaqueReason::DeferredSourcePackage => 8,
3387 crate::engine::OpaqueReason::UnsupportedSourceSemantics => 9,
3388 crate::engine::OpaqueReason::UncertainDefaultSheetBinding => 10,
3389 };
3390 1u64 << index
3391 };
3392 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3393 stats.staged_selected = report.selected_staged_cells as u64;
3394 stats.staged_retained = report.retained_staged_cells as u64;
3395 stats.target_requested = report.requested_targets as u64;
3396 stats.target_normalized_regions = report.normalized_regions as u64;
3397 stats.target_scope_level = match &report.widened_scope {
3398 crate::engine::PrepareScope::Exact => 0,
3399 crate::engine::PrepareScope::Sheets(_) => 1,
3400 crate::engine::PrepareScope::Workbook => 2,
3401 };
3402 stats.target_widening_reason_bits = report
3403 .widening_reasons
3404 .iter()
3405 .copied()
3406 .fold(0, |bits, reason| bits | reason_bit(reason));
3407 stats.graph_source_scratch_estimated = report.estimated_scratch_bytes;
3408 stats.graph_source_scratch_observed = report.observed_scratch_bytes;
3409 stats.target_commit_estimated_work = report.estimated_commit_work;
3410 stats.target_commit_actual_work = report.actual_commit_work;
3411 stats.target_commit_window_ns = Self::duration_ns(report.commit_window);
3412 stats.phases.staged_prepare_ns = stats
3413 .phases
3414 .staged_prepare_ns
3415 .saturating_add(Self::duration_ns(report.commit_window));
3416 }
3417 }
3418
3419 fn begin_evaluation_request(&mut self) {
3423 self.freshness_begin_request();
3424 #[cfg(test)]
3425 {
3426 self.evaluation_request_begin_count_for_test = self
3427 .evaluation_request_begin_count_for_test
3428 .saturating_add(1);
3429 }
3430 self.last_cycle_telemetry = CycleTelemetry::default();
3431 self.graph.authority_sync();
3432 self.pending_iterative_redirty.clear();
3435 self.reconcile_retained_sccs_at_request_begin();
3436 self.clock.refresh();
3440 }
3441
3442 fn redirty_for_next_recalc(&mut self) {
3451 self.graph.redirty_volatiles();
3452 let pending = std::mem::take(&mut self.pending_iterative_redirty);
3453 let dirty_at_begin = std::mem::take(&mut self.retained_scc_dirty_at_begin);
3454 for (vertex, scc) in dirty_at_begin {
3455 if self.retained_scc_members.get(&vertex) == Some(&scc) {
3456 self.retained_scc_members.remove(&vertex);
3462 if !self.graph.is_dirty(vertex) {
3463 self.iterative_state_values.remove(&vertex);
3464 }
3465 }
3466 }
3467 if !self.iterative_state_values.is_empty() || !self.retained_scc_members.is_empty() {
3468 let graph = &self.graph;
3469 self.iterative_state_values
3470 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3471 self.retained_scc_members
3472 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3473 }
3474 for &vertex in &pending {
3479 if !self.graph.is_live_formula_vertex(vertex) {
3480 continue;
3481 }
3482 if let Some(cell) = self.graph.get_cell_ref(vertex) {
3483 let sheet_name = self.graph.sheet_name(cell.sheet_id);
3484 match self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1) {
3485 Some(value) if !matches!(value, LiteralValue::Empty) => {
3486 self.iterative_state_values.insert(vertex, value);
3487 }
3488 _ => {
3489 self.iterative_state_values.remove(&vertex);
3490 }
3491 }
3492 }
3493 }
3494 if !pending.is_empty() {
3495 self.graph.redirty_iterative_members(&pending);
3496 }
3497 }
3498
3499 fn retained_scc_config_fingerprint(&self) -> u64 {
3504 use std::hash::{Hash, Hasher};
3505 let mut hasher = rustc_hash::FxHasher::default();
3506 let config = &self.config;
3507 std::mem::discriminant(&config.cycle.detection).hash(&mut hasher);
3508 match config.cycle.policy {
3509 CyclePolicy::Error => 0u8.hash(&mut hasher),
3510 CyclePolicy::Iterate {
3511 max_iterations,
3512 max_change,
3513 } => {
3514 1u8.hash(&mut hasher);
3515 max_iterations.hash(&mut hasher);
3516 max_change.to_bits().hash(&mut hasher);
3517 }
3518 }
3519 config.date_system.hash(&mut hasher);
3520 config.workbook_seed.hash(&mut hasher);
3521 std::mem::discriminant(&config.volatile_level).hash(&mut hasher);
3522 format!("{:?}", config.deterministic_mode).hash(&mut hasher);
3525 config.range_expansion_limit.hash(&mut hasher);
3526 config.max_open_ended_rows.hash(&mut hasher);
3527 config.max_open_ended_cols.hash(&mut hasher);
3528 hasher.finish()
3529 }
3530
3531 fn reconcile_retained_sccs_at_request_begin(&mut self) {
3537 self.retained_scc_dirty_at_begin.clear();
3538 if self.retained_scc_members.is_empty() {
3539 return;
3540 }
3541 let graph = &self.graph;
3544 self.retained_scc_members
3545 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3546 if self.retained_scc_config_fingerprint() != self.retained_scc_config_fingerprint {
3547 let members: Vec<VertexId> = self.retained_scc_members.keys().copied().collect();
3548 self.retained_scc_members.clear();
3549 self.graph.mark_dirty_many(&members);
3550 return;
3551 }
3552 let changes =
3553 crate::function_registry::semantic_changes_since(self.retained_scc_function_epoch_seen);
3554 let global_changed = changes.epoch != self.retained_scc_function_epoch_seen;
3555 let provider_revision = self.resolver.planning_semantic_revision();
3556 let provider_changed = provider_revision != self.retained_scc_provider_revision_seen;
3557 if global_changed || provider_changed {
3558 let changed: BTreeSet<(String, String)> = changes.keys.into_iter().collect();
3559 let affected: Vec<VertexId> = self
3560 .retained_scc_members
3561 .keys()
3562 .copied()
3563 .filter(|&vertex| {
3564 let Some(ast) = self.graph.get_formula(vertex) else {
3565 return true;
3566 };
3567 (global_changed
3568 && (!changes.complete || Self::ast_uses_changed_function(&ast, &changed)))
3569 || (provider_changed && Self::ast_contains_function(&ast))
3570 })
3571 .collect();
3572 for vertex in &affected {
3573 self.retained_scc_members.remove(vertex);
3574 }
3575 if !affected.is_empty() {
3576 self.graph.mark_dirty_many(&affected);
3577 }
3578 self.retained_scc_function_epoch_seen = changes.epoch;
3579 self.retained_scc_provider_revision_seen = provider_revision;
3580 }
3581 let mut dirty_sccs: FxHashSet<u64> = FxHashSet::default();
3582 let mut all_sccs: FxHashSet<u64> = FxHashSet::default();
3583 for (&vertex, &scc) in &self.retained_scc_members {
3584 all_sccs.insert(scc);
3585 if self.graph.is_dirty(vertex) {
3586 dirty_sccs.insert(scc);
3587 self.retained_scc_dirty_at_begin.push((vertex, scc));
3588 }
3589 }
3590 let reused_members = self
3591 .retained_scc_members
3592 .values()
3593 .filter(|scc| !dirty_sccs.contains(scc))
3594 .count();
3595 let t = &mut self.last_cycle_telemetry;
3596 t.reused_sccs = all_sccs.len() - dirty_sccs.len();
3597 t.reused_scc_members = reused_members;
3598 }
3599
3600 pub fn virtual_dep_fallback_activations(&self) -> u64 {
3601 self.virtual_dep_fallback_activations
3602 }
3603
3604 #[cfg(test)]
3605 pub(crate) fn lookup_index_flights_built_for_test(&self) -> usize {
3606 self.lookup_index_cache
3607 .flights_built
3608 .load(std::sync::atomic::Ordering::Relaxed)
3609 }
3610
3611 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
3612 self.lookup_index_cache.report()
3613 }
3614
3615 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
3616 let start_row = view.start_row();
3617 let end_row = view.end_row();
3618 let start_col = view.start_col();
3619 let end_col = view.end_col();
3620 for row in start_row..=end_row {
3621 let Ok(row_u32) = u32::try_from(row) else {
3622 return true;
3623 };
3624 for col in start_col..=end_col {
3625 let Ok(col_u32) = u32::try_from(col) else {
3626 return true;
3627 };
3628 let cell_ref = self
3629 .graph
3630 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
3631 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
3632 && self.graph.is_volatile(vertex_id)
3633 {
3634 return true;
3635 }
3636 }
3637 }
3638 false
3639 }
3640
3641 fn build_lookup_index_impl(
3642 &self,
3643 view: &RangeView<'_>,
3644 axis: LookupAxis,
3645 ) -> Option<Arc<LookupIndex>> {
3646 let (rows, cols) = view.dims();
3647 if rows == 0 || cols == 0 {
3648 self.lookup_index_cache.note_skipped_tiny();
3649 return None;
3650 }
3651 let len = match axis {
3652 LookupAxis::ColumnInView(col) => {
3653 if col >= cols {
3654 self.lookup_index_cache.note_skipped_tiny();
3655 return None;
3656 }
3657 rows
3658 }
3659 LookupAxis::RowInView(row) => {
3660 if row >= rows {
3661 self.lookup_index_cache.note_skipped_tiny();
3662 return None;
3663 }
3664 cols
3665 }
3666 };
3667 if len < 64 {
3668 self.lookup_index_cache.note_skipped_tiny();
3669 return None;
3670 }
3671
3672 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
3673 let key = LookupIndexKey {
3674 sheet_id,
3675 start_row: u32::try_from(view.start_row()).ok()?,
3676 start_col: u32::try_from(view.start_col()).ok()?,
3677 end_row: u32::try_from(view.end_row()).ok()?,
3678 end_col: u32::try_from(view.end_col()).ok()?,
3679 axis,
3680 snapshot_id: self.data_snapshot_id(),
3681 };
3682 if let Some(index) = self.lookup_index_cache.get(&key) {
3683 return Some(index);
3684 }
3685 if self
3686 .lookup_index_cache
3687 .would_exceed_cap(estimate_bytes(len, 0))
3688 {
3689 self.lookup_index_cache.note_skipped_cap();
3690 return None;
3691 }
3692 if !self.lookup_index_cache.should_build(key) {
3693 return None;
3694 }
3695 self.lookup_index_cache.single_flight(key, || {
3697 if let Some(index) = self.lookup_index_cache.recheck(&key) {
3698 return Some(index);
3699 }
3700 if self.lookup_index_cache.is_known_volatile(&key) {
3701 self.lookup_index_cache.note_skipped_volatile();
3702 return None;
3703 }
3704 if self.lookup_view_contains_volatile(view, sheet_id) {
3705 self.lookup_index_cache.note_volatile_key(key);
3706 self.lookup_index_cache.note_skipped_volatile();
3707 return None;
3708 }
3709 #[cfg(test)]
3710 self.lookup_index_cache
3711 .flights_built
3712 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3713 match LookupIndex::build(view, axis, self.config.date_system).ok()? {
3714 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
3715 BuildOutcome::ErrorInLookupAxis => {
3716 self.lookup_index_cache.note_skipped_error();
3717 None
3718 }
3719 BuildOutcome::Degenerate => {
3720 self.lookup_index_cache.note_skipped_tiny();
3721 None
3722 }
3723 }
3724 })
3725 }
3726
3727 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
3728 if !self.config.enable_virtual_dep_telemetry {
3729 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
3730 fallback_mode_activations: self.virtual_dep_fallback_activations,
3731 ..VirtualDepTelemetry::default()
3732 };
3733 }
3734 }
3735
3736 fn source_cache_session(&self) -> SourceCacheSession {
3737 self.clear_source_cache();
3738 SourceCacheSession {
3739 cache: self.source_cache.clone(),
3740 }
3741 }
3742
3743 fn resolve_source_scalar_cached(
3744 &self,
3745 name: &str,
3746 version: Option<u64>,
3747 ) -> Result<LiteralValue, ExcelError> {
3748 let key = (name.to_string(), version);
3749 if let Ok(mut g) = self.source_cache.write() {
3750 if let Some(v) = g.scalars.get(&key) {
3751 return Ok(v.clone());
3752 }
3753
3754 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
3755 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3756 ExcelError::new(ExcelErrorKind::Ref)
3757 .with_message(format!("Unresolved source scalar: {name}"))
3758 } else {
3759 err
3760 }
3761 })?;
3762 g.scalars.insert(key, v.clone());
3763 Ok(v)
3764 } else {
3765 self.resolver.resolve_source_scalar(name).map_err(|err| {
3766 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3767 ExcelError::new(ExcelErrorKind::Ref)
3768 .with_message(format!("Unresolved source scalar: {name}"))
3769 } else {
3770 err
3771 }
3772 })
3773 }
3774 }
3775
3776 fn resolve_source_table_cached(
3777 &self,
3778 name: &str,
3779 version: Option<u64>,
3780 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
3781 let key = (name.to_string(), version);
3782 if let Ok(mut g) = self.source_cache.write() {
3783 if let Some(t) = g.tables.get(&key) {
3784 return Ok(t.clone());
3785 }
3786
3787 let t = self.resolver.resolve_source_table(name).map_err(|err| {
3788 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3789 ExcelError::new(ExcelErrorKind::Ref)
3790 .with_message(format!("Unresolved source table: {name}"))
3791 } else {
3792 err
3793 }
3794 })?;
3795 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
3796 g.tables.insert(key, t.clone());
3797 Ok(t)
3798 } else {
3799 self.resolver
3800 .resolve_source_table(name)
3801 .map_err(|err| {
3802 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3803 ExcelError::new(ExcelErrorKind::Ref)
3804 .with_message(format!("Unresolved source table: {name}"))
3805 } else {
3806 err
3807 }
3808 })
3809 .map(Arc::from)
3810 }
3811 }
3812
3813 fn source_table_to_range_view(
3814 &self,
3815 table: &dyn crate::traits::Table,
3816 spec: &Option<formualizer_parse::parser::TableSpecifier>,
3817 ) -> Result<RangeView<'static>, ExcelError> {
3818 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
3819
3820 let owned = match spec {
3821 Some(TableSpecifier::Column(c)) => {
3822 let c = c.trim();
3823 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
3824 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3825 "Complex structured references not yet supported".to_string(),
3826 ));
3827 }
3828 table.get_column(c)?.materialise().into_owned()
3829 }
3830 Some(TableSpecifier::ColumnRange(start, end)) => {
3831 let cols = table.columns();
3832 let start = start.trim();
3833 let end = end.trim();
3834 let start_key = start.to_lowercase();
3835 let end_key = end.to_lowercase();
3836 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
3837 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
3838 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
3839 if si > ei {
3840 std::mem::swap(&mut si, &mut ei);
3841 }
3842 let h = table.data_height();
3843 let w = ei - si + 1;
3844 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
3845 for (offset, ci) in (si..=ei).enumerate() {
3846 let cname = &cols[ci];
3847 let col_range = table.get_column(cname)?;
3848 let (rh, _) = col_range.dimensions();
3849 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
3850 row[offset] = col_range.get(r, 0)?;
3851 }
3852 }
3853 rows
3854 } else {
3855 return Err(ExcelError::new(ExcelErrorKind::Ref)
3856 .with_message("Column range refers to unknown column(s)".to_string()));
3857 }
3858 }
3859 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
3860 | Some(TableSpecifier::Headers) => table
3861 .headers_row()
3862 .map(|r| r.materialise().into_owned())
3863 .unwrap_or_default(),
3864 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
3865 | Some(TableSpecifier::Totals) => table
3866 .totals_row()
3867 .map(|r| r.materialise().into_owned())
3868 .unwrap_or_default(),
3869 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
3870 table
3871 .data_body()
3872 .map(|r| r.materialise().into_owned())
3873 .unwrap_or_default()
3874 }
3875 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
3876 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
3877 if let Some(h) = table.headers_row() {
3878 out.extend(h.iter_rows());
3879 }
3880 if let Some(body) = table.data_body() {
3881 out.extend(body.iter_rows());
3882 }
3883 if let Some(tr) = table.totals_row() {
3884 out.extend(tr.iter_rows());
3885 }
3886 out
3887 }
3888 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
3889 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3890 "@ (This Row) requires table-aware context; not yet supported".to_string(),
3891 ));
3892 }
3893 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
3894 return Err(ExcelError::new(ExcelErrorKind::NImpl)
3895 .with_message("Complex structured references not yet supported".to_string()));
3896 }
3897 None => {
3898 return Err(ExcelError::new(ExcelErrorKind::NImpl)
3899 .with_message("Table reference without specifier is unsupported".to_string()));
3900 }
3901 };
3902
3903 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
3904 }
3905
3906 pub fn default_sheet_id(&self) -> SheetId {
3907 self.graph.default_sheet_id()
3908 }
3909
3910 pub fn default_sheet_name(&self) -> &str {
3911 self.graph.default_sheet_name()
3912 }
3913
3914 pub fn set_workbook_seed(&mut self, seed: u64) {
3916 self.config.workbook_seed = seed;
3917 }
3918
3919 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
3921 self.config.volatile_level = level;
3922 }
3923
3924 pub fn set_temporal_egress(&mut self, policy: crate::engine::TemporalEgress) {
3926 self.config.temporal_egress = policy;
3927 }
3928
3929 pub fn temporal_egress(&self) -> crate::engine::TemporalEgress {
3930 self.config.temporal_egress
3931 }
3932
3933 pub fn set_deterministic_mode(
3935 &mut self,
3936 mode: crate::engine::DeterministicMode,
3937 ) -> Result<(), ExcelError> {
3938 let clock = mode.build_clock()?;
3939 self.config.deterministic_mode = mode;
3940 self.clock = crate::timezone::SnapshotClock::new(clock);
3941 Ok(())
3942 }
3943
3944 pub fn set_clock(&mut self, clock: Arc<dyn crate::timezone::ClockProvider>) {
3952 self.clock = crate::timezone::SnapshotClock::new(clock);
3953 }
3954
3955 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
3956 self.config.deterministic_mode.validate()
3957 }
3958
3959 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
3960 self.graph.sheet_id(name)
3961 }
3962
3963 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
3964 self.add_sheet(name)
3965 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
3966 }
3967
3968 pub fn sheet_name(&self, id: SheetId) -> &str {
3969 self.graph.sheet_name(id)
3970 }
3971
3972 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
3973 let id = self.graph.add_sheet(name)?;
3974 self.ensure_arrow_sheet(name);
3975 self.mark_topology_edited();
3976 Ok(id)
3977 }
3978
3979 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
3980 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
3981 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
3982 })?;
3983 if new_name.is_empty() || new_name.len() > 255 {
3984 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
3985 }
3986 if self.graph.sheet_id(new_name).is_some() {
3987 return Err(ExcelError::new(ExcelErrorKind::Value)
3988 .with_message(format!("Sheet '{new_name}' already exists")));
3989 }
3990 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
3991
3992 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
3993 let mut copied_sheet = source_sheet;
3994 copied_sheet.name = Arc::<str>::from(new_name);
3995 self.arrow_sheets.sheets.push(copied_sheet);
3996 } else {
3997 self.ensure_arrow_sheet(new_name);
3998 }
3999
4000 let duplicated_formulas = self
4001 .graph
4002 .formula_vertices()
4003 .into_iter()
4004 .filter(|vertex| {
4005 self.graph
4006 .get_cell_ref(*vertex)
4007 .is_some_and(|cell| cell.sheet_id == new_id)
4008 })
4009 .collect::<Vec<_>>();
4010 self.graph.mark_vertices_dirty_batch(&duplicated_formulas);
4011 self.mark_topology_edited();
4012 Ok(new_id)
4013 }
4014
4015 fn ensure_arrow_sheet(&mut self, name: &str) {
4016 if self.arrow_sheets.sheet(name).is_some() {
4017 return;
4018 }
4019 self.arrow_sheets
4020 .sheets
4021 .push(crate::arrow_store::ArrowSheet {
4022 name: std::sync::Arc::<str>::from(name),
4023 date_system: self.config.date_system,
4024 columns: Vec::new(),
4025 nrows: 0,
4026 chunk_starts: Vec::new(),
4027 chunk_rows: 32 * 1024,
4028 });
4029 }
4030
4031 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
4032 let name = self.graph.sheet_name(sheet_id).to_string();
4033 self.purge_derived_formats_for_sheet(sheet_id);
4034 self.graph.remove_sheet(sheet_id)?;
4035 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
4036 self.clear_all_computed_overlays();
4040 self.mark_all_formula_vertices_dirty();
4041 self.clear_staged_formulas_for_sheet(&name);
4042 if self.row_visibility.remove(&sheet_id).is_some() {
4043 self.invalidate_row_visibility_mask_cache();
4044 }
4045 self.record_structural_change(StructuralScope::RemovedSheet(sheet_id));
4046 self.mark_topology_edited();
4047 Ok(())
4048 }
4049
4050 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
4052 if let Some(asheet) = self
4053 .arrow_sheets
4054 .sheets
4055 .iter_mut()
4056 .find(|s| s.name.as_ref() == target_name)
4057 {
4058 asheet.name = std::sync::Arc::<str>::from(new_name);
4059 return true;
4060 }
4061 false
4062 }
4063
4064 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
4065 let old_name = self.graph.sheet_name(sheet_id).to_string();
4066
4067 self.rename_sheet_in_arrow_store(&old_name, new_name);
4070
4071 match self.graph.rename_sheet(sheet_id, new_name) {
4073 Ok(_) => {
4074 self.rename_staged_formula_sheet(&old_name, new_name);
4075 let sheet_vertices: Vec<VertexId> = self
4077 .graph
4078 .grid_vertices_in_sheet(sheet_id)
4079 .map(|(id, _)| id)
4080 .collect();
4081 for v_id in sheet_vertices {
4082 self.graph.mark_vertex_dirty(v_id);
4083 }
4084 self.mark_topology_edited();
4086 Ok(())
4087 }
4088 Err(e) => {
4089 self.rename_sheet_in_arrow_store(new_name, &old_name);
4091 Err(e)
4092 }
4093 }
4094 }
4095
4096 pub fn named_ranges_iter(
4097 &self,
4098 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
4099 self.graph.named_ranges_iter()
4100 }
4101
4102 pub fn sheet_named_ranges_iter(
4103 &self,
4104 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
4105 self.graph.sheet_named_ranges_iter()
4106 }
4107
4108 pub fn resolve_name_entry(
4109 &self,
4110 name: &str,
4111 current_sheet: SheetId,
4112 ) -> Option<&crate::engine::named_range::NamedRange> {
4113 self.graph.resolve_name_entry(name, current_sheet)
4114 }
4115
4116 pub(crate) fn name_query_scope(
4126 &self,
4127 scope_sheet: Option<&str>,
4128 ) -> Result<NameScope, ExcelError> {
4129 match scope_sheet {
4130 None => Ok(NameScope::Workbook),
4131 Some(sheet) => self
4132 .graph
4133 .sheet_id(sheet)
4134 .map(NameScope::Sheet)
4135 .ok_or_else(|| {
4136 ExcelError::new(ExcelErrorKind::Ref)
4137 .with_message(format!("name scope sheet not found: {sheet}"))
4138 }),
4139 }
4140 }
4141
4142 fn resolve_sheet_locator(
4155 &self,
4156 locator: &crate::reference::SharedSheetLocator<'_>,
4157 context_sheet: SheetId,
4158 ) -> Result<SheetId, ExcelError> {
4159 self.graph
4160 .sheet_reg()
4161 .resolve_locator(locator, context_sheet)
4162 }
4163
4164 pub fn has_name(&self, name: &str, scope_sheet: Option<&str>) -> bool {
4165 let Ok(scope) = self.name_query_scope(scope_sheet) else {
4166 return false;
4167 };
4168 self.graph
4169 .resolve_name_entry_in_scope(name, scope)
4170 .is_some()
4171 }
4172
4173 pub fn resolved_name_value(
4177 &self,
4178 name: &str,
4179 scope_sheet: Option<&str>,
4180 ) -> Option<LiteralValue> {
4181 let scope = self.name_query_scope(scope_sheet).ok()?;
4182 let entry = self.graph.resolve_name_entry_in_scope(name, scope)?;
4183 self.graph.get_value(entry.vertex)
4184 }
4185
4186 pub fn table_metadata(&self, name: &str) -> Option<TableMetadata> {
4187 let entry = self.graph.resolve_table_entry(name)?;
4188 Some(TableMetadata {
4189 name: entry.name.clone(),
4190 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
4191 start_row: entry.range.start.coord.row() + 1,
4192 start_col: entry.range.start.coord.col() + 1,
4193 end_row: entry.range.end.coord.row() + 1,
4194 end_col: entry.range.end.coord.col() + 1,
4195 header_row: entry.header_row,
4196 headers: entry.headers.clone(),
4197 totals_row: entry.totals_row,
4198 })
4199 }
4200
4201 pub fn tables(&self) -> Vec<TableMetadata> {
4203 self.graph
4204 .table_names()
4205 .into_iter()
4206 .filter_map(|name| self.table_metadata(&name))
4207 .collect()
4208 }
4209
4210 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4211 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
4212
4213 for (name, named) in self.graph.named_ranges_iter() {
4214 out.push(crate::engine::named_range::NamedRangeSnapshot {
4215 name: name.clone(),
4216 scope: NameScope::Workbook,
4217 definition: named.definition.clone(),
4218 });
4219 }
4220
4221 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
4222 out.push(crate::engine::named_range::NamedRangeSnapshot {
4223 name: name.clone(),
4224 scope: NameScope::Sheet(*sheet_id),
4225 definition: named.definition.clone(),
4226 });
4227 }
4228
4229 out.sort_by(|a, b| {
4230 let a_scope = match a.scope {
4231 NameScope::Workbook => (0u8, 0u32),
4232 NameScope::Sheet(id) => (1u8, u32::from(id)),
4233 };
4234 let b_scope = match b.scope {
4235 NameScope::Workbook => (0u8, 0u32),
4236 NameScope::Sheet(id) => (1u8, u32::from(id)),
4237 };
4238 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
4239 });
4240
4241 out
4242 }
4243
4244 pub fn named_ranges_snapshot_for_sheet(
4245 &self,
4246 sheet_id: SheetId,
4247 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4248 self.named_ranges_snapshot()
4249 .into_iter()
4250 .filter(|entry| match entry.scope {
4251 NameScope::Workbook => true,
4252 NameScope::Sheet(id) => id == sheet_id,
4253 })
4254 .collect()
4255 }
4256
4257 pub fn define_name(
4258 &mut self,
4259 name: &str,
4260 definition: NamedDefinition,
4261 scope: NameScope,
4262 ) -> Result<(), ExcelError> {
4263 self.graph.validate_define_name(name, scope)?;
4264 self.graph.define_name(name, definition, scope)?;
4265 self.record_structural_change(StructuralScope::AllSheets);
4266
4267 self.mark_topology_edited();
4268
4269 Ok(())
4270 }
4271
4272 pub fn update_name(
4273 &mut self,
4274 name: &str,
4275 definition: NamedDefinition,
4276 scope: NameScope,
4277 ) -> Result<(), ExcelError> {
4278 self.graph.validate_existing_name(name, scope)?;
4279 self.graph.update_name(name, definition, scope)?;
4280 self.record_structural_change(StructuralScope::AllSheets);
4281
4282 self.mark_topology_edited();
4283
4284 Ok(())
4285 }
4286
4287 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
4288 self.graph.validate_existing_name(name, scope)?;
4289 self.graph.delete_name(name, scope)?;
4290 self.record_structural_change(StructuralScope::AllSheets);
4291
4292 self.mark_topology_edited();
4293
4294 Ok(())
4295 }
4296
4297 pub fn define_table(
4298 &mut self,
4299 name: &str,
4300 range: crate::reference::RangeRef,
4301 header_row: bool,
4302 headers: Vec<String>,
4303 totals_row: bool,
4304 ) -> Result<(), ExcelError> {
4305 self.graph
4306 .define_table(name, range, header_row, headers, totals_row)?;
4307 self.record_structural_change(StructuralScope::AllSheets);
4308 self.mark_topology_edited();
4309 Ok(())
4310 }
4311
4312 pub fn define_source_scalar(
4313 &mut self,
4314 name: &str,
4315 version: Option<u64>,
4316 ) -> Result<(), ExcelError> {
4317 self.graph.define_source_scalar(name, version)?;
4318 self.record_structural_change(StructuralScope::OpaqueGlobal);
4319 self.mark_topology_edited();
4320 Ok(())
4321 }
4322
4323 pub fn define_source_table(
4324 &mut self,
4325 name: &str,
4326 version: Option<u64>,
4327 ) -> Result<(), ExcelError> {
4328 self.graph.define_source_table(name, version)?;
4329 self.record_structural_change(StructuralScope::OpaqueGlobal);
4330 self.mark_topology_edited();
4331 Ok(())
4332 }
4333
4334 pub fn set_source_scalar_version(
4335 &mut self,
4336 name: &str,
4337 version: Option<u64>,
4338 ) -> Result<(), ExcelError> {
4339 self.graph.set_source_scalar_version(name, version)?;
4340 Ok(())
4341 }
4342
4343 pub fn set_source_table_version(
4344 &mut self,
4345 name: &str,
4346 version: Option<u64>,
4347 ) -> Result<(), ExcelError> {
4348 self.graph.set_source_table_version(name, version)?;
4349 Ok(())
4350 }
4351
4352 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
4353 self.graph.invalidate_source(name)?;
4354 Ok(())
4355 }
4356
4357 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
4358 self.graph.get_value(vertex)
4359 }
4360
4361 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
4362 self.graph.get_cell_value(sheet, row, col)
4363 }
4364
4365 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
4366 self.graph.get_vertex_for_cell(cell)
4367 }
4368
4369 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
4370 self.graph.get_evaluation_vertices()
4371 }
4372
4373 pub fn baseline_stats(&self) -> EngineBaselineStats {
4375 let graph = self.graph.baseline_stats();
4376 EngineBaselineStats {
4377 graph_vertex_count: graph.graph_vertex_count,
4378 graph_formula_vertex_count: graph.graph_formula_vertex_count,
4379 graph_edge_count: graph.graph_edge_count,
4380 dirty_vertex_count: graph.dirty_vertex_count,
4381 evaluation_vertex_count: graph.evaluation_vertex_count,
4382 formula_ast_root_count: graph.formula_ast_root_count,
4383 formula_ast_node_count: graph.formula_ast_node_count,
4384 staged_formula_count: self.staged_formula_count(),
4385 formula_plane_active_span_count: 0,
4386 formula_plane_producer_result_entries: 0,
4387 formula_plane_consumer_read_entries: 0,
4388 formula_plane_mixed_topology_cache_builds: 0,
4389 formula_plane_mixed_topology_cache_hits: 0,
4390 formula_plane_mixed_topology_cache_overflows: 0,
4391 formula_plane_dirty_pending_events: 0,
4392 formula_plane_dirty_region_events_recorded: 0,
4393 formula_plane_dirty_span_region_events_recorded: 0,
4394 formula_plane_dirty_whole_span_seeds_recorded: 0,
4395 formula_plane_dirty_global_invalidations: 0,
4396 formula_plane_structural_span_candidates: 0,
4397 formula_plane_cycle_member_span_demotions: 0,
4398 formula_plane_array_result_span_demotions: 0,
4399 retained_scc_members: self.retained_scc_members.len(),
4400 }
4401 }
4402
4403 pub(crate) fn inspection_mutation_revision(&self) -> u64 {
4405 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
4406 }
4407
4408 #[cfg(test)]
4409 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
4410 self.used_axis_bounds_cache
4411 .read()
4412 .ok()
4413 .and_then(|guard| {
4414 guard.as_ref().map(|cache| {
4415 (
4416 cache.row_hits.load(Ordering::Relaxed),
4417 cache.row_misses.load(Ordering::Relaxed),
4418 cache.col_hits.load(Ordering::Relaxed),
4419 cache.col_misses.load(Ordering::Relaxed),
4420 )
4421 })
4422 })
4423 .unwrap_or((0, 0, 0, 0))
4424 }
4425
4426 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
4427 self.graph.set_first_load_assume_new(enabled);
4428 }
4429
4430 pub fn first_load_assume_new(&self) -> bool {
4431 self.graph.first_load_assume_new()
4432 }
4433
4434 pub fn reset_ensure_touched(&mut self) {
4435 self.graph.reset_ensure_touched();
4436 }
4437
4438 pub fn finalize_sheet_index(&mut self, sheet: &str) {
4439 self.graph.finalize_sheet_index(sheet);
4440 }
4441
4442 pub fn action<T>(
4451 &mut self,
4452 name: impl AsRef<str>,
4453 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4454 ) -> Result<T, crate::engine::EditorError> {
4455 if self.action_depth != 0 {
4456 return Err(crate::engine::EditorError::TransactionFailed {
4457 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
4458 .to_string(),
4459 });
4460 }
4461
4462 self.action_depth = 1;
4463 let engine_ptr: *mut Engine<R> = self;
4464 let _guard = ActionDepthGuard {
4465 engine: engine_ptr,
4466 _marker: std::marker::PhantomData,
4467 };
4468
4469 let mut tx = EngineAction {
4470 engine: self,
4471 name: name.as_ref().to_string(),
4472 capture: None,
4473 arrow_undo: None,
4474 atomic_policy: false,
4475 };
4476 f(&mut tx)
4477 }
4478
4479 pub fn action_atomic<T>(
4483 &mut self,
4484 name: impl Into<String>,
4485 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4486 ) -> Result<T, crate::engine::EditorError> {
4487 let (v, _j) = self.action_atomic_journal(name, f)?;
4488 Ok(v)
4489 }
4490
4491 pub fn action_atomic_journal<T>(
4493 &mut self,
4494 name: impl Into<String>,
4495 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4496 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4497 if self.action_depth != 0 {
4498 return Err(crate::engine::EditorError::TransactionFailed {
4499 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4500 .to_string(),
4501 });
4502 }
4503
4504 self.action_depth = 1;
4505 let engine_ptr: *mut Engine<R> = self;
4506 let _guard = ActionDepthGuard {
4507 engine: engine_ptr,
4508 _marker: std::marker::PhantomData,
4509 };
4510
4511 let name_str = name.into();
4512 let mut capture = MutationCapture::new(Default::default());
4513 let start_len = capture.len();
4514 self.action_atomic_impl(&mut capture, start_len, true, name_str, f)
4515 }
4516
4517 fn action_atomic_impl<T>(
4518 &mut self,
4519 capture: &mut MutationCapture,
4520 start_len: usize,
4521 expand_runs: bool,
4522 name: String,
4523 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4524 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4525 let invalidation_baseline = self.invalidation_baseline();
4526 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
4527 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
4528
4529 let capture_ptr: *mut MutationCapture = capture;
4530 let mut tx = EngineAction {
4531 engine: self,
4532 name: name.clone(),
4533 capture: Some(capture_ptr),
4534 arrow_undo: Some(arrow_ptr),
4535 atomic_policy: true,
4536 };
4537
4538 let res = f(&mut tx);
4539
4540 let capture_ref = unsafe { &*capture_ptr };
4547 let has_runs = capture_ref.lazy_len() > 0;
4548 let graph_events: Vec<crate::engine::ChangeEvent> = if expand_runs || res.is_err() {
4549 capture_ref.expanded_events_from(start_len, 0)
4550 } else {
4551 capture_ref.events()[start_len..].to_vec()
4552 };
4553 let graph_batch = crate::engine::GraphUndoBatch {
4554 events: graph_events,
4555 };
4556 let affected_cells = arrow_undo.ops.len();
4557 let journal = crate::engine::ActionJournal {
4558 name,
4559 graph: graph_batch,
4560 arrow: arrow_undo,
4561 affected_cells,
4562 };
4563
4564 match res {
4565 Ok(v) => {
4566 if !journal.graph.is_empty() || !journal.arrow.is_empty() || has_runs {
4567 for event in &journal.graph.events {
4568 self.record_change_for_event(event);
4569 }
4570 let mut impact = Self::classify_change_events(
4571 &journal.graph.events,
4572 LoggedEditDirection::Original,
4573 )
4574 .max(Self::classify_arrow_undo(&journal.arrow));
4575 if has_runs {
4576 impact = impact.max(LoggedEditImpact::Topology);
4577 }
4578 self.apply_logged_edit_impact(impact, invalidation_baseline);
4579 }
4580 Ok((v, journal))
4581 }
4582 Err(e) => {
4583 if let Err(rb) = self.rollback_from_action_journal(&journal, invalidation_baseline)
4584 {
4585 return Err(crate::engine::EditorError::TransactionFailed {
4586 reason: format!(
4587 "Engine::action_atomic rollback failed after error '{e}': {rb}"
4588 ),
4589 });
4590 }
4591 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
4592 for event in &journal.graph.events {
4593 self.record_change_for_event(event);
4594 }
4595 }
4596 Err(e)
4597 }
4598 }
4599 }
4600
4601 pub fn action_with_logger<T>(
4608 &mut self,
4609 log: &mut crate::engine::ChangeLog,
4610 name: impl AsRef<str>,
4611 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4612 ) -> Result<T, crate::engine::EditorError> {
4613 if self.action_depth != 0 {
4614 return Err(crate::engine::EditorError::TransactionFailed {
4615 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4616 .to_string(),
4617 });
4618 }
4619
4620 self.action_depth = 1;
4621 let engine_ptr: *mut Engine<R> = self;
4622 let _guard = ActionDepthGuard {
4623 engine: engine_ptr,
4624 _marker: std::marker::PhantomData,
4625 };
4626
4627 let name_str = name.as_ref().to_string();
4628 let mut capture = MutationCapture::new(log.current_meta());
4629 let start_len = capture.len();
4630 capture.begin_compound(name_str.clone());
4631
4632 let res = self.action_atomic_impl(&mut capture, start_len, false, name_str, f);
4635 capture.close_compounds();
4636
4637 match res {
4638 Ok((v, _journal)) => {
4639 log.publish_capture(capture);
4640 Ok(v)
4641 }
4642 Err(e) => {
4643 log.discard_capture(capture);
4645 Err(e)
4646 }
4647 }
4648 }
4649
4650 fn rollback_from_action_journal(
4651 &mut self,
4652 journal: &crate::engine::ActionJournal,
4653 invalidation_baseline: InvalidationBaseline,
4654 ) -> Result<(), crate::engine::EditorError> {
4655 self.invalidate_for_action_journal(
4658 journal,
4659 LoggedEditDirection::InverseReplay,
4660 invalidation_baseline,
4661 );
4662 journal.graph.undo(&mut self.graph)?;
4664 self.apply_inverse_row_visibility_events(&journal.graph.events);
4666 self.apply_arrow_undo_batch(&journal.arrow, true);
4668 Ok(())
4669 }
4670
4671 fn rollback_from_change_events(
4672 &mut self,
4673 events: &[crate::engine::ChangeEvent],
4674 invalidation_baseline: InvalidationBaseline,
4675 ) -> Result<(), crate::engine::EditorError> {
4676 use crate::engine::ChangeEvent;
4677
4678 self.invalidate_for_change_events(
4681 events,
4682 LoggedEditDirection::InverseReplay,
4683 invalidation_baseline,
4684 );
4685
4686 self.graph
4688 .authority_set_replay(crate::engine::authority::history::Replay::Undo);
4689 let rolled_back = (|| {
4690 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
4691 let mut compound_stack: Vec<usize> = Vec::new();
4692 for (i, ev) in events.iter().enumerate().rev() {
4693 match ev {
4694 ChangeEvent::CompoundEnd { depth } => {
4695 compound_stack.push(*depth);
4696 if let Some(description) =
4697 crate::engine::graph::editor::change_log::compound_start_description(
4698 i,
4699 |j| &events[j],
4700 )
4701 {
4702 editor.inverse_compound_end(description);
4703 }
4704 }
4705 ChangeEvent::CompoundStart { depth, .. } => {
4706 if compound_stack.last() == Some(depth) {
4707 compound_stack.pop();
4708 }
4709 editor.apply_inverse(ev.clone())?;
4710 }
4711 ChangeEvent::SetRowVisibility { .. } => {
4712 }
4714 _ => {
4715 editor.apply_inverse(ev.clone())?;
4716 }
4717 }
4718 }
4719 Ok::<_, crate::engine::EditorError>(())
4720 })();
4721 self.graph
4722 .authority_set_replay(crate::engine::authority::history::Replay::Forward);
4723 rolled_back?;
4724
4725 for ev in events.iter().rev() {
4727 self.apply_inverse_row_visibility_event(ev);
4728 }
4729
4730 for ev in events.iter().rev() {
4732 self.mirror_inverse_change_to_arrow(ev);
4733 }
4734
4735 Ok(())
4736 }
4737
4738 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
4739 let sheet_id = self.graph.sheet_id(sheet)?;
4740 let coord = Coord::from_excel(row, col, true, true);
4741 let cell = CellRef::new(sheet_id, coord);
4742 let vid = self.graph.get_vertex_for_cell(&cell)?;
4743 self.graph.get_formula(vid)
4744 }
4745
4746 pub fn define_name_with_logger(
4747 &mut self,
4748 log: &mut crate::engine::ChangeLog,
4749 name: &str,
4750 definition: NamedDefinition,
4751 scope: NameScope,
4752 ) -> Result<(), crate::engine::EditorError> {
4753 self.graph
4754 .validate_define_name(name, scope)
4755 .map_err(crate::engine::EditorError::Excel)?;
4756
4757 {
4758 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4759 editor.define_name(name, definition, scope)?;
4760 }
4761 self.record_structural_change(StructuralScope::AllSheets);
4762
4763 self.mark_topology_edited();
4764
4765 Ok(())
4766 }
4767
4768 pub fn update_name_with_logger(
4769 &mut self,
4770 log: &mut crate::engine::ChangeLog,
4771 name: &str,
4772 definition: NamedDefinition,
4773 scope: NameScope,
4774 ) -> Result<(), crate::engine::EditorError> {
4775 self.graph
4776 .validate_existing_name(name, scope)
4777 .map_err(crate::engine::EditorError::Excel)?;
4778 {
4779 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4780 editor.update_name(name, definition, scope)?;
4781 }
4782 self.record_structural_change(StructuralScope::AllSheets);
4783
4784 self.mark_topology_edited();
4785
4786 Ok(())
4787 }
4788
4789 pub fn delete_name_with_logger(
4790 &mut self,
4791 log: &mut crate::engine::ChangeLog,
4792 name: &str,
4793 scope: NameScope,
4794 ) -> Result<(), crate::engine::EditorError> {
4795 self.graph
4796 .validate_existing_name(name, scope)
4797 .map_err(crate::engine::EditorError::Excel)?;
4798 {
4799 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4800 editor.delete_name(name, scope)?;
4801 }
4802 self.record_structural_change(StructuralScope::AllSheets);
4803
4804 self.mark_topology_edited();
4805
4806 Ok(())
4807 }
4808
4809 pub fn edit_with_logger<T>(
4810 &mut self,
4811 log: &mut crate::engine::ChangeLog,
4812 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
4813 ) -> Result<T, crate::engine::EditorError> {
4814 let mut capture = MutationCapture::new(log.current_meta());
4815 let result = self.edit_with_capture(&mut capture, f);
4816 capture.close_compounds();
4817 match result {
4818 Ok(value) => {
4819 log.publish_capture(capture);
4820 Ok(value)
4821 }
4822 Err(error) => {
4823 log.discard_capture(capture);
4824 Err(error)
4825 }
4826 }
4827 }
4828
4829 fn edit_with_capture<T>(
4830 &mut self,
4831 capture: &mut MutationCapture,
4832 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
4833 ) -> Result<T, crate::engine::EditorError> {
4834 let invalidation_baseline = self.invalidation_baseline();
4835 let start_len = capture.len();
4836 let lazy_start = capture.lazy_len();
4837
4838 struct ArrowSpillReader<'a> {
4841 sheets: &'a crate::arrow_store::SheetStore,
4842 }
4843 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
4844 fn read_cell_value(
4845 &self,
4846 sheet: &str,
4847 row: u32,
4848 col: u32,
4849 ) -> Option<formualizer_common::LiteralValue> {
4850 use formualizer_common::LiteralValue;
4851 let asheet = self.sheets.sheet(sheet)?;
4852 let r0 = row.saturating_sub(1) as usize;
4853 let c0 = col.saturating_sub(1) as usize;
4854 let v = asheet.get_cell_value(r0, c0);
4855 if matches!(v, LiteralValue::Empty) {
4856 None
4857 } else {
4858 Some(v)
4859 }
4860 }
4861 }
4862
4863 let ret = {
4864 let spill_reader = ArrowSpillReader {
4865 sheets: &self.arrow_sheets,
4866 };
4867 let mut editor = crate::engine::VertexEditor::with_capture_and_spill_reader(
4868 &mut self.graph,
4869 capture,
4870 &spill_reader,
4871 );
4872 f(&mut editor)
4873 };
4874
4875 let new_events = capture.events()[start_len..].to_vec();
4879 let new_runs = capture.lazy_len() > lazy_start;
4880 if new_events.iter().any(|event| {
4881 matches!(
4882 event,
4883 ChangeEvent::DefineName { .. }
4884 | ChangeEvent::UpdateName { .. }
4885 | ChangeEvent::DeleteName { .. }
4886 )
4887 }) {
4888 let all = capture.expanded_events_from(start_len, lazy_start);
4889 self.rollback_from_change_events(&all, invalidation_baseline)?;
4890 return Err(crate::engine::EditorError::TransactionUnsupported {
4891 reason: "name mutations must use Engine's prepared logged-name APIs".to_string(),
4892 });
4893 }
4894
4895 self.clear_logged_cell_format_states(&new_events);
4898 for ev in &new_events {
4899 self.mirror_forward_change_to_arrow(ev);
4900 }
4901 for ev in &new_events {
4902 self.record_change_for_event(ev);
4903 }
4904
4905 if self.action_depth == 0 {
4908 let mut impact =
4909 Self::classify_change_events(&new_events, LoggedEditDirection::Original);
4910 if new_runs {
4911 impact = impact.max(LoggedEditImpact::Topology);
4912 }
4913 self.apply_logged_edit_impact(impact, invalidation_baseline);
4914 }
4915
4916 Ok(ret)
4917 }
4918
4919 pub(crate) fn preflight_replay_admission(
4920 &mut self,
4921 events: &[ChangeEvent],
4922 forward: bool,
4923 ) -> Result<(), crate::engine::EditorError> {
4924 if !self.graph_admission_enabled() {
4925 return Ok(());
4926 }
4927 let mut vertex_delta = 0i128;
4928 let mut edge_delta = 0i128;
4929 let mut added_vertices = 0usize;
4930 let mut added_edges = 0usize;
4931 let mut formula_cells = BTreeSet::new();
4932 for event in events {
4933 match event {
4934 ChangeEvent::AddVertex {
4935 formula,
4936 coord,
4937 sheet_id,
4938 ..
4939 } => {
4940 let delta = if forward { 1 } else { -1 };
4941 vertex_delta += delta;
4942 if forward {
4943 added_vertices = added_vertices.saturating_add(1);
4944 if formula.is_some() {
4945 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
4946 }
4947 }
4948 }
4949 ChangeEvent::RemoveVertex {
4950 old_formula,
4951 coord,
4952 sheet_id,
4953 ..
4954 } => {
4955 let delta = if forward { -1 } else { 1 };
4956 vertex_delta += delta;
4957 if !forward {
4958 added_vertices = added_vertices.saturating_add(1);
4959 if old_formula.is_some()
4960 && let (Some(sheet_id), Some(coord)) = (sheet_id, coord)
4961 {
4962 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
4963 }
4964 }
4965 }
4966 ChangeEvent::EdgeAdded { .. } => {
4967 let delta = if forward { 1 } else { -1 };
4968 edge_delta += delta;
4969 if forward {
4970 added_edges = added_edges.saturating_add(1);
4971 }
4972 }
4973 ChangeEvent::EdgeRemoved { .. } => {
4974 let delta = if forward { -1 } else { 1 };
4975 edge_delta += delta;
4976 if !forward {
4977 added_edges = added_edges.saturating_add(1);
4978 }
4979 }
4980 ChangeEvent::SetFormula {
4981 addr, old_formula, ..
4982 } => {
4983 if forward || old_formula.is_some() {
4984 formula_cells.insert((addr.sheet_id, addr.coord.row(), addr.coord.col()));
4985 }
4986 }
4987 _ => {}
4988 }
4989 }
4990 let stats = self.graph.baseline_stats();
4991 let final_vertices = i128::try_from(stats.graph_vertex_count)
4992 .ok()
4993 .and_then(|count| count.checked_add(vertex_delta))
4994 .and_then(|count| usize::try_from(count).ok())
4995 .ok_or_else(|| {
4996 crate::engine::EditorError::Excel(
4997 ExcelError::new(ExcelErrorKind::NImpl)
4998 .with_message("replay vertex count overflow"),
4999 )
5000 })?;
5001 let final_edges = i128::try_from(stats.graph_edge_count)
5002 .ok()
5003 .and_then(|count| count.checked_add(edge_delta))
5004 .and_then(|count| usize::try_from(count).ok())
5005 .ok_or_else(|| {
5006 crate::engine::EditorError::Excel(
5007 ExcelError::new(ExcelErrorKind::NImpl)
5008 .with_message("replay edge count overflow"),
5009 )
5010 })?;
5011 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
5012 final_vertices,
5013 final_edges,
5014 materialization_cells: formula_cells.len() as u64,
5015 added_vertices,
5016 added_edges,
5017 })
5018 .map_err(crate::engine::EditorError::Excel)
5019 }
5020
5021 pub fn undo_logged(
5026 &mut self,
5027 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5028 log: &mut crate::engine::ChangeLog,
5029 ) -> Result<(), crate::engine::EditorError> {
5030 let pending_events = log
5031 .last_group_indices()
5032 .into_iter()
5033 .map(|index| log.events()[index].clone())
5034 .collect::<Vec<_>>();
5035 self.preflight_replay_admission(&pending_events, false)?;
5036 let invalidation_baseline = self.invalidation_baseline();
5037 self.invalidate_for_change_events(
5040 &pending_events,
5041 LoggedEditDirection::InverseReplay,
5042 invalidation_baseline,
5043 );
5044 let batch = undo.undo(&mut self.graph, log)?;
5045 for item in batch.iter().rev() {
5046 self.apply_inverse_row_visibility_event(&item.event);
5047 self.apply_inverse_staged_formula_event(&item.event);
5048 }
5049 if !batch.is_empty() {
5050 let events = batch
5051 .iter()
5052 .map(|item| item.event.clone())
5053 .collect::<Vec<_>>();
5054 self.clear_logged_cell_format_states(&events);
5055 }
5056 self.mirror_undo_batch_to_arrow(&batch);
5057 if !batch.is_empty() {
5058 for item in &batch {
5059 self.record_change_for_event(&item.event);
5060 }
5061 }
5062 crate::engine::trace::fz_event!(
5063 tracing::Level::INFO,
5064 "history",
5065 "history.replay",
5066 op = "undo",
5067 events_replayed = batch.len(),
5068 ownership_resyncs = batch.len()
5069 );
5070 Ok(())
5071 }
5072
5073 pub fn redo_logged(
5074 &mut self,
5075 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5076 log: &mut crate::engine::ChangeLog,
5077 ) -> Result<(), crate::engine::EditorError> {
5078 let pending_events = undo.pending_redo_events();
5079 self.preflight_replay_admission(&pending_events, true)?;
5080 let invalidation_baseline = self.invalidation_baseline();
5081 self.invalidate_for_change_events(
5082 &pending_events,
5083 LoggedEditDirection::ForwardReplay,
5084 invalidation_baseline,
5085 );
5086 let batch = undo.redo(&mut self.graph, log)?;
5087 for item in &batch {
5088 self.apply_forward_row_visibility_event(&item.event);
5089 self.apply_forward_staged_formula_event(&item.event);
5090 }
5091 if !batch.is_empty() {
5092 let events = batch
5093 .iter()
5094 .map(|item| item.event.clone())
5095 .collect::<Vec<_>>();
5096 self.clear_logged_cell_format_states(&events);
5097 }
5098 self.mirror_redo_batch_to_arrow(&batch);
5099 if !batch.is_empty() {
5100 for item in &batch {
5101 self.record_change_for_event(&item.event);
5102 }
5103 }
5104 crate::engine::trace::fz_event!(
5105 tracing::Level::INFO,
5106 "history",
5107 "history.replay",
5108 op = "redo",
5109 events_replayed = batch.len(),
5110 ownership_resyncs = batch.len()
5111 );
5112 Ok(())
5113 }
5114
5115 pub fn undo_action(
5119 &mut self,
5120 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5121 ) -> Result<(), crate::engine::EditorError> {
5122 let Some(journal) = undo.pop_undo_action() else {
5123 return Ok(());
5124 };
5125 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, false) {
5126 undo.push_done_action(journal);
5127 return Err(error);
5128 }
5129 let invalidation_baseline = self.invalidation_baseline();
5130
5131 self.invalidate_for_action_journal(
5132 &journal,
5133 LoggedEditDirection::InverseReplay,
5134 invalidation_baseline,
5135 );
5136 journal.graph.undo(&mut self.graph)?;
5137 self.apply_inverse_row_visibility_events(&journal.graph.events);
5138 self.apply_arrow_undo_batch(&journal.arrow, true);
5139 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5140 for event in &journal.graph.events {
5141 self.record_change_for_event(event);
5142 }
5143 }
5144
5145 #[cfg(feature = "tracing")]
5146 let events_replayed = journal.graph.events.len();
5147 crate::engine::trace::fz_event!(
5148 tracing::Level::INFO,
5149 "history",
5150 "history.replay",
5151 op = "undo",
5152 events_replayed,
5153 ownership_resyncs = events_replayed
5154 );
5155 undo.push_redo_action(journal);
5156 Ok(())
5157 }
5158
5159 pub fn redo_action(
5163 &mut self,
5164 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5165 ) -> Result<(), crate::engine::EditorError> {
5166 let Some(journal) = undo.pop_redo_action() else {
5167 return Ok(());
5168 };
5169 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, true) {
5170 undo.push_redo_action(journal);
5171 return Err(error);
5172 }
5173 let invalidation_baseline = self.invalidation_baseline();
5174 self.invalidate_for_action_journal(
5175 &journal,
5176 LoggedEditDirection::ForwardReplay,
5177 invalidation_baseline,
5178 );
5179 journal.graph.redo(&mut self.graph)?;
5180 self.apply_forward_row_visibility_events(&journal.graph.events);
5181 self.apply_arrow_undo_batch(&journal.arrow, false);
5182 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5183 for event in &journal.graph.events {
5184 self.record_change_for_event(event);
5185 }
5186 }
5187
5188 #[cfg(feature = "tracing")]
5189 let events_replayed = journal.graph.events.len();
5190 crate::engine::trace::fz_event!(
5191 tracing::Level::INFO,
5192 "history",
5193 "history.replay",
5194 op = "redo",
5195 events_replayed,
5196 ownership_resyncs = events_replayed
5197 );
5198 undo.push_done_action(journal);
5199 Ok(())
5200 }
5201
5202 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
5203 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
5204 let row = addr.coord.row() + 1;
5206 let col = addr.coord.col() + 1;
5207 (sheet, row, col)
5208 }
5209
5210 fn mirror_undo_batch_to_arrow(
5211 &mut self,
5212 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5213 ) {
5214 for item in batch.iter().rev() {
5216 self.mirror_inverse_change_to_arrow(&item.event);
5217 }
5218 }
5219
5220 fn mirror_redo_batch_to_arrow(
5221 &mut self,
5222 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5223 ) {
5224 for item in batch.iter() {
5226 self.mirror_forward_change_to_arrow(&item.event);
5227 }
5228 }
5229
5230 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5231 use crate::engine::ChangeEvent;
5232 use formualizer_common::LiteralValue;
5233
5234 match ev {
5235 ChangeEvent::SetValue {
5236 addr,
5237 old_value,
5238 old_formula,
5239 ..
5240 } => {
5241 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5242 if old_formula.is_some() {
5243 self.clear_delta_overlay_cell(&sheet, row, col);
5244 } else {
5245 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5246 self.mirror_value_to_overlay(&sheet, row, col, &v);
5247 }
5248 }
5249 ChangeEvent::SetFormula {
5250 addr,
5251 old_value,
5252 old_formula,
5253 ..
5254 } => {
5255 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5256 if old_formula.is_some() {
5257 self.clear_delta_overlay_cell(&sheet, row, col);
5258 } else {
5259 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5260 self.mirror_value_to_overlay(&sheet, row, col, &v);
5261 }
5262 }
5263 ChangeEvent::SpillCommitted { old, new, .. } => {
5264 self.mirror_spill_snapshot(new, true);
5266 if let Some(snap) = old {
5267 self.mirror_spill_snapshot(snap, false);
5268 }
5269 }
5270 ChangeEvent::SpillCleared { old, .. } => {
5271 self.mirror_spill_snapshot(old, false);
5273 }
5274 ChangeEvent::SetRowVisibility { .. } => {
5275 }
5277 _ => {}
5278 }
5279 }
5280
5281 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5282 use crate::engine::ChangeEvent;
5283
5284 match ev {
5285 ChangeEvent::SetValue { addr, new, .. } => {
5286 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5287 self.mirror_value_to_overlay(&sheet, row, col, new);
5288 }
5289 ChangeEvent::SetFormula { addr, .. } => {
5290 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5291 self.clear_delta_overlay_cell(&sheet, row, col);
5292 }
5294 ChangeEvent::SpillCommitted { old, new, .. } => {
5295 if let Some(snap) = old {
5296 self.mirror_spill_snapshot(snap, true);
5297 }
5298 self.mirror_spill_snapshot(new, false);
5299 }
5300 ChangeEvent::SpillCleared { old, .. } => {
5301 self.mirror_spill_snapshot(old, true);
5302 }
5303 ChangeEvent::SetRowVisibility { .. } => {
5304 }
5306 _ => {
5307 }
5309 }
5310 }
5311
5312 fn mirror_spill_snapshot(
5313 &mut self,
5314 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
5315 clear_only: bool,
5316 ) {
5317 use formualizer_common::LiteralValue;
5318
5319 let mut i = 0usize;
5320 for row in &snap.values {
5321 for v in row {
5322 if let Some(cell) = snap.target_cells.get(i) {
5323 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5324 let out = if clear_only {
5325 LiteralValue::Empty
5326 } else {
5327 v.clone()
5328 };
5329 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
5330 }
5331 i += 1;
5332 }
5333 }
5334 if clear_only {
5336 for cell in snap.target_cells.iter().skip(i) {
5337 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5338 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
5339 }
5340 }
5341 }
5342
5343 pub fn set_default_sheet_by_name(&mut self, name: &str) {
5344 self.graph.set_default_sheet_by_name(name);
5345 }
5346
5347 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
5348 self.graph.set_default_sheet_by_id(id);
5349 }
5350
5351 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
5352 self.graph.set_sheet_index_mode(mode);
5353 }
5354
5355 #[cfg(feature = "test-support")]
5356 #[doc(hidden)]
5357 pub fn mark_all_formulas_dirty_for_test(&mut self) {
5358 self.mark_all_formula_vertices_dirty();
5359 }
5360
5361 #[cfg(feature = "test-support")]
5362 #[doc(hidden)]
5363 pub fn take_criteria_mask_work_for_test() -> (usize, usize) {
5364 criteria_mask_test_hooks::take_mask_work()
5365 }
5366
5367 #[cfg(feature = "test-support")]
5368 #[doc(hidden)]
5369 pub fn lookup_index_cache_report_for_test(&self) -> LookupIndexCacheReport {
5370 self.lookup_index_cache.report()
5371 }
5372
5373 #[cfg(any(test, feature = "benchmark_internal"))]
5374 #[doc(hidden)]
5375 pub fn reset_recalc_reuse_probe(&mut self) {
5376 *self.recalc_reuse_probe.get_mut().unwrap() = RecalcReuseProbe::default();
5377 }
5378
5379 #[cfg(any(test, feature = "benchmark_internal"))]
5380 #[doc(hidden)]
5381 pub fn recalc_reuse_probe(&self) -> RecalcReuseProbe {
5382 let mut probe = self.recalc_reuse_probe.lock().unwrap().clone();
5383 if let Some(cached) = self.cached_static_schedule.as_ref() {
5384 let entry_bytes = |e: &CachedScheduleEntry| {
5385 std::mem::size_of::<CachedScheduleEntry>()
5386 + e.candidate_vertices.heap_bytes()
5387 + std::mem::size_of::<crate::engine::Schedule>()
5388 + 2 * std::mem::size_of::<usize>()
5389 + schedule_probe_retained_bytes(&e.schedule)
5390 };
5391 probe.schedule_retained_bytes = entry_bytes(cached)
5392 + self.recent_schedules.iter().map(entry_bytes).sum::<usize>()
5393 + self.base_schedule.as_ref().map_or(0, entry_bytes)
5394 + self.recent_schedules.capacity() * std::mem::size_of::<CachedScheduleEntry>();
5395 }
5396 probe
5397 }
5398
5399 #[cfg(test)]
5400 pub(crate) fn cached_static_schedule_for_test(&self) -> Option<Arc<crate::engine::Schedule>> {
5401 self.cached_static_schedule
5402 .as_ref()
5403 .map(|cached| Arc::clone(&cached.schedule))
5404 }
5405
5406 fn clear_cached_static_schedule(&mut self) {
5407 self.cached_static_schedule = None;
5408 self.recent_schedules.clear();
5409 self.base_schedule = None;
5410 }
5411
5412 fn retain_recent_schedule(&mut self, entry: CachedScheduleEntry) {
5415 const RECENT_SCHEDULES: usize = 8;
5416 const RECENT_SCHEDULE_VERTICES: usize = 65_536;
5417 let revision = self.schedule_cache_authority_revision();
5418 let epoch = self.topology_epoch;
5419 self.recent_schedules
5420 .retain(|e| e.topology_epoch == epoch && e.authority_revision == revision);
5421 let current =
5422 |e: &CachedScheduleEntry| e.topology_epoch == epoch && e.authority_revision == revision;
5423 if self.base_schedule.as_ref().is_some_and(|b| !current(b)) {
5424 self.base_schedule = None;
5425 }
5426 let mut entry = entry;
5429 if current(&entry)
5430 && entry.candidate_vertices.len() > BASE_SCHEDULE_MIN_VERTICES
5431 && self
5432 .base_schedule
5433 .as_ref()
5434 .is_none_or(|b| entry.candidate_vertices.len() > b.candidate_vertices.len())
5435 {
5436 match self.base_schedule.replace(entry) {
5437 Some(previous) => entry = previous,
5438 None => return,
5439 }
5440 }
5441 if entry.topology_epoch != epoch
5442 || entry.authority_revision != revision
5443 || entry.candidate_vertices.len() > RECENT_SCHEDULE_VERTICES / 4
5444 {
5445 return;
5446 }
5447 self.recent_schedules.push(entry);
5448 let mut total: usize = self
5449 .recent_schedules
5450 .iter()
5451 .map(|e| e.candidate_vertices.len())
5452 .sum();
5453 while self.recent_schedules.len() > RECENT_SCHEDULES || total > RECENT_SCHEDULE_VERTICES {
5454 let oldest = self.recent_schedules.remove(0);
5455 total -= oldest.candidate_vertices.len();
5456 }
5457 }
5458
5459 fn invalidation_baseline(&self) -> InvalidationBaseline {
5460 InvalidationBaseline {
5461 snapshot_id: self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed),
5462 topology_epoch: self.topology_epoch,
5463 }
5464 }
5465
5466 fn classify_change_events(
5467 events: &[crate::engine::ChangeEvent],
5468 direction: LoggedEditDirection,
5469 ) -> LoggedEditImpact {
5470 use crate::engine::ChangeEvent;
5471
5472 events
5473 .iter()
5474 .map(|event| match event {
5475 ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
5476 LoggedEditImpact::NoOp
5477 }
5478 ChangeEvent::SetRowVisibility { .. }
5479 | ChangeEvent::SetValue {
5480 old_formula: None,
5481 old_value: Some(_),
5482 ..
5483 } => LoggedEditImpact::DataOnly,
5484 ChangeEvent::SetValue {
5488 old_formula: None,
5489 old_value: None,
5490 ..
5491 } if direction == LoggedEditDirection::Original => LoggedEditImpact::DataOnly,
5492 ChangeEvent::SetValue { .. }
5493 | ChangeEvent::SetFormula { .. }
5494 | ChangeEvent::AddVertex { .. }
5495 | ChangeEvent::RemoveVertex { .. }
5496 | ChangeEvent::VertexMoved { .. }
5497 | ChangeEvent::FormulaAdjusted { .. }
5498 | ChangeEvent::NamedRangeAdjusted { .. }
5499 | ChangeEvent::EdgeAdded { .. }
5500 | ChangeEvent::EdgeRemoved { .. }
5501 | ChangeEvent::DefineName { .. }
5502 | ChangeEvent::UpdateName { .. }
5503 | ChangeEvent::DeleteName { .. }
5504 | ChangeEvent::SpillCommitted { .. }
5505 | ChangeEvent::SpillCleared { .. }
5506 | ChangeEvent::StagedFormulaCellChanged { .. } => LoggedEditImpact::Topology,
5507 })
5508 .max()
5509 .unwrap_or(LoggedEditImpact::NoOp)
5510 }
5511
5512 fn classify_arrow_undo(arrow: &crate::engine::ArrowUndoBatch) -> LoggedEditImpact {
5513 use crate::engine::ArrowOp;
5514
5515 arrow
5516 .ops
5517 .iter()
5518 .map(|op| match op {
5519 ArrowOp::SetDeltaCell { .. } | ArrowOp::SetComputedCell { .. } => {
5520 LoggedEditImpact::DataOnly
5521 }
5522 ArrowOp::RestoreComputedRect { .. }
5523 | ArrowOp::InsertRows { .. }
5524 | ArrowOp::InsertCols { .. } => LoggedEditImpact::Topology,
5525 })
5526 .max()
5527 .unwrap_or(LoggedEditImpact::NoOp)
5528 }
5529
5530 fn apply_logged_edit_impact(
5531 &mut self,
5532 impact: LoggedEditImpact,
5533 baseline: InvalidationBaseline,
5534 ) {
5535 match impact {
5536 LoggedEditImpact::NoOp => {}
5537 LoggedEditImpact::DataOnly => {
5538 if self.topology_epoch == baseline.topology_epoch
5539 && self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
5540 == baseline.snapshot_id
5541 {
5542 self.mark_data_edited();
5543 }
5544 }
5545 LoggedEditImpact::Topology => {
5546 if self.topology_epoch == baseline.topology_epoch {
5549 self.mark_topology_edited();
5550 }
5551 }
5552 }
5553 }
5554
5555 fn invalidate_for_change_events(
5556 &mut self,
5557 events: &[crate::engine::ChangeEvent],
5558 direction: LoggedEditDirection,
5559 baseline: InvalidationBaseline,
5560 ) {
5561 self.apply_logged_edit_impact(Self::classify_change_events(events, direction), baseline);
5562 }
5563
5564 fn invalidate_for_action_journal(
5565 &mut self,
5566 journal: &crate::engine::ActionJournal,
5567 direction: LoggedEditDirection,
5568 baseline: InvalidationBaseline,
5569 ) {
5570 let impact = Self::classify_change_events(&journal.graph.events, direction)
5571 .max(Self::classify_arrow_undo(&journal.arrow));
5572 self.apply_logged_edit_impact(impact, baseline);
5573 }
5574
5575 pub fn mark_data_edited(&mut self) {
5578 self.lookup_index_cache.clear();
5579 self.snapshot_id
5580 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5581 self.has_edited = true;
5582 }
5583
5584 pub fn mark_topology_edited(&mut self) {
5586 self.lookup_index_cache.clear();
5587 self.snapshot_id
5588 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5589 self.topology_epoch = self.topology_epoch.wrapping_add(1);
5590 self.graph.bump_topology_revision();
5591 self.clear_cached_static_schedule();
5592 if let Some(ledger) = self.active_resource_ledger.as_mut() {
5593 let released = ledger.account_mixed_cache(0);
5594 debug_assert!(released.is_ok());
5595 }
5596 self.has_edited = true;
5597 self.graph.authority_sync_eager();
5601 }
5602
5603 fn mark_all_formula_vertices_dirty(&mut self) {
5604 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
5605 for vertex in vertices {
5606 self.graph.mark_vertex_dirty(vertex);
5607 }
5608 }
5609
5610 fn mark_moved_formula_vertices_dirty(
5611 &mut self,
5612 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
5613 ) {
5614 for vertex in &summary.vertices_moved {
5615 if self.graph.has_formula(*vertex) {
5616 self.graph.mark_vertex_dirty(*vertex);
5617 }
5618 }
5619 }
5620
5621 pub fn sheet_store(&self) -> &SheetStore {
5623 &self.arrow_sheets
5624 }
5625
5626 pub(crate) fn has_row_visibility_state(&self) -> bool {
5629 !self.row_visibility.is_empty()
5630 }
5631
5632 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
5634 &mut self.arrow_sheets
5635 }
5636
5637 pub fn has_staged_formulas(&self) -> bool {
5638 !self.staged_formulas.is_empty()
5639 }
5640
5641 pub fn staged_formula_count(&self) -> usize {
5642 self.staged_formulas.values().map(StagedSheet::len).sum()
5643 }
5644
5645 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
5647 self.staged_formulas
5648 .entry(sheet.to_string())
5649 .or_default()
5650 .stage(row, col, text);
5651 self.staged_formula_index.stage(sheet, row, col);
5652 if let Some(sheet) = self.graph.sheet_id(sheet) {
5653 self.invalidate_pending_spills(StructuralScope::Cell {
5654 sheet,
5655 row: row.saturating_sub(1),
5656 col: col.saturating_sub(1),
5657 });
5658 }
5659 }
5660
5661 fn index_removed_staged_sheet(&mut self, sheet: &str, staged: &StagedSheet) {
5662 for (row, col, _) in &staged.entries {
5663 self.staged_formula_index.remove(sheet, *row, *col);
5664 }
5665 if staged.deferred_package.is_some() {
5666 self.staged_formula_index.set_package(sheet, None);
5667 }
5668 }
5669
5670 fn restore_staged_sheet(&mut self, sheet: String, staged: StagedSheet) {
5671 self.staged_formulas.insert(sheet, staged);
5672 }
5673
5674 #[doc(hidden)]
5675 pub fn source_formula_ingress(&mut self) -> SourceFormulaIngress<'_, R> {
5676 SourceFormulaIngress { engine: self }
5677 }
5678
5679 #[doc(hidden)]
5680 #[doc(hidden)]
5683 #[cfg(any(test, feature = "test-support"))]
5684 pub fn set_before_prepared_span_commit_hook(
5685 &mut self,
5686 hook: impl FnOnce() + Send + Sync + 'static,
5687 ) {
5688 self.before_prepared_span_commit_hook = Some(Box::new(hook));
5689 }
5690
5691 #[doc(hidden)]
5692 #[cfg(test)]
5694 pub(crate) fn set_before_target_preparation_commit_hook(
5695 &mut self,
5696 hook: impl FnOnce() + Send + Sync + 'static,
5697 ) {
5698 self.before_target_preparation_commit_hook = Some(Box::new(hook));
5699 }
5700
5701 fn stage_deferred_formula_package(&mut self, package: crate::engine::DeferredFormulaPackage) {
5702 if let Ok(replay) = package.replay.lock()
5703 && let Some(footprint) = replay.selection_cache_footprint()
5704 && !self
5705 .source_cache_footprints
5706 .iter()
5707 .any(|known| known.ptr_eq(&footprint))
5708 {
5709 self.source_cache_footprints.push(footprint);
5710 }
5711 let sheet = package.sheet_name.clone();
5712 let staged = self.staged_formulas.entry(sheet.clone()).or_default();
5713 debug_assert!(staged.deferred_package.is_none());
5714 staged.deferred_package = Some(package);
5715 staged.reconcile_attached_deferred_package();
5716 self.staged_formula_index
5717 .set_package(&sheet, staged.deferred_package.as_ref());
5718 if let Some(sheet_id) = self.graph.sheet_id(&sheet) {
5719 let package = self
5720 .staged_formulas
5721 .get(&sheet)
5722 .and_then(|staged| staged.deferred_package.as_ref());
5723 for &(vertex, anchor, region) in &self.blocked_pending_spills {
5724 if anchor.sheet_id == sheet_id
5725 && self.graph.vertex_exists(vertex)
5726 && self.graph.get_cell_ref(vertex) == Some(anchor)
5727 && self.staged_formula_index.package_occupies_spill(
5728 &sheet,
5729 (anchor.coord.row() + 1, anchor.coord.col() + 1),
5730 (
5731 region.rows.query_bounds().1 + 1,
5732 region.cols.query_bounds().1 + 1,
5733 ),
5734 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
5735 )
5736 {
5737 self.graph.mark_vertex_dirty(vertex);
5738 }
5739 }
5740 }
5741 }
5742
5743 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
5744 let mut removed = None;
5745 let mut remove_sheet = false;
5746 let mut had_package = false;
5747 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
5748 had_package = entries.deferred_package.is_some();
5749 removed = entries.remove(row, col);
5750 remove_sheet = entries.is_empty();
5751 }
5752 let ordinary_removed = self.staged_formula_index.remove(sheet, row, col);
5753 if !ordinary_removed && had_package {
5754 self.staged_formula_index.touch_package(sheet);
5755 }
5756 if remove_sheet {
5757 self.staged_formulas.remove(sheet);
5758 self.staged_formula_index.set_package(sheet, None);
5759 }
5760 if (ordinary_removed || had_package)
5761 && let Some(sheet) = self.graph.sheet_id(sheet)
5762 {
5763 self.invalidate_pending_spills(StructuralScope::Cell {
5764 sheet,
5765 row: row.saturating_sub(1),
5766 col: col.saturating_sub(1),
5767 });
5768 }
5769 removed
5770 }
5771
5772 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
5773 if self.staged_formulas.remove(sheet).is_some() {
5774 self.staged_formula_index.clear_sheet(sheet);
5775 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
5776 self.invalidate_pending_spills(StructuralScope::Sheet(sheet_id));
5777 }
5778 }
5779 }
5780
5781 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
5782 let Some(entries) = self.staged_formulas.remove(old) else {
5783 return;
5784 };
5785 self.staged_formula_index.clear_sheet(old);
5786 let (formulas, mut package) = entries.into_parts();
5787 for (row, col, text) in formulas {
5788 self.stage_formula_text(new, row, col, text);
5789 }
5790 if let Some(package) = package.as_mut() {
5791 package.sheet_name = new.to_string();
5792 }
5793 if let Some(package) = package {
5794 self.stage_deferred_formula_package(package);
5795 }
5796 }
5797
5798 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
5800 self.staged_formulas
5801 .get(sheet)
5802 .and_then(|v| v.get(row, col))
5803 }
5804
5805 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
5806 &self.formula_parse_diagnostics
5807 }
5808
5809 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
5810 std::mem::take(&mut self.formula_parse_diagnostics)
5811 }
5812
5813 pub fn clear_formula_parse_diagnostics(&mut self) {
5814 self.formula_parse_diagnostics.clear();
5815 }
5816
5817 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
5818 self.last_formula_ingest_report.as_ref()
5819 }
5820
5821 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
5822 &self.formula_ingest_report_total
5823 }
5824
5825 #[cfg(test)]
5826 pub(crate) fn set_before_target_planning_snapshot_hook_for_test(
5827 &mut self,
5828 hook: impl FnOnce() + Send + Sync + 'static,
5829 ) {
5830 self.before_target_planning_snapshot_hook = Some(Box::new(hook));
5831 }
5832
5833 #[cfg(test)]
5834 pub(crate) fn inject_target_semantic_stale_once_for_test(&mut self) {
5835 self.inject_target_semantic_stale_once_for_test = true;
5836 }
5837
5838 #[cfg(test)]
5839 pub(crate) fn force_virtual_dep_changes_for_test(&mut self, rounds: usize) {
5840 self.force_virtual_dep_changes_remaining_for_test = rounds;
5841 }
5842
5843 #[cfg(test)]
5844 pub(crate) fn fail_evaluation_commit_preflight_once_for_test(&mut self) {
5845 self.fail_evaluation_commit_preflight_once_for_test = true;
5846 }
5847
5848 #[cfg(test)]
5849 pub(crate) fn set_target_preparation_fault_for_test(
5850 &mut self,
5851 fault: crate::engine::target_preparation::TargetPreparationFault,
5852 ) {
5853 self.target_preparation_fault_for_test = Some(fault);
5854 }
5855
5856 #[cfg(test)]
5857 pub(crate) fn staged_formula_index_revision_for_test(&self) -> u64 {
5858 self.staged_formula_index.revision()
5859 }
5860
5861 #[cfg(test)]
5862 pub(crate) fn deferred_package_for_test(
5863 &self,
5864 sheet: &str,
5865 ) -> &crate::engine::DeferredFormulaPackage {
5866 self.staged_formulas
5867 .get(sheet)
5868 .unwrap()
5869 .deferred_package
5870 .as_ref()
5871 .unwrap()
5872 }
5873
5874 #[cfg(test)]
5875 pub(crate) fn staged_formula_index_is_consistent_for_test(&self) -> bool {
5876 let ordinary_storage = self
5877 .staged_formulas
5878 .values()
5879 .map(|sheet| sheet.entries.len())
5880 .sum::<usize>();
5881 let package_storage = self
5882 .staged_formulas
5883 .values()
5884 .filter(|sheet| sheet.deferred_package.is_some())
5885 .count();
5886 ordinary_storage == self.staged_formula_index.ordinary_count()
5887 && package_storage == self.staged_formula_index.package_count()
5888 && self.staged_formulas.iter().all(|(name, sheet)| {
5889 sheet.entries.iter().all(|(row, col, _)| {
5890 let leases = self
5891 .staged_formula_index
5892 .leases_in_region(name, *row, *col, *row, *col);
5893 leases.len() == 1 && leases[0].row == *row && leases[0].col == *col
5894 })
5895 })
5896 }
5897
5898 #[cfg(test)]
5899 pub(crate) fn evaluation_request_begin_count_for_test(&self) -> u64 {
5900 self.evaluation_request_begin_count_for_test
5901 }
5902
5903 #[cfg(test)]
5904 pub(crate) fn set_before_legacy_fallback_final_provider_sample_hook(
5905 &mut self,
5906 hook: impl FnOnce() + Send + Sync + 'static,
5907 ) {
5908 self.before_legacy_fallback_final_provider_sample_hook = Some(Box::new(hook));
5909 }
5910
5911 #[cfg(test)]
5912 pub(crate) fn set_after_eager_proposal_commit_hook(
5913 &mut self,
5914 hook: impl FnOnce() + Send + Sync + 'static,
5915 ) {
5916 self.after_eager_proposal_commit_hook = Some(Box::new(hook));
5917 }
5918
5919 #[cfg(test)]
5920 pub(crate) fn topology_epoch_for_test(&self) -> u64 {
5921 self.topology_epoch
5922 }
5923
5924 #[cfg(test)]
5925 pub(crate) fn graph_topology_revision_for_test(&self) -> u64 {
5926 self.graph.topology_revision()
5927 }
5928
5929 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
5930 self.formula_ingest_report_total.mode = report.mode;
5931 self.formula_ingest_report_total.accumulate(&report);
5932 self.last_formula_ingest_report = Some(report);
5933 }
5934
5935 fn collect_planning_function_requests(
5936 ast: &ASTNode,
5937 requests: &mut Vec<(String, String, usize)>,
5938 ) {
5939 match &ast.node_type {
5940 ASTNodeType::Function { name, args } => {
5941 requests.push((String::new(), name.clone(), args.len()));
5942 for arg in args {
5943 Self::collect_planning_function_requests(arg, requests);
5944 }
5945 }
5946 ASTNodeType::BinaryOp { left, right, .. } => {
5947 Self::collect_planning_function_requests(left, requests);
5948 Self::collect_planning_function_requests(right, requests);
5949 }
5950 ASTNodeType::UnaryOp { expr, .. } => {
5951 Self::collect_planning_function_requests(expr, requests);
5952 }
5953 ASTNodeType::Call { callee, args } => {
5954 Self::collect_planning_function_requests(callee, requests);
5955 for arg in args {
5956 Self::collect_planning_function_requests(arg, requests);
5957 }
5958 }
5959 ASTNodeType::Array(rows) => {
5960 for cell in rows.iter().flatten() {
5961 Self::collect_planning_function_requests(cell, requests);
5962 }
5963 }
5964 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {}
5965 }
5966 }
5967
5968 fn prepared_function_semantics_changed(
5969 &self,
5970 preparation: &crate::engine::FormulaCompressedPreparation,
5971 guard: &crate::function_registry::SemanticEpochReadGuard,
5972 ) -> bool {
5973 if preparation.function_semantic_epoch == guard.epoch() {
5974 return false;
5975 }
5976
5977 guard
5978 .semantic_changes_affect_requests_since(preparation.function_semantic_epoch, Vec::new())
5979 }
5980
5981 pub(crate) fn prepare_source_formula_families(
5982 &mut self,
5983 sheet_name: &str,
5984 families: &[crate::engine::SourceFormulaFamily],
5985 ) -> crate::engine::FormulaCompressedPreparation {
5986 crate::engine::FormulaCompressedPreparation {
5987 engine_token: Arc::clone(&self.source_formula_token),
5988 function_semantic_epoch: crate::function_registry::semantic_epoch(),
5989 function_provider_revision: None,
5990 function_semantics_used: false,
5991 sheet_name: Arc::from(sheet_name),
5992 rejected: BTreeMap::new(),
5993 eager_replay: Vec::new(),
5994 preparation_spool_replays: 0,
5995 clean_rejected_anchor_counts: [0; 3],
5996 fragmented_rejected_anchor_counts: [0; 3],
5997 exact_replay: None,
5998 replay_disposition: crate::engine::FormulaReplayDisposition::default(),
5999 }
6000 }
6001
6002 fn prepare_source_formula_proposals(
6003 &mut self,
6004 sheet_name: &str,
6005 families: &[crate::engine::SourceFormulaFamily],
6006 authority_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6007 replay_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6008 formula_record_count: u64,
6009 replay: Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6010 suppressed: &BTreeSet<(u32, u32)>,
6011 consumed: &FxHashSet<(crate::engine::SourceFamilyId, crate::engine::SourceCoord)>,
6012 consumed_engine: Option<&Arc<()>>,
6013 ) -> Result<crate::engine::FormulaCompressedPreparation, ExcelError> {
6014 let mut preparation = self.prepare_source_formula_families(sheet_name, families);
6015 preparation.exact_replay = Some(Arc::clone(&replay));
6016 preparation
6017 .replay_disposition
6018 .register_consumed_members(consumed_engine, consumed.iter().copied());
6019 if !preparation
6020 .replay_disposition
6021 .consumed_engine_matches(&self.source_formula_token)
6022 {
6023 return Err(ExcelError::new(ExcelErrorKind::Value)
6024 .with_message("ResidualConsumedEngineMismatch"));
6025 }
6026 preparation
6027 .replay_disposition
6028 .extend_suppressed_excel_coords(suppressed.iter().copied());
6029 Ok(preparation)
6030 }
6031
6032 fn formula_batch_from_exact_replay(
6033 &mut self,
6034 sheet_name: &str,
6035 replayed: impl IntoIterator<Item = crate::engine::DeferredReplayFormula>,
6036 ) -> Result<FormulaIngestBatch, ExcelError> {
6037 let mut cache = rustc_hash::FxHashMap::default();
6038 let mut formulas = Vec::new();
6039 for record in replayed {
6040 let key = if record.text.starts_with('=') {
6041 record.text
6042 } else {
6043 format!("={}", record.text)
6044 };
6045 let ast_id = if let Some(cached) = cache.get(&key) {
6046 *cached
6047 } else {
6048 let parsed = match formualizer_parse::parser::parse(&key) {
6049 Ok(parsed) => parsed,
6050 Err(error) => {
6051 let Some(parsed) = self.handle_formula_parse_error(
6052 sheet_name,
6053 record.row,
6054 record.col,
6055 &key,
6056 error.to_string(),
6057 )?
6058 else {
6059 continue;
6060 };
6061 parsed
6062 }
6063 };
6064 let ast_id = self.intern_formula_ast(&parsed);
6065 cache.insert(key.clone(), ast_id);
6066 ast_id
6067 };
6068 formulas.push(
6069 FormulaIngestRecord::new(record.row, record.col, ast_id, Some(Arc::from(key)))
6070 .with_source_proof(record.source_order, record.family, record.partition_owner),
6071 );
6072 }
6073 Ok(FormulaIngestBatch::new(sheet_name.to_string(), formulas))
6074 }
6075
6076 fn prepare_target_combined_legacy_graph(
6077 &self,
6078 packages: &[PreparedTargetSourcePackage],
6079 ordinary: &[PreparedOrdinaryStagedFormula],
6080 ) -> Result<(PreparedLegacyGraphPlan, usize), ExcelError> {
6081 let mut planned_by_coord = BTreeMap::new();
6082 for package in packages {
6083 for (row, col, ast_id, plan) in &package.legacy {
6084 planned_by_coord.insert((package.sheet_id, *row, *col), (*ast_id, plan.clone()));
6085 }
6086 }
6087 for formula in ordinary {
6088 if let Some((ast_id, plan)) = formula.ast_id.zip(formula.plan.clone()) {
6089 planned_by_coord.insert(
6090 (formula.sheet_id, formula.lease.row, formula.lease.col),
6091 (ast_id, plan),
6092 );
6093 }
6094 }
6095 let planned = planned_by_coord
6096 .into_iter()
6097 .map(|((sheet_id, row, col), (ast_id, plan))| (sheet_id, row, col, ast_id, plan))
6098 .collect::<Vec<_>>();
6099 let formula_count = planned.len();
6100 let graph = self
6101 .graph
6102 .prepare_legacy_graph_plan_multi_sheet(planned)
6103 .map_err(|error| {
6104 ExcelError::new(ExcelErrorKind::Value)
6105 .with_message(format!("target graph preparation failed: {error}"))
6106 })?;
6107 Ok((graph, formula_count))
6108 }
6109
6110 fn replay_target_coordinates(
6111 &mut self,
6112 replay: &Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6113 coordinates: &[(u32, u32)],
6114 deadline: Option<std::time::Instant>,
6115 scratch: &mut u64,
6116 ) -> Result<Option<Vec<crate::engine::DeferredReplayFormula>>, ExcelError> {
6117 if coordinates.is_empty() {
6118 return Ok(Some(Vec::new()));
6119 }
6120 let mut guard = replay.lock().map_err(|_| {
6121 ExcelError::new(ExcelErrorKind::Value)
6122 .with_message("deferred formula spool lock poisoned")
6123 })?;
6124 let retained = guard.selection_cache_footprint().is_some();
6125 let records = guard.replay_selected_exact(coordinates, &mut |work, bytes| {
6126 self.target_preparation_checkpoint(deadline, work)?;
6127 if retained && let Some(ledger) = self.active_resource_ledger.as_mut() {
6128 ledger
6129 .reserve_retained(bytes)
6130 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
6131 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
6132 }
6133 self.reserve_graph_source_scratch(bytes)?;
6134 *scratch = scratch.saturating_add(bytes);
6135 Ok(())
6136 });
6137 drop(guard);
6138 let reconciled = self.reconcile_source_cache_footprints();
6139 let records = records?;
6140 reconciled?;
6141 Ok(records)
6142 }
6143
6144 fn prepare_target_exact_source_selection(
6145 &mut self,
6146 sheet: &str,
6147 lease: StagedPackageLease,
6148 coordinates: Vec<(u32, u32)>,
6149 previous: &BTreeSet<(u32, u32)>,
6150 allow_partial_shared: bool,
6151 deadline: Option<std::time::Instant>,
6152 scratch: &mut u64,
6153 ) -> Result<Option<PreparedTargetSourcePackage>, ExcelError> {
6154 let package = self
6155 .staged_formulas
6156 .get(sheet)
6157 .and_then(|s| s.deferred_package.as_ref())
6158 .unwrap();
6159 if !package.source_geometry_complete
6160 || ((!package.families.is_empty() || !package.partitioned_families.is_empty())
6161 && !package.coordinates_cover_families)
6162 || package.reconciliation_replay.is_some()
6163 {
6164 return Ok(None);
6165 }
6166 let selected_points: BTreeSet<_> = coordinates
6167 .into_iter()
6168 .filter(|point| !package.suppressed.contains(point))
6169 .collect();
6170 let mut prepared = PreparedTargetSourcePackage::empty_selection(
6171 sheet,
6172 self.graph.sheet_id(sheet).unwrap(),
6173 lease,
6174 );
6175 if selected_points.is_empty() {
6176 return Ok(Some(prepared));
6177 }
6178 let mut routing = crate::engine::FormulaReplayDisposition::default();
6179 for partition in &package.partitioned_families {
6180 routing
6181 .register_partition(partition, false)
6182 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?;
6183 }
6184 routing.extend_suppressed_excel_coords(package.suppressed.iter().copied());
6185 routing.register_consumed_members(
6186 package.consumed_engine.as_ref(),
6187 package.consumed_members.iter().copied(),
6188 );
6189 let replay = Arc::clone(&package.replay);
6190 let source_report = package.accounting_report();
6191 prepared.source_report = source_report;
6192 prepared.disposition = routing.clone();
6193
6194 let replay_points: BTreeSet<_> = selected_points
6195 .iter()
6196 .copied()
6197 .filter(|&(row, col)| !prepared.direct_contains(row, col))
6198 .collect();
6199 let coordinates: Vec<_> = replay_points.iter().copied().collect();
6200 let records = self.replay_target_coordinates(&replay, &coordinates, deadline, scratch)?;
6201 prepared.spool_replays = u64::from(!coordinates.is_empty());
6202 let Some(mut replay_records) = records else {
6203 return Ok(None);
6204 };
6205 for record in &mut replay_records {
6206 if record.family.is_none() {
6207 record.partition_owner = routing
6208 .ordinary_disposition(crate::engine::SourceCoord {
6209 row: record.row.saturating_sub(1),
6210 col: record.col.saturating_sub(1),
6211 })
6212 .1;
6213 }
6214 }
6215 replay_records.sort_by_key(|record| record.source_order);
6216 if replay_records
6217 .iter()
6218 .any(|record| !selected_points.contains(&(record.row, record.col)))
6219 || replay_records
6220 .windows(2)
6221 .any(|records| records[0].source_order == records[1].source_order)
6222 {
6223 return Err(ExcelError::new(ExcelErrorKind::Value)
6224 .with_message("invalid indexed exact source selection"));
6225 }
6226 let represented: BTreeSet<_> = replay_records.iter().map(|r| (r.row, r.col)).collect();
6227 if represented != replay_points {
6228 return Err(ExcelError::new(ExcelErrorKind::Value)
6229 .with_message("incomplete indexed exact source selection"));
6230 }
6231 let source = self
6234 .staged_formulas
6235 .get(sheet)
6236 .unwrap()
6237 .deferred_package
6238 .as_ref()
6239 .unwrap();
6240 let contains = |rect: crate::engine::SourceRect, coord: crate::engine::SourceCoord| {
6241 coord.row >= rect.start.row
6242 && coord.row <= rect.end.row
6243 && coord.col >= rect.start.col
6244 && coord.col <= rect.end.col
6245 };
6246 let mut checked = BTreeSet::new();
6247 for record in replay_records.iter().rev() {
6248 if !checked.insert((record.row, record.col)) {
6249 continue;
6250 }
6251 let coord = crate::engine::SourceCoord {
6252 row: record.row - 1,
6253 col: record.col - 1,
6254 };
6255 let agrees = |owner, shared: bool| {
6256 record.family == shared.then_some(owner)
6257 && record.partition_owner.or(record.family) == Some(owner)
6258 };
6259 let mut valid = true;
6260 for family in &source.families {
6261 let owns = match &family.members {
6262 crate::engine::SourceFamilyMembers::CompleteDomain(domain) => {
6263 contains(domain.rect(), coord)
6264 }
6265 crate::engine::SourceFamilyMembers::ExplicitMembers(members) => {
6266 members.as_slice().binary_search(&coord).is_ok()
6267 }
6268 };
6269 if owns && !agrees(family.source_id, true) {
6270 valid = false;
6271 }
6272 }
6273 for family in &source.partitioned_families {
6274 if family
6275 .fragments
6276 .iter()
6277 .any(|fragment| contains(fragment.rect(), coord))
6278 && !agrees(family.source_id, true)
6279 {
6280 valid = false;
6281 }
6282 if let Some(member) = family
6283 .legacy_members
6284 .as_slice()
6285 .iter()
6286 .find(|member| member.coord == coord)
6287 && !agrees(
6288 family.source_id,
6289 member.kind == crate::engine::PartitionLegacyMemberKind::SharedFamilyMember,
6290 )
6291 {
6292 valid = false;
6293 }
6294 }
6295 if !valid {
6296 return Err(ExcelError::new(ExcelErrorKind::Value)
6297 .with_message("indexed source ownership mismatch"));
6298 }
6299 }
6300 prepared.selected_points = Some(selected_points);
6301 prepared.replay_records = replay_records;
6302 Ok(Some(prepared))
6303 }
6304
6305 fn prepare_target_source_package(
6306 &mut self,
6307 sheet: &str,
6308 lease: StagedPackageLease,
6309 deadline: Option<std::time::Instant>,
6310 ) -> Result<PreparedTargetSourcePackage, ExcelError> {
6311 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
6312 ExcelError::new(ExcelErrorKind::Ref)
6313 .with_message(format!("deferred source sheet not found: {sheet}"))
6314 })?;
6315 let (
6316 source_report,
6317 families,
6318 partitions,
6319 replay,
6320 invalidated,
6321 suppressed,
6322 consumed_members,
6323 consumed_engine,
6324 reconciliation_replay,
6325 ) = {
6326 let package = self
6327 .staged_formulas
6328 .get(sheet)
6329 .and_then(|staged| staged.deferred_package.as_ref())
6330 .ok_or_else(|| {
6331 ExcelError::new(ExcelErrorKind::Value)
6332 .with_message("staged deferred source package is unavailable")
6333 })?;
6334 if package.sheet_name != sheet {
6335 return Err(ExcelError::new(ExcelErrorKind::Value)
6336 .with_message("deferred formula package sheet mismatch"));
6337 }
6338 (
6339 package.accounting_report(),
6340 package.families.clone(),
6341 package.partitioned_families.clone(),
6342 Arc::clone(&package.replay),
6343 package.invalidated.clone(),
6344 package.suppressed.clone(),
6345 package.consumed_members.clone(),
6346 package.consumed_engine.clone(),
6347 package.reconciliation_replay.clone(),
6348 )
6349 };
6350
6351 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
6352 for partition in &partitions {
6353 replay_disposition
6354 .register_partition(partition, false)
6355 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
6356 }
6357 replay_disposition.extend_suppressed_excel_coords(suppressed.iter().copied());
6358 replay_disposition
6359 .register_consumed_members(consumed_engine.as_ref(), consumed_members.iter().copied());
6360 if !replay_disposition.consumed_engine_matches(&self.source_formula_token) {
6361 return Err(ExcelError::new(ExcelErrorKind::Value)
6362 .with_message("ResidualConsumedEngineMismatch"));
6363 }
6364 self.target_preparation_checkpoint(deadline, 1)?;
6365 let mut replay_records = if let Some(mut records) = reconciliation_replay {
6366 records.retain(|record| {
6367 let Some((row, col)) = record.row.checked_sub(1).zip(record.col.checked_sub(1))
6368 else {
6369 return true;
6370 };
6371 let coord = crate::engine::SourceCoord { row, col };
6372 let disposition = record.family.map_or_else(
6373 || replay_disposition.ordinary_disposition(coord).0,
6374 |family| replay_disposition.shared_disposition(family, coord),
6375 );
6376 !matches!(
6377 disposition,
6378 crate::engine::FormulaReplayCoordinateDisposition::Direct
6379 | crate::engine::FormulaReplayCoordinateDisposition::Suppressed
6380 )
6381 });
6382 records
6383 } else {
6384 replay
6385 .lock()
6386 .map_err(|_| {
6387 ExcelError::new(ExcelErrorKind::Value)
6388 .with_message("deferred formula spool lock poisoned")
6389 })?
6390 .replay_partitioned(&replay_disposition, &partitions)
6391 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?
6392 };
6393 replay_records.sort_by_key(|record| record.source_order);
6394 for chunk in replay_records.chunks(256) {
6395 self.target_preparation_checkpoint(deadline, chunk.len() as u64)?;
6396 }
6397 if replay_records
6398 .windows(2)
6399 .any(|records| records[0].source_order == records[1].source_order)
6400 {
6401 return Err(ExcelError::new(ExcelErrorKind::Value)
6402 .with_message("duplicate deferred source-order proof"));
6403 }
6404
6405 let disposition = replay_disposition;
6406 let direct_families = 0usize;
6407 let direct_cells = 0u64;
6408 let direct_fragments = 0u64;
6409 let direct_complete_families = 0u64;
6410 let direct_complete_cells = 0u64;
6411 let direct_partition_families = 0u64;
6412 let direct_partition_cells = 0u64;
6413 let anchor_parses = 0u64;
6414 let anchor_asts = 0u64;
6415 let anchor_analyses = 0u64;
6416
6417 Ok(PreparedTargetSourcePackage {
6418 sheet: sheet.to_string(),
6419 sheet_id,
6420 lease,
6421 selected_points: None,
6422 complete_selections: Default::default(),
6423 deferred_shared: false,
6424 direct_domains: Vec::new(),
6425 source_report,
6426 replay_records,
6427 spool_replays: 1,
6428 disposition,
6429 legacy: Vec::new(),
6430 direct_families,
6431 direct_cells,
6432 direct_fragments,
6433 direct_complete_families,
6434 direct_complete_cells,
6435 direct_partition_families,
6436 direct_partition_cells,
6437 anchor_parses,
6438 anchor_asts,
6439 anchor_analyses,
6440 })
6441 }
6442
6443 fn fallback_planning_snapshot(
6444 &self,
6445 batch: &FormulaIngestBatch,
6446 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
6447 let mut requests = Vec::new();
6448 for formula in &batch.formulas {
6449 let ast = self
6450 .graph
6451 .data_store()
6452 .retrieve_ast(formula.ast_id, self.graph.sheet_reg())
6453 .ok_or_else(|| {
6454 ExcelError::new(ExcelErrorKind::Value)
6455 .with_message("ordered fallback AST is unavailable")
6456 })?;
6457 Self::collect_planning_function_requests(&ast, &mut requests);
6458 }
6459 requests.sort();
6460 requests.dedup();
6461 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
6462 &self.resolver,
6463 requests,
6464 )
6465 .map_err(|error| ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}")))
6466 }
6467
6468 fn prepare_legacy_batch_fallback(
6469 &mut self,
6470 batch: FormulaIngestBatch,
6471 function_provider: &dyn crate::traits::FunctionProvider,
6472 ) -> Result<
6473 (
6474 crate::engine::graph::prepared_legacy_graph::PreparedLegacyGraphPlan,
6475 u64,
6476 ),
6477 ExcelError,
6478 > {
6479 let formula_count = batch.formulas.len() as u64;
6480 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
6481 let mut planned = Vec::with_capacity(batch.formulas.len());
6482 for record in batch.formulas {
6483 let placement = CellRef::new(
6484 sheet_id,
6485 Coord::from_excel(record.row, record.col, true, true),
6486 );
6487 let ingested = self
6488 .graph
6489 .ingest_pipeline(function_provider)
6490 .enable_function_semantics()
6491 .ingest_formula(
6492 FormulaAstInput::RawArena(record.ast_id),
6493 placement,
6494 record.formula_text,
6495 )
6496 .map_err(|error| {
6497 ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}"))
6498 })?;
6499 planned.push((record.row, record.col, ingested.ast_id, ingested.dep_plan));
6500 }
6501 let plan = self
6502 .graph
6503 .prepare_legacy_graph_plan(sheet_id, planned)
6504 .map_err(|error| {
6505 ExcelError::new(ExcelErrorKind::Value).with_message(error.to_string())
6506 })?;
6507 Ok((plan, formula_count))
6508 }
6509
6510 fn publish_compressed_partial_report(
6511 &mut self,
6512 report: &FormulaIngestReport,
6513 direct_report: &FormulaIngestReport,
6514 ) {
6515 if direct_report.source_family_promoted == 0
6516 && direct_report.graph_formula_cells_materialized == 0
6517 {
6518 return;
6519 }
6520 let mut published = report.clone();
6521 published.accumulate(direct_report);
6522 self.record_formula_ingest_report(published);
6523 }
6524
6525 fn finish_compressed_formula_sources(
6526 &mut self,
6527 batches: Vec<(
6528 FormulaIngestBatch,
6529 crate::engine::FormulaCompressedSourceReport,
6530 crate::engine::FormulaCompressedPreparation,
6531 )>,
6532 ) -> Result<FormulaIngestReport, ExcelError> {
6533 self.observe_function_semantic_epoch()?;
6534 if batches.iter().any(|(_, _, preparation)| {
6535 !Arc::ptr_eq(&preparation.engine_token, &self.source_formula_token)
6536 }) {
6537 return Err(ExcelError::new(ExcelErrorKind::Value)
6538 .with_message("compressed source preparation belongs to another engine"));
6539 }
6540 if batches.iter().any(|(fallback, _, preparation)| {
6541 preparation.sheet_name.as_ref() != fallback.sheet_name
6542 }) {
6543 return Err(ExcelError::new(ExcelErrorKind::Value)
6544 .with_message("compressed source preparation sheet mismatch"));
6545 }
6546 let initial_guard = crate::function_registry::semantic_epoch_read_guard();
6547 let initial_provider_revision = self.resolver.planning_semantic_revision();
6548 let mut fallback_batches = Vec::with_capacity(batches.len());
6549 let mut stale_fallback_batches = Vec::new();
6550 let mut pending_preparations = Vec::new();
6551 for (mut fallback, mut source, mut preparation) in batches {
6552 for formula in fallback.formulas.drain(..) {
6553 let source_order = formula.source_order.ok_or_else(|| {
6554 ExcelError::new(ExcelErrorKind::Value).with_message(
6555 "compressed source supplied formulas without source-order proof",
6556 )
6557 })?;
6558 let text = formula.formula_text.ok_or_else(|| {
6559 ExcelError::new(ExcelErrorKind::Value).with_message(
6560 "ordered compressed fallback formula has no exact source text",
6561 )
6562 })?;
6563 preparation
6564 .eager_replay
6565 .push(crate::engine::DeferredReplayFormula {
6566 source_order,
6567 row: formula.row,
6568 col: formula.col,
6569 text: text.to_string(),
6570 family: formula.source_family,
6571 partition_owner: formula.partition_owner,
6572 });
6573 }
6574 preparation
6575 .eager_replay
6576 .sort_by_key(|record| record.source_order);
6577 source.source_spool_replays = source
6578 .source_spool_replays
6579 .saturating_add(preparation.preparation_spool_replays);
6580 let stale_reason = preparation
6581 .function_semantics_used
6582 .then(|| {
6583 if preparation.function_provider_revision != initial_provider_revision {
6584 Some("FunctionProviderRevisionChanged")
6585 } else if self.prepared_function_semantics_changed(&preparation, &initial_guard)
6586 {
6587 Some("FunctionSemanticEpochChanged")
6588 } else {
6589 None
6590 }
6591 })
6592 .flatten();
6593 let stale_semantics = stale_reason.is_some();
6594 for reason in preparation.rejected.values() {
6595 *source.fallback_reasons.entry(reason.clone()).or_default() += 1;
6596 }
6597 source.replay_families = source.families_seen;
6599 source.replay_cells = source.family_cells_seen;
6600 let compressed = crate::engine::FormulaCompressedSourceBatch::new(
6601 fallback.sheet_name.clone(),
6602 source,
6603 );
6604 if stale_semantics {
6605 stale_fallback_batches.push((fallback, compressed));
6606 } else {
6607 fallback_batches.push((fallback, compressed));
6608 }
6609 pending_preparations.push((preparation, stale_semantics));
6610 }
6611 drop(initial_guard);
6612
6613 let configured_mode = self.config.formula_plane_mode;
6615 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6616 let stale_result =
6617 self.ingest_compressed_formula_source_batches_inner(stale_fallback_batches, false);
6618 self.config.formula_plane_mode = configured_mode;
6619 let mut report = stale_result?;
6620 report.mode = configured_mode;
6621
6622 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6623 let fallback_result =
6624 self.ingest_compressed_formula_source_batches_inner(fallback_batches, false);
6625 self.config.formula_plane_mode = configured_mode;
6626 match fallback_result {
6627 Ok(fallback_report) => report.accumulate(&fallback_report),
6628 Err(error) => {
6629 self.record_formula_ingest_report(report);
6630 return Err(error);
6631 }
6632 }
6633
6634 let mut direct_report =
6635 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
6636 for (preparation, _) in &pending_preparations {
6637 direct_report.source_anchor_parses = direct_report.source_anchor_parses.saturating_add(
6638 preparation.clean_rejected_anchor_counts[0]
6639 .saturating_add(preparation.fragmented_rejected_anchor_counts[0]),
6640 );
6641 direct_report.source_anchor_asts = direct_report.source_anchor_asts.saturating_add(
6642 preparation.clean_rejected_anchor_counts[1]
6643 .saturating_add(preparation.fragmented_rejected_anchor_counts[1]),
6644 );
6645 direct_report.source_anchor_analyses =
6646 direct_report.source_anchor_analyses.saturating_add(
6647 preparation.clean_rejected_anchor_counts[2]
6648 .saturating_add(preparation.fragmented_rejected_anchor_counts[2]),
6649 );
6650 }
6651 loop {
6652 #[cfg(any(test, feature = "test-support"))]
6653 if let Some(hook) = self.before_prepared_span_commit_hook.take() {
6654 hook();
6655 }
6656 let commit_guard = crate::function_registry::semantic_epoch_read_guard();
6657 let commit_provider_revision = self.resolver.planning_semantic_revision();
6658 let mut newly_stale = Vec::new();
6659 let mut current = Vec::new();
6660 for pending in pending_preparations.drain(..) {
6661 let stale_reason = pending
6662 .0
6663 .function_semantics_used
6664 .then(|| {
6665 if pending.0.function_provider_revision != commit_provider_revision {
6666 Some("FunctionProviderRevisionChanged")
6667 } else if self
6668 .prepared_function_semantics_changed(&pending.0, &commit_guard)
6669 {
6670 Some("FunctionSemanticEpochChanged")
6671 } else {
6672 None
6673 }
6674 })
6675 .flatten();
6676 if let Some(reason) = stale_reason {
6677 newly_stale.push((pending, reason));
6678 } else {
6679 current.push(pending);
6680 }
6681 }
6682 if !newly_stale.is_empty() {
6683 drop(commit_guard);
6684 for ((mut preparation, was_initially_stale), reason) in newly_stale {
6685 preparation
6686 .eager_replay
6687 .sort_by_key(|record| record.source_order);
6688 direct_report.source_spool_replays =
6689 direct_report.source_spool_replays.saturating_add(1);
6690 if !was_initially_stale {
6691 direct_report
6692 .fallback_reasons
6693 .entry(reason.to_string())
6694 .or_default();
6695 }
6696 preparation.function_semantics_used = false;
6697 if !preparation.eager_replay.is_empty() {
6698 current.push((preparation, false));
6699 }
6700 }
6701 pending_preparations = current;
6702 continue;
6703 }
6704 drop(commit_guard);
6705
6706 enum SourceProposal {
6708 KnownFallback {
6709 source_order: crate::engine::SourceFormulaOrder,
6710 records: Vec<crate::engine::DeferredReplayFormula>,
6711 },
6712 }
6713
6714 impl SourceProposal {
6715 fn source_order(&self) -> crate::engine::SourceFormulaOrder {
6716 match self {
6717 Self::KnownFallback { source_order, .. } => *source_order,
6718 }
6719 }
6720 }
6721
6722 for (mut preparation, _) in current {
6723 let mut proposals = Vec::with_capacity(preparation.eager_replay.len());
6724 let mut family_fallbacks: BTreeMap<_, Vec<_>> = BTreeMap::new();
6725 for record in preparation.eager_replay.drain(..) {
6726 if let Some(owner) = record.partition_owner.or(record.family) {
6727 family_fallbacks.entry(owner).or_default().push(record);
6728 } else {
6729 proposals.push(SourceProposal::KnownFallback {
6730 source_order: record.source_order,
6731 records: vec![record],
6732 });
6733 }
6734 }
6735 for (_, mut records) in family_fallbacks {
6736 records.sort_by_key(|record| record.source_order);
6737 if records
6738 .windows(2)
6739 .any(|window| window[0].source_order == window[1].source_order)
6740 {
6741 self.publish_compressed_partial_report(&report, &direct_report);
6742 return Err(ExcelError::new(ExcelErrorKind::Value)
6743 .with_message("duplicate exact-replay source-order proof"));
6744 }
6745 let Some(source_order) = records.first().map(|record| record.source_order)
6746 else {
6747 self.publish_compressed_partial_report(&report, &direct_report);
6748 return Err(ExcelError::new(ExcelErrorKind::Value)
6749 .with_message("empty exact-replay fallback family"));
6750 };
6751 proposals.push(SourceProposal::KnownFallback {
6752 source_order,
6753 records,
6754 });
6755 }
6756 proposals.sort_by_key(SourceProposal::source_order);
6757 if proposals
6758 .windows(2)
6759 .any(|window| window[0].source_order() == window[1].source_order())
6760 {
6761 self.publish_compressed_partial_report(&report, &direct_report);
6762 return Err(ExcelError::new(ExcelErrorKind::Value)
6763 .with_message("ambiguous compressed source-order proof"));
6764 }
6765
6766 for proposal in proposals {
6767 match proposal {
6768 SourceProposal::KnownFallback { records, .. } => {
6769 let batch = match self.formula_batch_from_exact_replay(
6770 preparation.sheet_name.as_ref(),
6771 records,
6772 ) {
6773 Ok(batch) => batch,
6774 Err(error) => {
6775 self.publish_compressed_partial_report(&report, &direct_report);
6776 return Err(error);
6777 }
6778 };
6779 if batch.is_empty() {
6780 continue;
6781 }
6782 let snapshot = match self.fallback_planning_snapshot(&batch) {
6783 Ok(snapshot) => snapshot,
6784 Err(error) => {
6785 self.publish_compressed_partial_report(&report, &direct_report);
6786 return Err(error);
6787 }
6788 };
6789 let commit_guard =
6790 crate::function_registry::semantic_epoch_read_guard();
6791 let provider_revision_initial =
6792 self.resolver.planning_semantic_revision();
6793 if (commit_guard.epoch() != snapshot.epoch()
6794 && snapshot.semantic_changes_affect_requests_since_guarded(
6795 &commit_guard,
6796 snapshot.epoch(),
6797 ))
6798 || snapshot.provider_revision().is_some_and(|revision| {
6799 Some(revision) != provider_revision_initial
6800 })
6801 {
6802 drop(commit_guard);
6803 self.publish_compressed_partial_report(&report, &direct_report);
6804 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6805 "ordered fallback planning snapshot became stale",
6806 ));
6807 }
6808 let (plan, formula_count) = match self
6809 .prepare_legacy_batch_fallback(batch, &snapshot)
6810 {
6811 Ok(plan) => plan,
6812 Err(error) => {
6813 drop(commit_guard);
6814 self.publish_compressed_partial_report(&report, &direct_report);
6815 return Err(error);
6816 }
6817 };
6818 let provider_revision_after =
6819 self.resolver.planning_semantic_revision();
6820 if provider_revision_after != provider_revision_initial {
6821 drop(commit_guard);
6822 self.publish_compressed_partial_report(&report, &direct_report);
6823 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6824 "function provider changed while preparing ordered fallback",
6825 ));
6826 }
6827 let graph_vertices = plan.new_vertex_count();
6828 let Some(graph_edges) = plan.planned_edge_count() else {
6829 drop(commit_guard);
6830 self.publish_compressed_partial_report(&report, &direct_report);
6831 return Err(ExcelError::new(ExcelErrorKind::Value)
6832 .with_message("prepared ordered fallback size overflow"));
6833 };
6834 if let Err(error) = self.prepared_legacy_admission(&plan, formula_count)
6835 {
6836 drop(commit_guard);
6837 self.publish_compressed_partial_report(&report, &direct_report);
6838 return Err(error);
6839 }
6840 if let Err(error) =
6841 self.graph.validate_prepared_legacy_graph_plan(&plan)
6842 {
6843 drop(commit_guard);
6844 self.publish_compressed_partial_report(&report, &direct_report);
6845 return Err(ExcelError::new(ExcelErrorKind::Value)
6846 .with_message(error.to_string()));
6847 }
6848 #[cfg(test)]
6849 if let Some(hook) = self
6850 .before_legacy_fallback_final_provider_sample_hook
6851 .take()
6852 {
6853 hook();
6854 }
6855 let provider_revision_final =
6856 self.resolver.planning_semantic_revision();
6857 if provider_revision_final != provider_revision_initial {
6858 drop(commit_guard);
6859 self.publish_compressed_partial_report(&report, &direct_report);
6860 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6861 "function provider changed after ordered fallback validation",
6862 ));
6863 }
6864 let graph_formulas =
6865 self.graph.apply_prevalidated_legacy_graph_plan(plan);
6866 direct_report.formula_cells_seen = direct_report
6867 .formula_cells_seen
6868 .saturating_add(formula_count);
6869 direct_report.graph_formula_cells_materialized = direct_report
6870 .graph_formula_cells_materialized
6871 .saturating_add(graph_formulas as u64);
6872 direct_report.graph_vertices_created = direct_report
6873 .graph_vertices_created
6874 .saturating_add(graph_vertices as u64);
6875 direct_report.graph_edges_created = direct_report
6876 .graph_edges_created
6877 .saturating_add(graph_edges as u64);
6878 }
6879 }
6880 #[cfg(test)]
6881 if let Some(hook) = self.after_eager_proposal_commit_hook.take() {
6882 hook();
6883 }
6884 }
6885 }
6886 break;
6887 }
6888 report.accumulate(&direct_report);
6889 self.record_formula_ingest_report(report.clone());
6890 Ok(report)
6891 }
6892 pub(crate) fn ingest_compressed_formula_source_batches(
6894 &mut self,
6895 batches: Vec<(
6896 FormulaIngestBatch,
6897 crate::engine::FormulaCompressedSourceBatch,
6898 )>,
6899 ) -> Result<FormulaIngestReport, ExcelError> {
6900 self.ingest_compressed_formula_source_batches_inner(batches, true)
6901 }
6902
6903 fn ingest_compressed_formula_source_batches_inner(
6904 &mut self,
6905 batches: Vec<(
6906 FormulaIngestBatch,
6907 crate::engine::FormulaCompressedSourceBatch,
6908 )>,
6909 publish_report: bool,
6910 ) -> Result<FormulaIngestReport, ExcelError> {
6911 let mut source_counts = [0_u64; 11];
6912 let mut source_report = crate::engine::FormulaCompressedSourceReport::default();
6913 let mut formula_batches = Vec::with_capacity(batches.len());
6914 let mut compressed_families = Vec::new();
6915 let mut partitioned_families = Vec::new();
6916 for (batch, compressed_batch) in batches {
6917 let (sheet_name, compressed, families, partitions) = compressed_batch.into_parts();
6918 if sheet_name.as_ref() != batch.sheet_name {
6919 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6920 "compressed formula source sheet does not match its replay batch",
6921 ));
6922 }
6923 compressed_families.push((batch.sheet_name.clone(), families));
6924 partitioned_families.push((batch.sheet_name.clone(), partitions));
6925 source_counts[0] = source_counts[0].saturating_add(compressed.source_formula_events);
6926 source_counts[1] = source_counts[1].saturating_add(compressed.source_ordinary_events);
6927 source_counts[2] =
6928 source_counts[2].saturating_add(compressed.source_shared_anchor_events);
6929 source_counts[3] =
6930 source_counts[3].saturating_add(compressed.source_shared_descendant_events);
6931 source_counts[4] = source_counts[4].saturating_add(compressed.source_unknown_events);
6932 source_counts[5] =
6933 source_counts[5].saturating_add(compressed.source_formula_records_spooled);
6934 source_counts[6] =
6935 source_counts[6].saturating_add(compressed.source_spool_encoded_bytes);
6936 source_counts[7] = source_counts[7].max(compressed.source_spool_peak_memory_bytes);
6937 source_counts[8] =
6938 source_counts[8].saturating_add(compressed.source_spool_spilled_bytes);
6939 source_counts[9] = source_counts[9].saturating_add(compressed.source_spool_spill_files);
6940 source_counts[10] = source_counts[10].saturating_add(compressed.source_spool_replays);
6941 source_report.families_seen = source_report
6942 .families_seen
6943 .saturating_add(compressed.families_seen);
6944 source_report.family_cells_seen = source_report
6945 .family_cells_seen
6946 .saturating_add(compressed.family_cells_seen);
6947 source_report.source_clean_families = source_report
6948 .source_clean_families
6949 .saturating_add(compressed.source_clean_families);
6950 source_report.source_clean_cells = source_report
6951 .source_clean_cells
6952 .saturating_add(compressed.source_clean_cells);
6953 source_report.source_fragmentable_families = source_report
6954 .source_fragmentable_families
6955 .saturating_add(compressed.source_fragmentable_families);
6956 source_report.source_fragmentable_cells = source_report
6957 .source_fragmentable_cells
6958 .saturating_add(compressed.source_fragmentable_cells);
6959 source_report.source_fragment_count = source_report
6960 .source_fragment_count
6961 .saturating_add(compressed.source_fragment_count);
6962 source_report.source_isolated_fallback_cells = source_report
6963 .source_isolated_fallback_cells
6964 .saturating_add(compressed.source_isolated_fallback_cells);
6965 source_report.source_hole_exclusions = source_report
6966 .source_hole_exclusions
6967 .saturating_add(compressed.source_hole_exclusions);
6968 source_report.source_ordinary_exclusions = source_report
6969 .source_ordinary_exclusions
6970 .saturating_add(compressed.source_ordinary_exclusions);
6971 source_report.source_partition_failures = source_report
6972 .source_partition_failures
6973 .saturating_add(compressed.source_partition_failures);
6974 source_report.replay_families = source_report
6975 .replay_families
6976 .saturating_add(compressed.replay_families);
6977 source_report.replay_cells = source_report
6978 .replay_cells
6979 .saturating_add(compressed.replay_cells);
6980 source_report.forward_descendants = source_report
6981 .forward_descendants
6982 .saturating_add(compressed.forward_descendants);
6983 source_report.evidence_limit_fallbacks = source_report
6984 .evidence_limit_fallbacks
6985 .saturating_add(compressed.evidence_limit_fallbacks);
6986 source_report.evidence_peak_bytes = source_report
6987 .evidence_peak_bytes
6988 .max(compressed.evidence_peak_bytes);
6989 for (reason, count) in compressed.fallback_reasons {
6990 *source_report.fallback_reasons.entry(reason).or_default() += count;
6991 }
6992 formula_batches.push(batch);
6993 }
6994 self.ingest_formula_batches_inner(
6995 formula_batches,
6996 source_counts,
6997 Some(source_report),
6998 compressed_families,
6999 partitioned_families,
7000 publish_report,
7001 )
7002 }
7003
7004 pub fn ingest_formula_batches(
7005 &mut self,
7006 batches: Vec<FormulaIngestBatch>,
7007 ) -> Result<FormulaIngestReport, ExcelError> {
7008 let has_formulas = batches.iter().any(|batch| !batch.formulas.is_empty());
7009 let report = self.ingest_formula_batches_inner(
7010 batches,
7011 [0; 11],
7012 None,
7013 Vec::new(),
7014 Vec::new(),
7015 true,
7016 )?;
7017 if has_formulas {
7018 self.mark_topology_edited();
7019 }
7020 Ok(report)
7021 }
7022
7023 fn ingest_formula_batches_unpublished(
7024 &mut self,
7025 batches: Vec<FormulaIngestBatch>,
7026 ) -> Result<FormulaIngestReport, ExcelError> {
7027 self.ingest_formula_batches_inner(batches, [0; 11], None, Vec::new(), Vec::new(), false)
7028 }
7029
7030 fn ingest_formula_batches_inner(
7031 &mut self,
7032 batches: Vec<FormulaIngestBatch>,
7033 source_counts: [u64; 11],
7034 source_report: Option<crate::engine::FormulaCompressedSourceReport>,
7035 compressed_families: Vec<(String, Vec<crate::engine::SourceFormulaFamily>)>,
7036 partitioned_families: Vec<(String, Vec<crate::engine::PartitionedSourceFormulaFamily>)>,
7037 publish_report: bool,
7038 ) -> Result<FormulaIngestReport, ExcelError> {
7039 self.observe_function_semantic_epoch()?;
7040 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
7041 #[cfg(feature = "tracing")]
7042 let arena_nodes_before = self.graph.data_store().memory_usage().total_ast_nodes;
7043 #[cfg(feature = "tracing")]
7044 let route = if !partitioned_families.is_empty() {
7045 "partitioned"
7046 } else if !compressed_families.is_empty() {
7047 "compressed_source"
7048 } else if source_report.is_some() {
7049 "replay"
7050 } else {
7051 "ordinary"
7052 };
7053 let _ingest_span = crate::engine::trace::fz_span!(
7054 tracing::Level::INFO,
7055 "ingest",
7056 "ingest.batch",
7057 mode = ?FormulaPlaneMode::Off,
7058 route,
7059 formula_cells = formula_cells_seen
7060 );
7061 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
7062 let materialize_batches = batches;
7063 report.formula_cells_seen = formula_cells_seen;
7064 report.source_formula_events = source_counts[0];
7065 report.source_ordinary_events = source_counts[1];
7066 report.source_shared_anchor_events = source_counts[2];
7067 report.source_shared_descendant_events = source_counts[3];
7068 report.source_unknown_events = source_counts[4];
7069 report.source_formula_records_spooled = source_counts[5];
7070 report.source_spool_encoded_bytes = source_counts[6];
7071 report.source_spool_peak_memory_bytes = source_counts[7];
7072 report.source_spool_spilled_bytes = source_counts[8];
7073 report.source_spool_spill_files = source_counts[9];
7074 report.source_spool_replays = source_counts[10];
7075 if let Some(source) = source_report {
7076 report.source_families_seen = source.families_seen;
7077 report.source_family_cells_seen = source.family_cells_seen;
7078 report.source_family_shadow_eligible = source.source_clean_families;
7079 report.source_family_shadow_eligible_cells = source.source_clean_cells;
7080 report.source_partitioned_families_seen = source.source_fragmentable_families;
7081 report.source_partition_holes = source.source_hole_exclusions;
7082 report.source_partition_ordinary_exceptions = source.source_ordinary_exclusions;
7083 report.source_partition_failures = source.source_partition_failures;
7084 report.source_partition_surviving_cells = source.source_fragmentable_cells;
7085 report.source_family_fallback = report
7086 .source_family_fallback
7087 .saturating_add(source.replay_families);
7088 report.source_family_fallback_cells = report
7089 .source_family_fallback_cells
7090 .saturating_add(source.replay_cells);
7091 report.source_forward_descendants = source.forward_descendants;
7092 report.source_evidence_limit_fallbacks = source.evidence_limit_fallbacks;
7093 report.source_evidence_peak_bytes = source.evidence_peak_bytes;
7094 for (reason, count) in source.fallback_reasons {
7095 let total = report.fallback_reasons.entry(reason).or_default();
7096 *total = total.saturating_add(count);
7097 }
7098 }
7099
7100 if self.graph.first_load_assume_new()
7104 && !self.graph_admission_enabled()
7105 && !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
7106 {
7107 let mut builder =
7108 crate::engine::ingest_builder::BulkIngestBuilder::new(&mut self.graph);
7109 for batch in materialize_batches {
7110 if batch.is_empty() {
7111 continue;
7112 }
7113 let sheet_id = builder
7114 .add_sheet_checked(&batch.sheet_name)
7115 .ok_or_else(|| {
7116 ExcelError::new(ExcelErrorKind::Ref)
7117 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7118 })?;
7119 builder.add_formula_refs(
7120 sheet_id,
7121 batch.formulas.into_iter().map(|record| {
7122 (
7123 record.row,
7124 record.col,
7125 crate::engine::graph::FormulaRef::of_ingested(
7126 record.ast_id,
7127 record.member_anchor,
7128 ),
7129 )
7130 }),
7131 );
7132 }
7133 let summary = builder.finish_with_provider(&self.resolver)?;
7134 report.graph_formula_cells_materialized = summary.formulas as u64;
7135 report.graph_vertices_created = summary.vertices as u64;
7136 report.graph_edges_created = summary.edges as u64;
7137 } else if !materialize_batches.iter().all(FormulaIngestBatch::is_empty) {
7138 let mut prepared_by_sheet: BTreeMap<String, Vec<_>> = BTreeMap::new();
7139 for batch in materialize_batches {
7140 if batch.is_empty() {
7141 continue;
7142 }
7143 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
7144 ExcelError::new(ExcelErrorKind::Ref)
7145 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7146 })?;
7147 let mut pipeline = self.ingest_pipeline();
7148 let prepared = prepared_by_sheet.entry(batch.sheet_name).or_default();
7151 prepared.reserve(batch.formulas.len());
7152 for record in batch.formulas {
7153 let placement = CellRef::new(
7154 sheet_id,
7155 Coord::from_excel(record.row, record.col, true, true),
7156 );
7157 let input = match record.member_anchor {
7158 Some(anchor) => FormulaAstInput::Member {
7159 template: record.ast_id,
7160 anchor,
7161 },
7162 None => FormulaAstInput::RawArena(record.ast_id),
7163 };
7164 let formula = pipeline.ingest_formula(input, placement, None)?;
7165 prepared.push((
7166 record.row,
7167 record.col,
7168 crate::engine::graph::FormulaRef::of_ingested(
7169 formula.ast_id,
7170 formula.member_anchor,
7171 ),
7172 formula.dep_plan,
7173 ));
7174 }
7175 }
7176 let admission_preflighted = self.graph_admission_enabled();
7177 if admission_preflighted {
7178 let mut preview = Vec::new();
7179 for (sheet_name, formulas) in &prepared_by_sheet {
7180 let sheet_id = self.graph.sheet_id(sheet_name).ok_or_else(|| {
7181 ExcelError::new(ExcelErrorKind::Ref)
7182 .with_message(format!("unknown ingest sheet: {sheet_name}"))
7183 })?;
7184 preview.extend(
7185 formulas
7186 .iter()
7187 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone())),
7188 );
7189 }
7190 let admission = self.graph.preview_formula_mutations(&preview)?;
7191 self.preflight_graph_admission(admission)?;
7192 }
7193
7194 let mut builder = self.begin_bulk_ingest();
7195 if admission_preflighted {
7196 builder.mark_admission_preflighted();
7197 }
7198 for (sheet_name, formulas) in prepared_by_sheet {
7199 if formulas.is_empty() {
7200 continue;
7201 }
7202 let sheet_id = builder.add_sheet(&sheet_name);
7203 builder.add_formula_plans(sheet_id, formulas);
7204 }
7205 let summary = builder.finish()?;
7206 report.graph_formula_cells_materialized = summary.formulas as u64;
7207 report.graph_vertices_created = summary.vertices as u64;
7208 report.graph_edges_created = summary.edges as u64;
7209 }
7210
7211 crate::engine::trace::fz_event!(
7212 tracing::Level::INFO,
7213 "ingest",
7214 "ingest.summary",
7215 candidate_cells = report.shadow_candidate_cells,
7216 accepted_span_cells = report.shadow_accepted_span_cells,
7217 fallback_cells = report.shadow_fallback_cells,
7218 spans_created = report.shadow_spans_created,
7219 templates_interned = report.shadow_templates_interned,
7220 graph_vertices_created = report.graph_vertices_created,
7221 graph_edges_created = report.graph_edges_created,
7222 arena_nodes_delta = self
7223 .graph
7224 .data_store()
7225 .memory_usage()
7226 .total_ast_nodes
7227 .saturating_sub(arena_nodes_before)
7228 );
7229 if publish_report {
7230 self.record_formula_ingest_report(report.clone());
7231 }
7232 Ok(report)
7233 }
7234
7235 fn dedup_formula_parse_diagnostics_since(&mut self, start: usize) {
7236 let mut unique = Vec::new();
7237 for diagnostic in self.formula_parse_diagnostics.drain(start..) {
7238 let duplicate = unique.iter().any(|prior: &FormulaParseDiagnostic| {
7239 prior.sheet == diagnostic.sheet
7240 && prior.row == diagnostic.row
7241 && prior.col == diagnostic.col
7242 && prior.formula == diagnostic.formula
7243 && prior.policy == diagnostic.policy
7244 });
7245 if !duplicate {
7246 unique.push(diagnostic);
7247 }
7248 }
7249 self.formula_parse_diagnostics.extend(unique);
7250 }
7251
7252 pub fn handle_formula_parse_error(
7253 &mut self,
7254 sheet: &str,
7255 row: u32,
7256 col: u32,
7257 formula: &str,
7258 message: String,
7259 ) -> Result<Option<ASTNode>, ExcelError> {
7260 let policy = self.config.formula_parse_policy;
7261
7262 if policy == FormulaParsePolicy::Strict {
7263 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
7264 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
7265 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
7266 )));
7267 }
7268
7269 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
7270 sheet: sheet.to_string(),
7271 row,
7272 col,
7273 formula: formula.to_string(),
7274 message: message.clone(),
7275 policy,
7276 });
7277
7278 match policy {
7279 FormulaParsePolicy::Strict => unreachable!(),
7280 FormulaParsePolicy::KeepCachedValue => Ok(None),
7281 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
7282 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
7283 None,
7284 ))),
7285 FormulaParsePolicy::CoerceToError => {
7286 let err = ExcelError::new(ExcelErrorKind::Error)
7287 .with_message(format!("Malformed formula: {message}"));
7288 Ok(Some(ASTNode::new(
7289 ASTNodeType::Literal(LiteralValue::Error(err)),
7290 None,
7291 )))
7292 }
7293 }
7294 }
7295
7296 #[cfg(test)]
7297 fn target_preparation_fault(
7298 &mut self,
7299 seam: crate::engine::target_preparation::TargetPreparationFault,
7300 ) -> Result<(), ExcelError> {
7301 if self.target_preparation_fault_for_test == Some(seam) {
7302 self.target_preparation_fault_for_test = None;
7303 Err(ExcelError::new(ExcelErrorKind::Value)
7304 .with_message(format!("injected target preparation fault: {seam:?}")))
7305 } else {
7306 Ok(())
7307 }
7308 }
7309
7310 fn preparation_stale(
7311 reason: formualizer_common::PreparationStaleReason,
7312 message: impl Into<String>,
7313 ) -> ExcelError {
7314 ExcelError::new(ExcelErrorKind::Value)
7315 .with_message(message)
7316 .with_extra(formualizer_common::ExcelErrorExtra::PreparationStale { reason })
7317 }
7318
7319 fn preparation_revision_stale_reason(
7320 assumptions: &crate::engine::PreparationRevision,
7321 current: &crate::engine::PreparationRevision,
7322 planning_requests: &BTreeSet<(String, String, usize)>,
7323 staged_leases_match: bool,
7324 ) -> Option<formualizer_common::PreparationStaleReason> {
7325 if assumptions.graph != current.graph {
7326 Some(formualizer_common::PreparationStaleReason::Graph)
7327 } else if assumptions.staged != current.staged || !staged_leases_match {
7328 Some(formualizer_common::PreparationStaleReason::Staged)
7329 } else if assumptions.symbols != current.symbols {
7330 Some(formualizer_common::PreparationStaleReason::Symbols)
7331 } else if assumptions.provider != current.provider {
7332 Some(formualizer_common::PreparationStaleReason::Provider)
7333 } else if assumptions.semantic != current.semantic
7334 && crate::function_registry::semantic_changes_affect_requests_since(
7335 assumptions.semantic,
7336 planning_requests.iter().cloned(),
7337 )
7338 {
7339 Some(formualizer_common::PreparationStaleReason::Semantic)
7340 } else {
7341 None
7342 }
7343 }
7344
7345 fn preparation_revisions(&self) -> crate::engine::PreparationRevision {
7346 crate::engine::PreparationRevision {
7347 graph: self.graph.topology_revision(),
7348 authority: 0,
7349 authority_indexes: 0,
7350 authority_indexed_plane: 0,
7351 staged: self.staged_formula_index.revision(),
7352 symbols: self.graph.symbol_revision(),
7353 semantic: crate::function_registry::semantic_epoch(),
7354 provider: self.resolver.planning_semantic_revision(),
7355 }
7356 }
7357
7358 fn planning_revision_snapshot(&self) -> PlanningRevisionSnapshot {
7359 let registry_guard = crate::function_registry::semantic_epoch_read_guard();
7360 let provider = self.resolver.planning_semantic_revision();
7361 let semantic = registry_guard.epoch();
7362 PlanningRevisionSnapshot {
7363 engine_topology_epoch: self.topology_epoch,
7364 graph_topology_revision: self.graph.topology_revision(),
7365 staged: self.staged_formula_index.revision(),
7366 symbols: self.graph.symbol_revision(),
7367 semantic,
7368 provider,
7369 deterministic_mode: self.config.deterministic_mode.clone(),
7370 budgets: self.evaluation_resource_budgets.clone(),
7371 }
7372 }
7373
7374 fn recalc_plan_key(&self) -> RecalcPlanKey {
7375 RecalcPlanKey {
7376 engine_token: Arc::clone(&self.recalc_plan_token),
7377 revisions: self.planning_revision_snapshot(),
7378 }
7379 }
7380
7381 fn plan_stale(reason: formualizer_common::PlanStaleReason) -> ExcelError {
7382 ExcelError::new(ExcelErrorKind::Value)
7383 .with_message(format!("recalculation plan is stale: {}", reason.as_str()))
7384 .with_extra(formualizer_common::ExcelErrorExtra::PlanStale { reason })
7385 }
7386
7387 fn validate_recalc_plan_key(&self, key: &RecalcPlanKey) -> Result<(), ExcelError> {
7388 use formualizer_common::PlanStaleReason;
7389
7390 if !Arc::ptr_eq(&key.engine_token, &self.recalc_plan_token) {
7391 return Err(Self::plan_stale(PlanStaleReason::Engine));
7392 }
7393
7394 let current = self.planning_revision_snapshot();
7395 let expected = &key.revisions;
7396 let stale = if expected.provider != current.provider {
7397 Some(PlanStaleReason::Provider)
7398 } else if expected.semantic != current.semantic {
7399 Some(PlanStaleReason::Semantic)
7400 } else if expected.budgets != current.budgets
7401 || expected.deterministic_mode != current.deterministic_mode
7402 {
7403 Some(PlanStaleReason::Budget)
7404 } else if expected.staged != current.staged {
7405 Some(PlanStaleReason::Staged)
7406 } else if expected.symbols != current.symbols {
7407 Some(PlanStaleReason::Symbols)
7408 } else if expected.graph_topology_revision != current.graph_topology_revision
7409 || expected.engine_topology_epoch != current.engine_topology_epoch
7410 {
7411 Some(PlanStaleReason::Graph)
7412 } else {
7413 None
7414 };
7415 stale.map_or(Ok(()), |reason| Err(Self::plan_stale(reason)))
7416 }
7417
7418 fn target_preparation_checkpoint(
7419 &mut self,
7420 deadline: Option<std::time::Instant>,
7421 work: u64,
7422 ) -> Result<(), ExcelError> {
7423 if self
7424 .active_cancel_flag
7425 .as_ref()
7426 .is_some_and(|cancel| cancel.is_cancelled())
7427 {
7428 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
7429 .with_message("target graph preparation cancelled"));
7430 }
7431 if deadline.is_some_and(|deadline| std::time::Instant::now() >= deadline) {
7432 return Err(crate::engine::ResourceLedgerError::Exhausted(
7433 formualizer_common::ResourceExhaustionDetail {
7434 reason: formualizer_common::ResourceExhaustionReason::Deadline,
7435 limit: 0,
7436 observed: 1,
7437 request_id: self
7438 .active_evaluation_resource_request
7439 .as_ref()
7440 .map(|stats| stats.request_id),
7441 },
7442 )
7443 .into_excel_error());
7444 }
7445 self.charge_bounded_work(work)
7446 }
7447
7448 fn opaque_reason_in_ast(
7449 &self,
7450 ast: &ASTNode,
7451 provider: &dyn crate::traits::FunctionProvider,
7452 ) -> Option<crate::engine::OpaqueReason> {
7453 match &ast.node_type {
7454 ASTNodeType::Function { name, args } => {
7455 let canonical = name.rsplit('.').next().unwrap_or(name).to_ascii_uppercase();
7456 if canonical == "INDIRECT" {
7457 return Some(crate::engine::OpaqueReason::RuntimeTextReference);
7458 }
7459 let Some(function) = provider.get_function_for_planning("", name) else {
7460 return Some(crate::engine::OpaqueReason::UnknownFunction);
7461 };
7462 let caps = function.caps();
7463 if caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7464 || caps.contains(FnCaps::RETURNS_REFERENCE)
7465 {
7466 return Some(crate::engine::OpaqueReason::DynamicReference);
7467 }
7468 args.iter()
7469 .find_map(|arg| self.opaque_reason_in_ast(arg, provider))
7470 }
7471 ASTNodeType::Call { .. } => Some(crate::engine::OpaqueReason::UnknownCustomFunction),
7472 ASTNodeType::UnaryOp { expr, .. } => self.opaque_reason_in_ast(expr, provider),
7473 ASTNodeType::BinaryOp { left, right, .. } => self
7474 .opaque_reason_in_ast(left, provider)
7475 .or_else(|| self.opaque_reason_in_ast(right, provider)),
7476 ASTNodeType::Array(rows) => rows
7477 .iter()
7478 .flat_map(|row| row.iter())
7479 .find_map(|item| self.opaque_reason_in_ast(item, provider)),
7480 ASTNodeType::Reference {
7481 reference:
7482 ReferenceType::Cell {
7483 sheet: Some(sheet), ..
7484 }
7485 | ReferenceType::Range {
7486 sheet: Some(sheet), ..
7487 },
7488 ..
7489 } if self.graph.sheet_id(sheet).is_none() => {
7490 Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding)
7491 }
7492 ASTNodeType::Reference {
7493 reference:
7494 ReferenceType::External(_)
7495 | ReferenceType::Cell3D { .. }
7496 | ReferenceType::Range3D { .. },
7497 ..
7498 } => Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding),
7499 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => None,
7500 }
7501 }
7502
7503 fn target_planning_snapshot(
7504 &mut self,
7505 ast: &ASTNode,
7506 planning_requests: &mut BTreeSet<(String, String, usize)>,
7507 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
7508 #[cfg(test)]
7509 if std::mem::take(&mut self.inject_target_semantic_stale_once_for_test) {
7510 return Err(Self::preparation_stale(
7511 formualizer_common::PreparationStaleReason::Semantic,
7512 "injected target semantic preparation movement",
7513 ));
7514 }
7515 #[cfg(test)]
7516 if let Some(hook) = self.before_target_planning_snapshot_hook.take() {
7517 hook();
7518 }
7519 let mut requests = Vec::new();
7520 Self::collect_planning_function_requests(ast, &mut requests);
7521 requests.sort();
7522 requests.dedup();
7523 planning_requests.extend(requests.iter().cloned());
7524 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
7525 &self.resolver,
7526 requests,
7527 )
7528 .map_err(|error| {
7529 ExcelError::new(ExcelErrorKind::Value)
7530 .with_message(format!("target planning snapshot unavailable: {error:?}"))
7531 })
7532 }
7533
7534 fn target_planning_snapshot_stale_reason(
7535 snapshot: &crate::function_registry::RegistryPlanningSnapshot,
7536 assumptions: &crate::engine::PreparationRevision,
7537 ) -> Option<formualizer_common::PreparationStaleReason> {
7538 if snapshot
7539 .provider_revision()
7540 .is_some_and(|revision| Some(revision) != assumptions.provider)
7541 {
7542 Some(formualizer_common::PreparationStaleReason::Provider)
7543 } else if snapshot.epoch() != assumptions.semantic
7544 && snapshot.semantic_changes_affect_requests_since(assumptions.semantic)
7545 {
7546 Some(formualizer_common::PreparationStaleReason::Semantic)
7547 } else {
7548 None
7549 }
7550 }
7551
7552 fn widen_target_preparation(
7553 policy: crate::engine::OpaquePreparePolicy,
7554 scope: &mut crate::engine::PrepareScope,
7555 reasons: &mut Vec<crate::engine::OpaqueReason>,
7556 reason: crate::engine::OpaqueReason,
7557 ) -> Result<bool, ExcelError> {
7558 if policy == crate::engine::OpaquePreparePolicy::Error {
7559 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7560 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7561 }
7562 if !reasons.contains(&reason) {
7563 reasons.push(reason);
7564 }
7565 if !matches!(scope, crate::engine::PrepareScope::Workbook) {
7566 *scope = crate::engine::PrepareScope::Workbook;
7567 Ok(true)
7568 } else {
7569 Ok(false)
7570 }
7571 }
7572
7573 fn widen_target_preparation_to_sheet(
7574 policy: crate::engine::OpaquePreparePolicy,
7575 scope: &mut crate::engine::PrepareScope,
7576 reasons: &mut Vec<crate::engine::OpaqueReason>,
7577 reason: crate::engine::OpaqueReason,
7578 sheet: &str,
7579 ) -> Result<bool, ExcelError> {
7580 if policy == crate::engine::OpaquePreparePolicy::Error {
7581 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7582 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7583 }
7584 if !reasons.contains(&reason) {
7585 reasons.push(reason);
7586 }
7587 match scope {
7588 crate::engine::PrepareScope::Exact => {
7589 *scope = crate::engine::PrepareScope::Sheets(vec![sheet.to_string()]);
7590 Ok(true)
7591 }
7592 crate::engine::PrepareScope::Sheets(sheets) => {
7593 if sheets.iter().any(|candidate| candidate == sheet) {
7594 Ok(false)
7595 } else {
7596 sheets.push(sheet.to_string());
7597 sheets.sort();
7598 Ok(true)
7599 }
7600 }
7601 crate::engine::PrepareScope::Workbook => Ok(false),
7602 }
7603 }
7604
7605 fn ast_has_proven_sheet_local_dynamic(
7606 ast: &ASTNode,
7607 provider: &dyn crate::traits::FunctionProvider,
7608 ) -> bool {
7609 fn classify(ast: &ASTNode, provider: &dyn crate::traits::FunctionProvider) -> (bool, bool) {
7610 match &ast.node_type {
7611 ASTNodeType::Function { name, args } => {
7612 let Some(function) = provider.get_function_for_planning("", name) else {
7613 return (false, false);
7614 };
7615 let caps = function.caps();
7616 let dynamic = caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7617 || caps.contains(FnCaps::RETURNS_REFERENCE);
7618 let canonical = name.rsplit('.').next().unwrap_or(name);
7619 if dynamic
7620 && !canonical.eq_ignore_ascii_case("OFFSET")
7621 && !canonical.eq_ignore_ascii_case("INDEX")
7622 {
7623 return (false, true);
7624 }
7625 let mut has_dynamic = dynamic;
7626 for arg in args {
7627 let (safe, child_dynamic) = classify(arg, provider);
7628 if !safe {
7629 return (false, has_dynamic || child_dynamic);
7630 }
7631 has_dynamic |= child_dynamic;
7632 }
7633 (true, has_dynamic)
7634 }
7635 ASTNodeType::UnaryOp { expr, .. } => classify(expr, provider),
7636 ASTNodeType::BinaryOp { left, right, .. } => {
7637 let (left_safe, left_dynamic) = classify(left, provider);
7638 let (right_safe, right_dynamic) = classify(right, provider);
7639 (left_safe && right_safe, left_dynamic || right_dynamic)
7640 }
7641 ASTNodeType::Array(rows) => {
7642 let mut has_dynamic = false;
7643 for item in rows.iter().flatten() {
7644 let (safe, child_dynamic) = classify(item, provider);
7645 if !safe {
7646 return (false, has_dynamic || child_dynamic);
7647 }
7648 has_dynamic |= child_dynamic;
7649 }
7650 (true, has_dynamic)
7651 }
7652 ASTNodeType::Reference {
7653 reference:
7654 ReferenceType::Cell { sheet: None, .. }
7655 | ReferenceType::Range { sheet: None, .. },
7656 ..
7657 }
7658 | ASTNodeType::Literal(_)
7659 | ASTNodeType::Omitted => (true, false),
7660 ASTNodeType::Call { .. } | ASTNodeType::Reference { .. } => (false, false),
7661 }
7662 }
7663
7664 let (safe, dynamic) = classify(ast, provider);
7665 safe && dynamic
7666 }
7667
7668 fn table_selection_region(
7669 &self,
7670 entry: &crate::engine::graph::TableEntry,
7671 selection: &crate::engine::TableSelection,
7672 ) -> Result<PreparationRegion, ExcelError> {
7673 let mut start_row = entry.range.start.coord.row() + 1;
7674 let mut end_row = entry.range.end.coord.row() + 1;
7675 let mut start_col = entry.range.start.coord.col() + 1;
7676 let mut end_col = entry.range.end.coord.col() + 1;
7677 match selection {
7678 crate::engine::TableSelection::Whole => {}
7679 crate::engine::TableSelection::Headers => {
7680 if !entry.header_row {
7681 return Err(ExcelError::new(ExcelErrorKind::Value)
7682 .with_message(format!("table {} has no header row", entry.name)));
7683 }
7684 end_row = start_row;
7685 }
7686 crate::engine::TableSelection::Data => {
7687 if entry.header_row {
7688 start_row = start_row.saturating_add(1);
7689 }
7690 if entry.totals_row {
7691 end_row = end_row.saturating_sub(1);
7692 }
7693 }
7694 crate::engine::TableSelection::Totals => {
7695 if !entry.totals_row {
7696 return Err(ExcelError::new(ExcelErrorKind::Value)
7697 .with_message(format!("table {} has no totals row", entry.name)));
7698 }
7699 start_row = end_row;
7700 }
7701 crate::engine::TableSelection::Column(column) => {
7702 let index = entry.col_index(column).ok_or_else(|| {
7703 ExcelError::new(ExcelErrorKind::Name)
7704 .with_message(format!("unknown table column: {column}"))
7705 })?;
7706 start_col = start_col.saturating_add(index as u32);
7707 end_col = start_col;
7708 }
7709 crate::engine::TableSelection::Columns { start, end } => {
7710 let first = entry.col_index(start).ok_or_else(|| {
7711 ExcelError::new(ExcelErrorKind::Name)
7712 .with_message(format!("unknown table column: {start}"))
7713 })?;
7714 let last = entry.col_index(end).ok_or_else(|| {
7715 ExcelError::new(ExcelErrorKind::Name)
7716 .with_message(format!("unknown table column: {end}"))
7717 })?;
7718 if first > last {
7719 return Err(ExcelError::new(ExcelErrorKind::Value)
7720 .with_message("table column selection is reversed"));
7721 }
7722 start_col = start_col.saturating_add(first as u32);
7723 end_col = entry
7724 .range
7725 .start
7726 .coord
7727 .col()
7728 .saturating_add(last as u32)
7729 .saturating_add(1);
7730 }
7731 }
7732 if start_row > end_row && matches!(selection, crate::engine::TableSelection::Data) {
7733 start_row = entry.range.start.coord.row() + 1;
7734 end_row = start_row;
7735 }
7736 if start_row > end_row || start_col > end_col {
7737 return Err(
7738 ExcelError::new(ExcelErrorKind::Value).with_message("table selection is empty")
7739 );
7740 }
7741 Ok(PreparationRegion {
7742 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
7743 sheet_id: entry.sheet_id(),
7744 start_row,
7745 start_col,
7746 end_row,
7747 end_col,
7748 })
7749 }
7750
7751 #[cfg(test)]
7752 pub(crate) fn reset_target_root_dedup_probes_for_test() {
7753 TARGET_ROOT_DEDUP_PROBES.with(|probes| probes.set(0));
7754 }
7755
7756 #[cfg(test)]
7757 pub(crate) fn target_root_dedup_probes_for_test() -> usize {
7758 TARGET_ROOT_DEDUP_PROBES.with(std::cell::Cell::get)
7759 }
7760
7761 pub(crate) fn resolve_target_producers(
7762 &mut self,
7763 targets: &[crate::engine::EvaluationTarget],
7764 ) -> Result<Vec<crate::engine::target_preparation::TargetProducer>, ExcelError> {
7765 use crate::engine::target_preparation::TargetProducer;
7766
7767 let request_id = self
7768 .active_evaluation_resource_request
7769 .as_ref()
7770 .map(|request| request.request_id);
7771 let mut roots = OrderedTargetProducers::with_capacity(targets.len())
7772 .map_err(|_| target_root_allocation_error(targets.len(), request_id))?;
7773 let resolve_region = |engine: &mut Self,
7774 region: Region,
7775 value_only: Option<CellRef>,
7776 roots: &mut OrderedTargetProducers|
7777 -> Result<(), ExcelError> {
7778 let before = roots.len();
7779 for anchor in engine.graph.spill_anchors_in_region(
7780 region.sheet_id(),
7781 region.axis_ranges().0.query_bounds().0,
7782 region.axis_ranges().1.query_bounds().0,
7783 region.axis_ranges().0.query_bounds().1,
7784 region.axis_ranges().1.query_bounds().1,
7785 ) {
7786 roots
7787 .push(TargetProducer::Legacy(anchor))
7788 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7789 }
7790 for vertex in engine.graph.vertices_in_region(
7791 region.sheet_id(),
7792 region.axis_ranges().0.query_bounds().0,
7793 region.axis_ranges().0.query_bounds().1,
7794 region.axis_ranges().1.query_bounds().0,
7795 region.axis_ranges().1.query_bounds().1,
7796 ) {
7797 let vertex = engine
7798 .graph
7799 .get_cell_ref(vertex)
7800 .and_then(|cell| engine.graph.spill_registry_anchor_for_cell(cell))
7801 .unwrap_or(vertex);
7802 if matches!(
7807 engine.graph.get_vertex_kind(vertex),
7808 VertexKind::FormulaScalar | VertexKind::FormulaArray
7809 ) {
7810 roots
7811 .push(TargetProducer::Legacy(vertex))
7812 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7813 }
7814 }
7815 if roots.len() == before
7816 && let Some(cell) = value_only
7817 {
7818 roots
7819 .push(TargetProducer::ValueOnly(cell))
7820 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7821 }
7822 Ok(())
7823 };
7824
7825 for target in targets {
7826 match target {
7827 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
7828 if *row == 0 || *col == 0 {
7829 return Err(ExcelError::new(ExcelErrorKind::Ref)
7830 .with_message("target cell coordinates are one-based"));
7831 }
7832 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
7833 ExcelError::new(ExcelErrorKind::Ref)
7834 .with_message(format!("target sheet not found: {sheet}"))
7835 })?;
7836 let cell = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
7837 resolve_region(
7838 self,
7839 Region::point(sheet_id, *row - 1, *col - 1),
7840 Some(cell),
7841 &mut roots,
7842 )?;
7843 }
7844 crate::engine::EvaluationTarget::Range(range) => {
7845 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
7846 ExcelError::new(ExcelErrorKind::Ref)
7847 .with_message(format!("target sheet not found: {}", range.sheet))
7848 })?;
7849 resolve_region(
7850 self,
7851 Region::rect(
7852 sheet_id,
7853 range.start_row - 1,
7854 range.end_row - 1,
7855 range.start_col - 1,
7856 range.end_col - 1,
7857 ),
7858 None,
7859 &mut roots,
7860 )?;
7861 }
7862 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
7863 let scope = self.name_query_scope(scope_sheet.as_deref())?;
7864 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, scope) {
7865 roots
7866 .push(TargetProducer::Symbol(entry.vertex))
7867 .map_err(|_| {
7868 target_root_allocation_error(roots.len() + 1, request_id)
7869 })?;
7870 }
7871 }
7872 crate::engine::EvaluationTarget::Table { name, .. } => {
7873 if let Some(entry) = self.graph.resolve_table_entry(name) {
7874 roots
7875 .push(TargetProducer::Symbol(entry.vertex))
7876 .map_err(|_| {
7877 target_root_allocation_error(roots.len() + 1, request_id)
7878 })?;
7879 }
7880 }
7881 }
7882 }
7883 Ok(roots.into_vec())
7884 }
7885
7886 pub fn prepare_graph_for_targets(
7890 &mut self,
7891 targets: &[crate::engine::EvaluationTarget],
7892 options: crate::engine::TargetEvalOptions<'_>,
7893 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7894 let previous_budgets = self.evaluation_resource_budgets.clone();
7895 let diagnostics_len = self.formula_parse_diagnostics.len();
7896 let previous_report = self.last_formula_ingest_report.clone();
7897 if let Some(budgets) = options.budgets {
7898 self.evaluation_resource_budgets = budgets.clone();
7899 }
7900 let previous_graph_budget_override = self
7901 .graph
7902 .set_admission_budget_override(Some(self.evaluation_resource_budgets.clone()));
7903 let previous_cancel = self.active_cancel_flag.take();
7907 self.active_cancel_flag = options.cancel.clone();
7908 let result = self.observe_evaluation_resource_request(
7909 EvaluationRequestKind::TargetPreparation,
7910 |engine| engine.prepare_graph_for_targets_unobserved(targets, &options),
7911 );
7912 self.active_cancel_flag = previous_cancel;
7913 self.graph
7914 .set_admission_budget_override(previous_graph_budget_override);
7915 self.evaluation_resource_budgets = previous_budgets;
7916 if result.is_err() {
7917 self.formula_parse_diagnostics.truncate(diagnostics_len);
7918 self.last_formula_ingest_report = previous_report;
7919 }
7920 result
7921 }
7922
7923 fn prepare_graph_for_targets_unobserved(
7924 &mut self,
7925 targets: &[crate::engine::EvaluationTarget],
7926 options: &crate::engine::TargetEvalOptions<'_>,
7927 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7928 let scratch_checkpoint = self
7929 .active_resource_ledger
7930 .as_ref()
7931 .map_or(0, crate::engine::ResourceLedger::scratch_checkpoint);
7932 let result = self.prepare_graph_for_targets_transaction(targets, options);
7933 let release = self
7934 .active_resource_ledger
7935 .as_mut()
7936 .map_or(Ok(()), |ledger| {
7937 ledger.release_scratch_to(scratch_checkpoint)
7938 });
7939 match (result, release) {
7940 (result, Ok(())) => result,
7941 (Ok(_), Err(error)) | (Err(_), Err(error)) => Err(error.into_excel_error()),
7942 }
7943 }
7944
7945 fn prepare_graph_for_targets_transaction(
7946 &mut self,
7947 targets: &[crate::engine::EvaluationTarget],
7948 options: &crate::engine::TargetEvalOptions<'_>,
7949 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7950 use crate::engine::{
7951 OpaqueReason, PreparationOutcome, PrepareScope, PreparedTargetGraphReport,
7952 TableSelection,
7953 };
7954
7955 self.target_preparation_checkpoint(options.deadline, 0)?;
7956 self.observe_function_semantic_epoch()?;
7957 let assumptions = self.preparation_revisions();
7958 let ledger_at_start = self
7959 .active_resource_ledger
7960 .as_ref()
7961 .map(|ledger| ledger.snapshot());
7962 let diagnostics_len = self.formula_parse_diagnostics.len();
7963 let report_len = self.last_formula_ingest_report.clone();
7964 let request_id = options.request_id.or_else(|| {
7965 self.active_evaluation_resource_request
7966 .as_ref()
7967 .map(|stats| stats.request_id)
7968 });
7969
7970 let mut scope = PrepareScope::Exact;
7971 let mut reasons = Vec::new();
7972 let mut regions = VecDeque::new();
7973 let mut deferred_shared_regions = VecDeque::new();
7974 let mut normalized = Vec::with_capacity(targets.len());
7975 let mut symbol_vertices = VecDeque::new();
7976 for target in targets {
7977 self.target_preparation_checkpoint(options.deadline, 1)?;
7978 match target {
7979 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
7980 if *row == 0 || *col == 0 {
7981 return Err(ExcelError::new(ExcelErrorKind::Ref)
7982 .with_message("target cell coordinates are one-based"));
7983 }
7984 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
7985 ExcelError::new(ExcelErrorKind::Ref)
7986 .with_message(format!("target sheet not found: {sheet}"))
7987 })?;
7988 regions.push_back(PreparationRegion {
7989 sheet: sheet.clone(),
7990 sheet_id,
7991 start_row: *row,
7992 start_col: *col,
7993 end_row: *row,
7994 end_col: *col,
7995 });
7996 normalized.push(target.clone());
7997 }
7998 crate::engine::EvaluationTarget::Range(range) => {
7999 if range.start_row == 0
8000 || range.start_col == 0
8001 || range.end_row < range.start_row
8002 || range.end_col < range.start_col
8003 {
8004 return Err(ExcelError::new(ExcelErrorKind::Ref)
8005 .with_message("invalid target range"));
8006 }
8007 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
8008 ExcelError::new(ExcelErrorKind::Ref)
8009 .with_message(format!("target sheet not found: {}", range.sheet))
8010 })?;
8011 regions.push_back(PreparationRegion {
8012 sheet: range.sheet.clone(),
8013 sheet_id,
8014 start_row: range.start_row,
8015 start_col: range.start_col,
8016 end_row: range.end_row,
8017 end_col: range.end_col,
8018 });
8019 normalized.push(target.clone());
8020 }
8021 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
8022 let name_scope = self.name_query_scope(scope_sheet.as_deref())?;
8023 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, name_scope) {
8024 symbol_vertices.push_back(entry.vertex);
8025 } else {
8026 Self::widen_target_preparation(
8027 options.opaque_policy,
8028 &mut scope,
8029 &mut reasons,
8030 OpaqueReason::UnresolvedName,
8031 )?;
8032 }
8033 normalized.push(target.clone());
8034 }
8035 crate::engine::EvaluationTarget::Table { name, selection } => {
8036 if let Some(entry) = self.graph.resolve_table_entry(name) {
8037 let region = self.table_selection_region(entry, selection)?;
8038 symbol_vertices.push_back(entry.vertex);
8039 regions.push_back(region);
8040 } else {
8041 Self::widen_target_preparation(
8042 options.opaque_policy,
8043 &mut scope,
8044 &mut reasons,
8045 OpaqueReason::UnresolvedTable,
8046 )?;
8047 }
8048 normalized.push(target.clone());
8049 }
8050 }
8051 }
8052
8053 let mut visited_regions = FxHashSet::default();
8054 let mut visited_vertices = FxHashSet::default();
8055 let mut selected = FxHashSet::default();
8056 let mut prepared = Vec::new();
8057 let mut pending_diagnostics = Vec::new();
8058 let mut planning_requests = BTreeSet::new();
8059 let mut selected_cells = Vec::new();
8060 let mut workbook_seeded = false;
8061 let mut sheet_scope_seeded = BTreeSet::new();
8062 let mut indexed_query_sheets = FxHashSet::default();
8063 let mut discovery_scratch_reserved = 0u64;
8064 let mut package_encountered = false;
8065 let mut selected_package_sheets = FxHashSet::default();
8066 let mut selected_package_points: BTreeMap<String, BTreeSet<(u32, u32)>> = BTreeMap::new();
8067 let mut prepared_packages: Vec<PreparedTargetSourcePackage> = Vec::new();
8068 let authoritative_with_ordinary = false;
8069 let has_unknown_package_sheet = self
8070 .staged_formula_index
8071 .package_sheets()
8072 .any(|sheet| self.graph.sheet_id(sheet).is_none());
8073
8074 loop {
8075 if let PrepareScope::Sheets(sheets) = &scope {
8076 for sheet in sheets.clone() {
8077 if !sheet_scope_seeded.insert(sheet.clone()) {
8078 continue;
8079 }
8080 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8081 package_encountered = true;
8082 Self::widen_target_preparation(
8083 options.opaque_policy,
8084 &mut scope,
8085 &mut reasons,
8086 OpaqueReason::UnsupportedSourceSemantics,
8087 )?;
8088 continue;
8089 };
8090 for lease in self.staged_formula_index.leases_for_sheet(&sheet) {
8091 self.target_preparation_checkpoint(options.deadline, 1)?;
8092 regions.push_back(PreparationRegion {
8093 sheet: sheet.clone(),
8094 sheet_id,
8095 start_row: lease.row,
8096 start_col: lease.col,
8097 end_row: lease.row,
8098 end_col: lease.col,
8099 });
8100 }
8101 if self
8102 .staged_formula_index
8103 .package_lease_for_sheet(&sheet)
8104 .is_some()
8105 {
8106 regions.push_back(PreparationRegion {
8107 sheet: sheet.clone(),
8108 sheet_id,
8109 start_row: 1,
8110 start_col: 1,
8111 end_row: self.workbook_load_limits.max_sheet_rows,
8112 end_col: self.workbook_load_limits.max_sheet_cols,
8113 });
8114 }
8115 }
8116 }
8117 if matches!(scope, PrepareScope::Workbook) && !workbook_seeded {
8118 workbook_seeded = true;
8119 for (sheet, lease) in self.staged_formula_index.all_leases() {
8120 self.target_preparation_checkpoint(options.deadline, 1)?;
8121 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8122 package_encountered = true;
8123 Self::widen_target_preparation(
8124 options.opaque_policy,
8125 &mut scope,
8126 &mut reasons,
8127 OpaqueReason::UnsupportedSourceSemantics,
8128 )?;
8129 continue;
8130 };
8131 regions.push_back(PreparationRegion {
8132 sheet,
8133 sheet_id,
8134 start_row: lease.row,
8135 start_col: lease.col,
8136 end_row: lease.row,
8137 end_col: lease.col,
8138 });
8139 }
8140 let package_sheets = self
8141 .staged_formula_index
8142 .package_sheets()
8143 .map(str::to_string)
8144 .collect::<Vec<_>>();
8145 for sheet in package_sheets {
8146 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8147 package_encountered = true;
8148 Self::widen_target_preparation(
8149 options.opaque_policy,
8150 &mut scope,
8151 &mut reasons,
8152 OpaqueReason::UnsupportedSourceSemantics,
8153 )?;
8154 continue;
8155 };
8156 regions.push_back(PreparationRegion {
8157 sheet,
8158 sheet_id,
8159 start_row: 1,
8160 start_col: 1,
8161 end_row: self.workbook_load_limits.max_sheet_rows,
8162 end_col: self.workbook_load_limits.max_sheet_cols,
8163 });
8164 }
8165 }
8166
8167 let allow_partial_shared = regions.is_empty() && symbol_vertices.is_empty();
8170 let next_region = if allow_partial_shared {
8171 deferred_shared_regions.pop_front()
8172 } else {
8173 regions.pop_front()
8174 };
8175 let Some(region) = next_region else {
8176 if let Some(vertex) = symbol_vertices.pop_front() {
8177 self.target_preparation_checkpoint(options.deadline, 1)?;
8178 if !visited_vertices.insert(vertex) || !self.graph.vertex_exists(vertex) {
8179 continue;
8180 }
8181 let vertex_is_dynamic = self.graph.is_dynamic(vertex);
8182 if let Some(ast) = self.graph.get_formula(vertex) {
8183 let snapshot =
8184 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8185 if let Some(reason) =
8186 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8187 {
8188 return Err(Self::preparation_stale(
8189 reason,
8190 "target planning snapshot became stale during discovery",
8191 ));
8192 }
8193 let opaque = self.opaque_reason_in_ast(&ast, &snapshot);
8194 if let Some(reason) =
8195 opaque.or(vertex_is_dynamic.then_some(OpaqueReason::DynamicReference))
8196 {
8197 if reason == OpaqueReason::DynamicReference
8198 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
8199 {
8200 let sheet = self.graph.get_vertex_sheet_id(vertex);
8201 let sheet = self.graph.sheet_name(sheet).to_string();
8202 Self::widen_target_preparation_to_sheet(
8203 options.opaque_policy,
8204 &mut scope,
8205 &mut reasons,
8206 reason,
8207 &sheet,
8208 )?;
8209 } else {
8210 Self::widen_target_preparation(
8211 options.opaque_policy,
8212 &mut scope,
8213 &mut reasons,
8214 reason,
8215 )?;
8216 }
8217 }
8218 } else if vertex_is_dynamic {
8219 Self::widen_target_preparation(
8220 options.opaque_policy,
8221 &mut scope,
8222 &mut reasons,
8223 OpaqueReason::DynamicReference,
8224 )?;
8225 }
8226 if let Some(anchor) = self
8227 .graph
8228 .get_cell_ref(vertex)
8229 .and_then(|cell| self.graph.spill_registry_anchor_for_cell(cell))
8230 {
8231 symbol_vertices.push_back(anchor);
8232 }
8233 if let Some(cell) = self.graph.get_cell_ref(vertex) {
8234 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
8235 regions.push_back(PreparationRegion {
8236 sheet,
8237 sheet_id: cell.sheet_id,
8238 start_row: cell.coord.row() + 1,
8239 start_col: cell.coord.col() + 1,
8240 end_row: cell.coord.row() + 1,
8241 end_col: cell.coord.col() + 1,
8242 });
8243 }
8244 match self.graph.authority_vertex_precedents(vertex) {
8248 Some(precedents) => {
8249 for (sheet_id, rect) in precedents {
8250 self.target_preparation_checkpoint(options.deadline, 1)?;
8251 if sheet_id == crate::engine::authority::geom::SYMBOL_SHEET {
8252 for slot in rect.r0..=rect.r1 {
8253 if let Some(symbol) =
8254 self.graph.authority_host().symbols().vertex(slot)
8255 {
8256 symbol_vertices.push_back(symbol);
8257 }
8258 }
8259 continue;
8260 }
8261 let sheet = self.graph.sheet_name(sheet_id).to_string();
8262 regions.push_back(PreparationRegion {
8263 sheet,
8264 sheet_id,
8265 start_row: rect.r0 + 1,
8266 start_col: rect.c0 + 1,
8267 end_row: (rect.r1 + 1)
8268 .min(self.workbook_load_limits.max_sheet_rows),
8269 end_col: (rect.c1 + 1)
8270 .min(self.workbook_load_limits.max_sheet_cols),
8271 });
8272 }
8273 }
8274 None => {
8276 if Self::widen_target_preparation(
8277 options.opaque_policy,
8278 &mut scope,
8279 &mut reasons,
8280 OpaqueReason::UnresolvedCrossSheetBinding,
8281 )? {
8282 continue;
8283 }
8284 }
8285 }
8286 if let Some(name) = self.graph.named_range_by_vertex(vertex).cloned() {
8287 match &name.definition {
8288 NamedDefinition::Cell(cell) => regions.push_back(PreparationRegion {
8289 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
8290 sheet_id: cell.sheet_id,
8291 start_row: cell.coord.row() + 1,
8292 start_col: cell.coord.col() + 1,
8293 end_row: cell.coord.row() + 1,
8294 end_col: cell.coord.col() + 1,
8295 }),
8296 NamedDefinition::Range(range) => regions.push_back(PreparationRegion {
8297 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
8298 sheet_id: range.start.sheet_id,
8299 start_row: range.start.coord.row() + 1,
8300 start_col: range.start.coord.col() + 1,
8301 end_row: range.end.coord.row() + 1,
8302 end_col: range.end.coord.col() + 1,
8303 }),
8304 NamedDefinition::Formula {
8305 ast,
8306 dependencies,
8307 range_deps,
8308 } => {
8309 let snapshot =
8310 self.target_planning_snapshot(ast, &mut planning_requests)?;
8311 if let Some(reason) = Self::target_planning_snapshot_stale_reason(
8312 &snapshot,
8313 &assumptions,
8314 ) {
8315 return Err(Self::preparation_stale(
8316 reason,
8317 "target planning snapshot became stale during discovery",
8318 ));
8319 }
8320 if let Some(reason) = self.opaque_reason_in_ast(ast, &snapshot) {
8321 Self::widen_target_preparation(
8322 options.opaque_policy,
8323 &mut scope,
8324 &mut reasons,
8325 reason,
8326 )?;
8327 }
8328 for dependency in dependencies {
8329 self.target_preparation_checkpoint(options.deadline, 1)?;
8330 symbol_vertices.push_back(*dependency);
8331 }
8332 for range in range_deps {
8333 self.target_preparation_checkpoint(options.deadline, 1)?;
8334 let context_sheet = self.graph.get_vertex_sheet_id(vertex);
8342 let Ok(sheet_id) =
8343 self.resolve_sheet_locator(&range.sheet, context_sheet)
8344 else {
8345 if Self::widen_target_preparation(
8346 options.opaque_policy,
8347 &mut scope,
8348 &mut reasons,
8349 OpaqueReason::UnresolvedCrossSheetBinding,
8350 )? {
8351 break;
8352 }
8353 continue;
8354 };
8355 regions.push_back(PreparationRegion {
8356 sheet: self.graph.sheet_name(sheet_id).to_string(),
8357 sheet_id,
8358 start_row: range
8359 .start_row
8360 .map_or(1, |bound| bound.index + 1),
8361 start_col: range
8362 .start_col
8363 .map_or(1, |bound| bound.index + 1),
8364 end_row: range.end_row.map_or(
8365 self.workbook_load_limits.max_sheet_rows,
8366 |bound| bound.index + 1,
8367 ),
8368 end_col: range.end_col.map_or(
8369 self.workbook_load_limits.max_sheet_cols,
8370 |bound| bound.index + 1,
8371 ),
8372 });
8373 }
8374 }
8375 NamedDefinition::Literal(_) => {}
8376 }
8377 }
8378 if let Some(table) = self.graph.table_by_vertex(vertex) {
8379 regions
8380 .push_back(self.table_selection_region(table, &TableSelection::Whole)?);
8381 }
8382 continue;
8383 }
8384 break;
8385 };
8386
8387 self.target_preparation_checkpoint(options.deadline, 1)?;
8388 if !visited_regions.insert(region.clone()) && !allow_partial_shared {
8389 continue;
8390 }
8391 if let PrepareScope::Sheets(sheets) = &mut scope
8392 && !sheets.iter().any(|sheet| sheet == ®ion.sheet)
8393 {
8394 sheets.push(region.sheet.clone());
8395 sheets.sort();
8396 }
8397 let package_match = self.staged_formula_index.package_for_region(
8398 ®ion.sheet,
8399 region.start_row,
8400 region.start_col,
8401 region.end_row,
8402 region.end_col,
8403 );
8404 let package_lease = match package_match {
8405 Some(Ok(lease)) => Some(lease),
8406 Some(Err(()))
8407 if region.start_row == 1
8408 && region.start_col == 1
8409 && region.end_row == self.workbook_load_limits.max_sheet_rows
8410 && region.end_col == self.workbook_load_limits.max_sheet_cols =>
8411 {
8412 self.staged_formula_index
8413 .package_lease_for_sheet(®ion.sheet)
8414 }
8415 Some(Err(())) => {
8416 Self::widen_target_preparation(
8417 options.opaque_policy,
8418 &mut scope,
8419 &mut reasons,
8420 OpaqueReason::DeferredSourcePackage,
8421 )?;
8422 None
8423 }
8424 None => None,
8425 };
8426 let compatibility_before_package_replay = package_lease.is_some()
8427 && (authoritative_with_ordinary || has_unknown_package_sheet);
8428 let package_lease = if compatibility_before_package_replay {
8429 package_encountered = true;
8430 Self::widen_target_preparation(
8431 options.opaque_policy,
8432 &mut scope,
8433 &mut reasons,
8434 OpaqueReason::UnsupportedSourceSemantics,
8435 )?;
8436 None
8437 } else {
8438 package_lease
8439 };
8440 if let Some(package_lease) = package_lease
8441 && !selected_package_sheets.contains(®ion.sheet)
8442 {
8443 self.target_preparation_checkpoint(options.deadline, 1)?;
8444 let mut points = self.staged_formula_index.package_points_in_region(
8445 ®ion.sheet,
8446 region.start_row,
8447 region.start_col,
8448 region.end_row,
8449 region.end_col,
8450 );
8451 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8452 points.retain(|point| !selected.contains(point));
8453 }
8454
8455 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8456 points.retain(|point| !selected.contains(point));
8457 }
8458 let permit_partial = allow_partial_shared
8459 || (points.len() == 1
8460 && regions.is_empty()
8461 && symbol_vertices.is_empty()
8462 && deferred_shared_regions.is_empty());
8463 let partial = self.prepare_target_exact_source_selection(
8464 ®ion.sheet,
8465 package_lease,
8466 points,
8467 selected_package_points
8468 .get(®ion.sheet)
8469 .unwrap_or(&BTreeSet::new()),
8470 permit_partial,
8471 options.deadline,
8472 &mut discovery_scratch_reserved,
8473 )?;
8474 let mut package = if let Some(mut package) = partial {
8475 if package.deferred_shared {
8476 deferred_shared_regions.push_back(region.clone());
8477 }
8478 if !package.direct_domains.is_empty() {
8482 for prior in prepared_packages
8483 .iter_mut()
8484 .filter(|prior| prior.sheet == region.sheet)
8485 {
8486 prior
8487 .replay_records
8488 .retain(|record| !package.direct_contains(record.row, record.col));
8489 prior
8490 .legacy
8491 .retain(|(row, col, _, _)| !package.direct_contains(*row, *col));
8492 if let Some(points) = prior.selected_points.as_mut() {
8493 points.retain(|&(row, col)| !package.direct_contains(row, col));
8494 }
8495 }
8496 }
8497 if selected_package_points.contains_key(®ion.sheet) {
8498 package.source_report = Default::default();
8499 }
8500 let points = package.selected_points.as_ref().unwrap();
8501 if !points.is_empty() {
8502 selected_package_points
8503 .entry(region.sheet.clone())
8504 .or_default()
8505 .extend(points.iter().copied());
8506 }
8507 package
8508 } else {
8509 selected_package_sheets.insert(region.sheet.clone());
8510 self.prepare_target_source_package(
8511 ®ion.sheet,
8512 package_lease,
8513 options.deadline,
8514 )?
8515 };
8516 if package
8517 .selected_points
8518 .as_ref()
8519 .is_none_or(|points| !points.is_empty())
8520 {
8521 let mut final_fallback = BTreeMap::new();
8522 for record in package.fallback_records() {
8523 final_fallback.insert((record.row, record.col), record.clone());
8524 }
8525 let batch = self.formula_batch_from_exact_replay(
8526 ®ion.sheet,
8527 final_fallback.into_values(),
8528 )?;
8529 for record in batch.formulas {
8530 self.target_preparation_checkpoint(options.deadline, 1)?;
8531 let ast = self
8532 .graph
8533 .data_store()
8534 .retrieve_ast(record.ast_id, self.graph.sheet_reg())
8535 .ok_or_else(|| {
8536 ExcelError::new(ExcelErrorKind::Value)
8537 .with_message("target fallback AST is unavailable")
8538 })?;
8539 let snapshot =
8540 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8541 if let Some(reason) =
8542 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8543 {
8544 return Err(Self::preparation_stale(
8545 reason,
8546 "target fallback planning snapshot became stale during discovery",
8547 ));
8548 }
8549 let proven_sheet_local_dynamic =
8550 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
8551 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
8552 if reason == OpaqueReason::DynamicReference
8553 && proven_sheet_local_dynamic
8554 {
8555 Self::widen_target_preparation_to_sheet(
8556 options.opaque_policy,
8557 &mut scope,
8558 &mut reasons,
8559 reason,
8560 ®ion.sheet,
8561 )?;
8562 } else {
8563 Self::widen_target_preparation(
8564 options.opaque_policy,
8565 &mut scope,
8566 &mut reasons,
8567 reason,
8568 )?;
8569 }
8570 }
8571 let placement = CellRef::new(
8572 package.sheet_id,
8573 Coord::from_excel(record.row, record.col, true, true),
8574 );
8575 let ingested = self
8576 .graph
8577 .ingest_pipeline(&snapshot)
8578 .enable_function_semantics()
8579 .ingest_formula(
8580 FormulaAstInput::RawArena(record.ast_id),
8581 placement,
8582 record.formula_text,
8583 )?;
8584 if ingested.dep_plan.dynamic {
8585 if proven_sheet_local_dynamic {
8586 Self::widen_target_preparation_to_sheet(
8587 options.opaque_policy,
8588 &mut scope,
8589 &mut reasons,
8590 OpaqueReason::DynamicReference,
8591 ®ion.sheet,
8592 )?;
8593 } else {
8594 Self::widen_target_preparation(
8595 options.opaque_policy,
8596 &mut scope,
8597 &mut reasons,
8598 OpaqueReason::DynamicReference,
8599 )?;
8600 }
8601 }
8602 for dep in &ingested.dep_plan.direct_cell_deps {
8603 self.target_preparation_checkpoint(options.deadline, 1)?;
8604 regions.push_back(PreparationRegion {
8605 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
8606 sheet_id: dep.sheet_id,
8607 start_row: dep.coord.row().saturating_add(1),
8608 start_col: dep.coord.col().saturating_add(1),
8609 end_row: dep.coord.row().saturating_add(1),
8610 end_col: dep.coord.col().saturating_add(1),
8611 });
8612 }
8613 for range in &ingested.dep_plan.range_deps {
8614 self.target_preparation_checkpoint(options.deadline, 1)?;
8615 let Ok(dependency_sheet) =
8618 self.resolve_sheet_locator(&range.sheet, package.sheet_id)
8619 else {
8620 Self::widen_target_preparation(
8621 options.opaque_policy,
8622 &mut scope,
8623 &mut reasons,
8624 OpaqueReason::UnresolvedCrossSheetBinding,
8625 )?;
8626 continue;
8627 };
8628 regions.push_back(PreparationRegion {
8629 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
8630 sheet_id: dependency_sheet,
8631 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
8632 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
8633 end_row: range
8634 .end_row
8635 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
8636 bound.index + 1
8637 }),
8638 end_col: range
8639 .end_col
8640 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
8641 bound.index + 1
8642 }),
8643 });
8644 }
8645 for name in ingested
8646 .dep_plan
8647 .resolved_named_refs
8648 .iter()
8649 .chain(&ingested.dep_plan.named_refs)
8650 {
8651 self.target_preparation_checkpoint(options.deadline, 1)?;
8652 if let Some(entry) =
8653 self.graph.resolve_name_entry(name, package.sheet_id)
8654 {
8655 symbol_vertices.push_back(entry.vertex);
8656 } else if self.graph.resolve_source_scalar_entry(name).is_none()
8657 && self.graph.resolve_source_table_entry(name).is_none()
8658 {
8659 Self::widen_target_preparation(
8660 options.opaque_policy,
8661 &mut scope,
8662 &mut reasons,
8663 OpaqueReason::UnresolvedName,
8664 )?;
8665 }
8666 }
8667 for table in &ingested.dep_plan.table_refs {
8668 self.target_preparation_checkpoint(options.deadline, 1)?;
8669 if let Some(entry) = self.graph.resolve_table_entry(table) {
8670 symbol_vertices.push_back(entry.vertex);
8671 } else if self.graph.resolve_source_table_entry(table).is_none() {
8672 Self::widen_target_preparation(
8673 options.opaque_policy,
8674 &mut scope,
8675 &mut reasons,
8676 OpaqueReason::UnresolvedTable,
8677 )?;
8678 }
8679 }
8680 package.legacy.push((
8681 record.row,
8682 record.col,
8683 ingested.ast_id,
8684 ingested.dep_plan,
8685 ));
8686 }
8687 prepared_packages.push(package);
8688 }
8689 }
8690 let leases = self.staged_formula_index.leases_in_region(
8691 ®ion.sheet,
8692 region.start_row,
8693 region.start_col,
8694 region.end_row,
8695 region.end_col,
8696 );
8697 for lease in leases {
8698 self.target_preparation_checkpoint(options.deadline, 1)?;
8699 let sheet_id = self.graph.sheet_id(®ion.sheet).ok_or_else(|| {
8700 ExcelError::new(ExcelErrorKind::Ref)
8701 .with_message(format!("staged formula sheet not found: {}", region.sheet))
8702 })?;
8703 let key = (region.sheet.clone(), lease.row, lease.col, lease.generation);
8704 if !selected.insert(key) {
8705 continue;
8706 }
8707 let text = self
8708 .staged_formulas
8709 .get(®ion.sheet)
8710 .and_then(|sheet| sheet.get_ordinary(lease.row, lease.col))
8711 .ok_or_else(|| {
8712 ExcelError::new(ExcelErrorKind::Value)
8713 .with_message("staged formula index is stale")
8714 })?
8715 .to_string();
8716 let formula = if text.starts_with('=') {
8717 text.clone()
8718 } else {
8719 format!("={text}")
8720 };
8721 self.target_preparation_checkpoint(options.deadline, 1)?;
8722 let ast = match formualizer_parse::parser::parse(&formula) {
8723 Ok(ast) => ast,
8724 Err(error) => {
8725 if self.config.formula_parse_policy == FormulaParsePolicy::Strict {
8726 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
8727 format!(
8728 "Formula parse error at {}!{}{}: {error}",
8729 region.sheet,
8730 col_letters_from_1based(lease.col)
8731 .unwrap_or_else(|_| "?".to_string()),
8732 lease.row
8733 ),
8734 ));
8735 }
8736 pending_diagnostics.push(FormulaParseDiagnostic {
8737 sheet: region.sheet.clone(),
8738 row: lease.row,
8739 col: lease.col,
8740 formula: formula.clone(),
8741 message: error.to_string(),
8742 policy: self.config.formula_parse_policy,
8743 });
8744 match self.config.formula_parse_policy {
8745 FormulaParsePolicy::KeepCachedValue => {
8746 selected_cells.push(
8747 formualizer_common::RangeAddress::new(
8748 region.sheet.clone(),
8749 lease.row,
8750 lease.col,
8751 lease.row,
8752 lease.col,
8753 )
8754 .expect("selected staged coordinates are valid"),
8755 );
8756 prepared.push(PreparedOrdinaryStagedFormula {
8757 sheet: region.sheet.clone(),
8758 sheet_id,
8759 lease,
8760 ast_id: None,
8761 plan: None,
8762 });
8763 continue;
8764 }
8765 FormulaParsePolicy::AsText => ASTNode::new(
8766 ASTNodeType::Literal(LiteralValue::Text(formula.clone())),
8767 None,
8768 ),
8769 FormulaParsePolicy::CoerceToError => ASTNode::new(
8770 ASTNodeType::Literal(LiteralValue::Error(
8771 ExcelError::new(ExcelErrorKind::Error)
8772 .with_message(format!("Malformed formula: {error}")),
8773 )),
8774 None,
8775 ),
8776 FormulaParsePolicy::Strict => unreachable!(),
8777 }
8778 }
8779 };
8780 self.target_preparation_checkpoint(options.deadline, 1)?;
8781 let snapshot = self.target_planning_snapshot(&ast, &mut planning_requests)?;
8782 self.target_preparation_checkpoint(options.deadline, 1)?;
8783 if let Some(reason) =
8784 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8785 {
8786 return Err(Self::preparation_stale(
8787 reason,
8788 "target planning snapshot became stale during discovery",
8789 ));
8790 }
8791 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
8792 if reason == OpaqueReason::DynamicReference
8793 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
8794 {
8795 Self::widen_target_preparation_to_sheet(
8796 options.opaque_policy,
8797 &mut scope,
8798 &mut reasons,
8799 reason,
8800 ®ion.sheet,
8801 )?;
8802 } else {
8803 Self::widen_target_preparation(
8804 options.opaque_policy,
8805 &mut scope,
8806 &mut reasons,
8807 reason,
8808 )?;
8809 }
8810 }
8811 let proven_sheet_local_dynamic =
8812 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
8813 let placement = CellRef::new(
8814 sheet_id,
8815 Coord::from_excel(lease.row, lease.col, true, true),
8816 );
8817 let ingested = self.graph.ingest_pipeline(&snapshot).ingest_formula(
8818 FormulaAstInput::Tree(ast),
8819 placement,
8820 Some(Arc::from(formula)),
8821 )?;
8822 self.target_preparation_checkpoint(options.deadline, 1)?;
8823 if ingested.dep_plan.dynamic {
8824 if proven_sheet_local_dynamic {
8825 Self::widen_target_preparation_to_sheet(
8826 options.opaque_policy,
8827 &mut scope,
8828 &mut reasons,
8829 OpaqueReason::DynamicReference,
8830 ®ion.sheet,
8831 )?;
8832 } else {
8833 Self::widen_target_preparation(
8834 options.opaque_policy,
8835 &mut scope,
8836 &mut reasons,
8837 OpaqueReason::DynamicReference,
8838 )?;
8839 }
8840 }
8841 for dep in &ingested.dep_plan.direct_cell_deps {
8842 self.target_preparation_checkpoint(options.deadline, 1)?;
8843 regions.push_back(PreparationRegion {
8844 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
8845 sheet_id: dep.sheet_id,
8846 start_row: dep.coord.row() + 1,
8847 start_col: dep.coord.col() + 1,
8848 end_row: dep.coord.row() + 1,
8849 end_col: dep.coord.col() + 1,
8850 });
8851 }
8852 for range in &ingested.dep_plan.range_deps {
8853 self.target_preparation_checkpoint(options.deadline, 1)?;
8854 let Ok(dependency_sheet) = self.resolve_sheet_locator(&range.sheet, sheet_id)
8856 else {
8857 Self::widen_target_preparation(
8858 options.opaque_policy,
8859 &mut scope,
8860 &mut reasons,
8861 OpaqueReason::UnresolvedCrossSheetBinding,
8862 )?;
8863 continue;
8864 };
8865 regions.push_back(PreparationRegion {
8866 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
8867 sheet_id: dependency_sheet,
8868 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
8869 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
8870 end_row: range
8871 .end_row
8872 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
8873 bound.index + 1
8874 }),
8875 end_col: range
8876 .end_col
8877 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
8878 bound.index + 1
8879 }),
8880 });
8881 }
8882 for name in ingested
8883 .dep_plan
8884 .resolved_named_refs
8885 .iter()
8886 .chain(&ingested.dep_plan.named_refs)
8887 {
8888 self.target_preparation_checkpoint(options.deadline, 1)?;
8889 if let Some(entry) = self.graph.resolve_name_entry(name, sheet_id) {
8890 symbol_vertices.push_back(entry.vertex);
8891 } else if self.graph.resolve_source_scalar_entry(name).is_none()
8892 && self.graph.resolve_source_table_entry(name).is_none()
8893 {
8894 Self::widen_target_preparation(
8895 options.opaque_policy,
8896 &mut scope,
8897 &mut reasons,
8898 OpaqueReason::UnresolvedName,
8899 )?;
8900 }
8901 }
8902 for table in &ingested.dep_plan.table_refs {
8903 self.target_preparation_checkpoint(options.deadline, 1)?;
8904 if let Some(entry) = self.graph.resolve_table_entry(table) {
8905 symbol_vertices.push_back(entry.vertex);
8906 } else if self.graph.resolve_source_table_entry(table).is_none() {
8907 Self::widen_target_preparation(
8908 options.opaque_policy,
8909 &mut scope,
8910 &mut reasons,
8911 OpaqueReason::UnresolvedTable,
8912 )?;
8913 }
8914 }
8915 selected_cells.push(
8916 formualizer_common::RangeAddress::new(
8917 region.sheet.clone(),
8918 lease.row,
8919 lease.col,
8920 lease.row,
8921 lease.col,
8922 )
8923 .expect("selected staged coordinates are valid"),
8924 );
8925 prepared.push(PreparedOrdinaryStagedFormula {
8926 sheet: region.sheet.clone(),
8927 sheet_id,
8928 lease,
8929 ast_id: Some(ingested.ast_id),
8930 plan: Some(ingested.dep_plan),
8931 });
8932 }
8933
8934 if indexed_query_sheets.insert(region.sheet_id) {
8935 self.graph.prepare_sheet_index_for_query(region.sheet_id);
8936 let bytes = (self.graph.sheet_index_vertex_count(region.sheet_id) as u64)
8937 .saturating_mul(32);
8938 self.reserve_graph_source_scratch(bytes)?;
8939 discovery_scratch_reserved = discovery_scratch_reserved.saturating_add(bytes);
8940 }
8941 let spill_anchors = self.graph.spill_anchors_in_region(
8942 region.sheet_id,
8943 region.start_row - 1,
8944 region.start_col - 1,
8945 region.end_row - 1,
8946 region.end_col - 1,
8947 );
8948 for anchor in spill_anchors {
8949 self.target_preparation_checkpoint(options.deadline, 1)?;
8950 symbol_vertices.push_back(anchor);
8951 }
8952 let vertices = self.graph.vertices_in_region(
8953 region.sheet_id,
8954 region.start_row - 1,
8955 region.end_row - 1,
8956 region.start_col - 1,
8957 region.end_col - 1,
8958 );
8959 for vertex in vertices {
8960 self.target_preparation_checkpoint(options.deadline, 1)?;
8961 symbol_vertices.push_back(vertex);
8962 }
8963 }
8964
8965 #[cfg(test)]
8966 self.target_preparation_fault(
8967 crate::engine::target_preparation::TargetPreparationFault::AfterDiscovery,
8968 )?;
8969
8970 if package_encountered {
8971 Self::widen_target_preparation(
8972 options.opaque_policy,
8973 &mut scope,
8974 &mut reasons,
8975 OpaqueReason::UnsupportedSourceSemantics,
8976 )?;
8977 self.target_preparation_checkpoint(options.deadline, 0)?;
8978 let selected_count = self.staged_formula_count();
8979 let selected_packages = self
8980 .staged_formulas
8981 .values()
8982 .filter_map(|staged| staged.deferred_package.as_ref())
8983 .map(|package| package.families.len() + package.partitioned_families.len())
8984 .sum();
8985 self.formula_parse_diagnostics.truncate(diagnostics_len);
8986 self.last_formula_ingest_report = report_len;
8987 #[cfg(test)]
8988 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
8989 hook();
8990 }
8991 self.target_preparation_checkpoint(options.deadline, 0)?;
8992 #[cfg(test)]
8993 self.target_preparation_fault(
8994 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
8995 )?;
8996 let current_revisions = self.preparation_revisions();
8997 if let Some(reason) = Self::preparation_revision_stale_reason(
8998 &assumptions,
8999 ¤t_revisions,
9000 &planning_requests,
9001 true,
9002 ) {
9003 return Err(Self::preparation_stale(
9004 reason,
9005 "target compatibility preparation plan is stale",
9006 ));
9007 }
9008 #[cfg(test)]
9009 self.target_preparation_fault(
9010 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9011 )?;
9012 let commit_work_before = self
9013 .active_resource_ledger
9014 .as_ref()
9015 .map_or(0, |ledger| ledger.snapshot().work_charged);
9016 let commit_started = crate::instant::FzInstant::now();
9017 self.build_graph_all_unobserved()?;
9018 let commit_window = commit_started.elapsed();
9019 let ledger_after = self
9020 .active_resource_ledger
9021 .as_ref()
9022 .map(|ledger| ledger.snapshot());
9023 let actual_commit_work = ledger_after
9024 .map_or(0, |snapshot| snapshot.work_charged)
9025 .saturating_sub(commit_work_before);
9026 let observed_scratch_bytes = ledger_after
9027 .map_or(0, |snapshot| snapshot.scratch_peak)
9028 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9029 let revisions = assumptions.clone();
9030 let report = PreparedTargetGraphReport {
9031 request_id: request_id.unwrap_or_default(),
9032 requested_targets: targets.len(),
9033 normalized_regions: visited_regions.len(),
9034 normalized_target_list: normalized,
9035 selected_staged_cells: selected_count,
9036 selected_source_families: selected_packages,
9037 retained_staged_cells: self.staged_formula_count(),
9038 selected_cells,
9039 retained_cells: Vec::new(),
9040 widened_scope: PrepareScope::Workbook,
9041 widening_reasons: reasons,
9042 revisions,
9043 commit_window,
9044 estimated_scratch_bytes: discovery_scratch_reserved
9045 .saturating_add((selected_count as u64).saturating_mul(256)),
9046 observed_scratch_bytes,
9047 estimated_commit_work: selected_count as u64,
9048 actual_commit_work,
9049 outcome: PreparationOutcome::CompatibilityPrepared,
9050 };
9051 self.observe_target_preparation_report(&report);
9052 return Ok(report);
9053 }
9054
9055 prepared.sort_by_key(|formula| formula.lease.insertion_order);
9056 for package in &prepared_packages {
9057 if let Some(points) = &package.selected_points {
9058 selected_cells.extend(points.iter().filter_map(|&(row, col)| {
9059 formualizer_common::RangeAddress::new(&package.sheet, row, col, row, col).ok()
9060 }));
9061 continue;
9062 }
9063 selected_cells.extend(package.replay_records.iter().filter_map(|record| {
9064 formualizer_common::RangeAddress::new(
9065 package.sheet.clone(),
9066 record.row,
9067 record.col,
9068 record.row,
9069 record.col,
9070 )
9071 .ok()
9072 }));
9073 }
9074 let (legacy_graph, planned_formula_count) =
9075 self.prepare_target_combined_legacy_graph(&prepared_packages, &prepared)?;
9076 let new_vertices = legacy_graph.new_vertex_count();
9077 let new_edges = legacy_graph.planned_edge_count().ok_or_else(|| {
9078 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9079 })?;
9080 let removed_edges = legacy_graph.removed_edge_count().ok_or_else(|| {
9081 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9082 })?;
9083 let current = self.graph.baseline_stats();
9084 let final_vertices = current
9085 .graph_vertex_count
9086 .checked_add(new_vertices)
9087 .ok_or_else(|| {
9088 crate::engine::ResourceLedgerError::Exhausted(
9089 formualizer_common::ResourceExhaustionDetail {
9090 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9091 limit: u64::MAX,
9092 observed: u64::MAX,
9093 request_id,
9094 },
9095 )
9096 .into_excel_error()
9097 })?;
9098 let final_edges = current
9099 .graph_edge_count
9100 .checked_sub(removed_edges)
9101 .and_then(|count| count.checked_add(new_edges))
9102 .ok_or_else(|| {
9103 crate::engine::ResourceLedgerError::Exhausted(
9104 formualizer_common::ResourceExhaustionDetail {
9105 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9106 limit: u64::MAX,
9107 observed: u64::MAX,
9108 request_id,
9109 },
9110 )
9111 .into_excel_error()
9112 })?;
9113 #[cfg(test)]
9114 self.target_preparation_fault(
9115 crate::engine::target_preparation::TargetPreparationFault::Admission,
9116 )?;
9117 let resource = |reason, limit: u64, observed: u64| {
9118 crate::engine::ResourceLedgerError::Exhausted(
9119 formualizer_common::ResourceExhaustionDetail {
9120 reason,
9121 limit,
9122 observed,
9123 request_id,
9124 },
9125 )
9126 .into_excel_error()
9127 };
9128 let admission = crate::engine::resource_ledger::GraphAdmission {
9129 final_vertices,
9130 final_edges,
9131 materialization_cells: planned_formula_count as u64,
9132 added_vertices: new_vertices,
9133 added_edges: new_edges,
9134 };
9135 let materialized_bytes = admission
9136 .materialized_graph_bytes()
9137 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
9138 if let Err(error) = self.preflight_graph_admission(admission) {
9139 if let formualizer_common::ExcelErrorExtra::Resource { detail } = &error.extra {
9140 self.observe_target_admission_failure(detail.reason);
9141 }
9142 return Err(error);
9143 }
9144 let selected_package_records = prepared_packages
9145 .iter()
9146 .map(|package| package.replay_records.len() as u64)
9147 .sum::<u64>();
9148 let planned_working_bytes = (prepared.len() as u64)
9149 .saturating_add(selected_package_records)
9150 .saturating_mul(256)
9151 .saturating_add((visited_regions.len() as u64).saturating_mul(128))
9152 .saturating_add((visited_vertices.len() as u64).saturating_mul(32))
9153 .saturating_add((selected.len() as u64).saturating_mul(96))
9154 .saturating_add(materialized_bytes);
9155 let residual_scratch = selected_package_points
9156 .iter()
9157 .map(|(sheet, points)| {
9158 let source = self
9159 .staged_formulas
9160 .get(sheet)
9161 .unwrap()
9162 .deferred_package
9163 .as_ref()
9164 .unwrap();
9165 (source.families.len() as u64)
9166 .saturating_mul(256)
9167 .saturating_add(
9168 source
9169 .partitioned_families
9170 .iter()
9171 .map(|family| {
9172 256u64
9173 .saturating_add(family.fragments.len() as u64 * 32)
9174 .saturating_add(family.legacy_members.len() as u64 * 32)
9175 })
9176 .sum::<u64>(),
9177 )
9178 .saturating_add(points.len() as u64 * 32)
9181 .saturating_add(
9182 prepared_packages
9183 .iter()
9184 .filter(|package| &package.sheet == sheet)
9185 .map(|package| package.replay_records.len() as u64 * 480)
9186 .sum::<u64>(),
9187 )
9188 })
9189 .sum::<u64>();
9190 let scratch_bytes = discovery_scratch_reserved
9191 .saturating_add(planned_working_bytes)
9192 .saturating_add(residual_scratch);
9193 let remaining_scratch = scratch_bytes.saturating_sub(discovery_scratch_reserved);
9194 if let Err(error) = self.reserve_graph_source_scratch(remaining_scratch) {
9195 self.observe_target_admission_failure(
9196 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9197 );
9198 return Err(error);
9199 }
9200
9201 let mut residual_sources = BTreeMap::new();
9202 let mut residual_owners = BTreeMap::new();
9203 for (sheet, points) in &selected_package_points {
9204 let selected: BTreeMap<_, BTreeSet<_>> = prepared_packages
9205 .iter()
9206 .filter(|package| &package.sheet == sheet)
9207 .flat_map(|package| &package.replay_records)
9208 .filter_map(|record| {
9209 record.partition_owner.or(record.family).map(|owner| {
9210 (
9211 owner,
9212 crate::engine::SourceCoord {
9213 row: record.row - 1,
9214 col: record.col - 1,
9215 },
9216 )
9217 })
9218 })
9219 .fold(BTreeMap::new(), |mut map, (owner, coord)| {
9220 map.entry(owner).or_default().insert(coord);
9221 map
9222 });
9223 let metadata_work = self
9224 .staged_formulas
9225 .get(sheet)
9226 .unwrap()
9227 .deferred_package
9228 .as_ref()
9229 .map_or(0, |source| {
9230 source.families.len() as u64
9231 + source
9232 .partitioned_families
9233 .iter()
9234 .map(|family| {
9235 1 + family.fragments.len() as u64 * family.fragments.len() as u64
9236 + family.legacy_members.len() as u64
9237 })
9238 .sum::<u64>()
9239 });
9240 let split_work = selected
9241 .values()
9242 .map(|points| points.len() as u64 * 128)
9243 .sum::<u64>();
9244 self.target_preparation_checkpoint(
9245 options.deadline,
9246 metadata_work
9247 .saturating_add(split_work)
9248 .saturating_add(points.len() as u64),
9249 )?;
9250 let source = self
9251 .staged_formulas
9252 .get(sheet)
9253 .unwrap()
9254 .deferred_package
9255 .as_ref()
9256 .unwrap();
9257 let complete: BTreeSet<_> = prepared_packages
9258 .iter()
9259 .filter(|package| &package.sheet == sheet)
9260 .flat_map(|package| package.complete_selections.iter().copied())
9261 .collect();
9262 let residual = source
9263 .residual_sources(&selected, &complete, &self.workbook_load_limits)
9264 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
9265 residual_owners.insert(
9266 sheet.clone(),
9267 residual
9268 .1
9269 .iter()
9270 .map(|family| family.source_id)
9271 .collect::<BTreeSet<_>>(),
9272 );
9273 residual_sources.insert(sheet.clone(), residual);
9274 }
9275
9276 for (sheet, points) in &selected_package_points {
9279 let package = self
9280 .staged_formulas
9281 .get_mut(sheet)
9282 .unwrap()
9283 .deferred_package
9284 .as_mut()
9285 .unwrap();
9286 let owner_count = prepared_packages
9287 .iter()
9288 .filter(|p| &p.sheet == sheet)
9289 .flat_map(|p| &p.replay_records)
9290 .filter_map(|record| record.partition_owner.or(record.family))
9291 .filter(|owner| residual_owners[sheet].contains(owner))
9292 .count();
9293 package
9294 .consumed_members
9295 .try_reserve(owner_count)
9296 .map_err(|_| {
9297 resource(
9298 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9299 0,
9300 owner_count as u64 * 24,
9301 )
9302 })?;
9303 package.suppressed.try_reserve(points.len()).map_err(|_| {
9304 resource(
9305 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9306 0,
9307 points.len() as u64 * 16,
9308 )
9309 })?;
9310 }
9311
9312 let estimated_commit_duration = std::time::Duration::from_nanos(
9313 (new_vertices as u64)
9314 .saturating_add(new_edges as u64)
9315 .saturating_add(prepared.len() as u64)
9316 .saturating_add(selected_package_records)
9317 .max(1)
9318 .saturating_mul(100),
9319 );
9320 if options.deadline.is_some_and(|deadline| {
9321 std::time::Instant::now()
9322 .checked_add(estimated_commit_duration)
9323 .is_none_or(|finish| finish > deadline)
9324 }) {
9325 self.observe_target_admission_failure(
9326 formualizer_common::ResourceExhaustionReason::Deadline,
9327 );
9328 return Err(resource(
9329 formualizer_common::ResourceExhaustionReason::Deadline,
9330 0,
9331 1,
9332 ));
9333 }
9334 #[cfg(test)]
9335 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
9336 hook();
9337 }
9338 self.target_preparation_checkpoint(options.deadline, 0)?;
9339 #[cfg(test)]
9340 self.target_preparation_fault(
9341 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
9342 )?;
9343 let current_revisions = self.preparation_revisions();
9344 let staged_leases_match = prepared.iter().all(|formula| {
9345 self.staged_formula_index
9346 .lease_matches(&formula.sheet, formula.lease)
9347 }) && prepared_packages.iter().all(|package| {
9348 self.staged_formula_index
9349 .package_lease_matches(&package.sheet, package.lease)
9350 });
9351 let stale_reason = Self::preparation_revision_stale_reason(
9352 &assumptions,
9353 ¤t_revisions,
9354 &planning_requests,
9355 staged_leases_match,
9356 );
9357 if let Some(reason) = stale_reason {
9358 return Err(Self::preparation_stale(
9359 reason,
9360 "target graph preparation plan is stale",
9361 ));
9362 }
9363 #[cfg(test)]
9364 self.target_preparation_fault(
9365 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9366 )?;
9367 self.graph
9368 .validate_prepared_legacy_graph_plan(&legacy_graph)
9369 .map_err(|error| {
9370 Self::preparation_stale(
9371 formualizer_common::PreparationStaleReason::Graph,
9372 format!("target graph preparation plan is stale: {error}"),
9373 )
9374 })?;
9375 #[cfg(test)]
9376 self.target_preparation_fault(
9377 crate::engine::target_preparation::TargetPreparationFault::Reservation,
9378 )?;
9379 self.graph.reserve_prepared_legacy_graph_plan(&legacy_graph);
9380 self.formula_parse_diagnostics
9381 .try_reserve(pending_diagnostics.len())
9382 .map_err(|_| {
9383 resource(
9384 formualizer_common::ResourceExhaustionReason::Admission,
9385 pending_diagnostics.len() as u64,
9386 pending_diagnostics.len() as u64,
9387 )
9388 })?;
9389 self.target_preparation_checkpoint(options.deadline, 0)?;
9390 #[cfg(test)]
9391 self.target_preparation_fault(
9392 crate::engine::target_preparation::TargetPreparationFault::BeforeFirstMutation,
9393 )?;
9394
9395 let commit_started = crate::instant::FzInstant::now();
9396 let committed = self
9397 .graph
9398 .apply_prevalidated_legacy_graph_plan(legacy_graph);
9399 for formula in &prepared {
9400 let removed = self
9401 .staged_formulas
9402 .get_mut(&formula.sheet)
9403 .and_then(|sheet| sheet.remove_ordinary(formula.lease.row, formula.lease.col));
9404 debug_assert!(removed.is_some());
9405 let index_removed = self.staged_formula_index.remove(
9406 &formula.sheet,
9407 formula.lease.row,
9408 formula.lease.col,
9409 );
9410 debug_assert!(index_removed);
9411 }
9412 for package in &prepared_packages {
9413 if let Some(points) = &package.selected_points {
9414 let staged = self.staged_formulas.get_mut(&package.sheet).unwrap();
9415 let source = staged.deferred_package.as_mut().unwrap();
9416 source.consumed_engine = Some(Arc::clone(&self.source_formula_token));
9417 source
9420 .consumed_members
9421 .extend(package.replay_records.iter().filter_map(|record| {
9422 record
9423 .partition_owner
9424 .or(record.family)
9425 .filter(|owner| residual_owners[&package.sheet].contains(owner))
9426 .map(|owner| {
9427 (
9428 owner,
9429 crate::engine::SourceCoord {
9430 row: record.row - 1,
9431 col: record.col - 1,
9432 },
9433 )
9434 })
9435 }));
9436 source.suppressed.extend(points.iter().copied());
9437 source.source_accounted = true;
9438 self.staged_formula_index
9439 .consume_package_points(&package.sheet, points);
9440 if source.suppressed.len() >= source.source_coordinates.len() {
9441 staged.deferred_package = None;
9442 self.staged_formula_index.set_package(&package.sheet, None);
9443 }
9444 } else {
9445 let removed = self
9446 .staged_formulas
9447 .get_mut(&package.sheet)
9448 .and_then(|staged| staged.deferred_package.take());
9449 debug_assert!(removed.is_some());
9450 self.staged_formula_index.set_package(&package.sheet, None);
9451 }
9452 }
9453 for (sheet, (families, partitions)) in residual_sources {
9454 if let Some(source) = self
9455 .staged_formulas
9456 .get_mut(&sheet)
9457 .and_then(|staged| staged.deferred_package.as_mut())
9458 {
9459 source.families = families;
9460 source.partitioned_families = partitions;
9461 source
9462 .consumed_members
9463 .retain(|(owner, _)| residual_owners[&sheet].contains(owner));
9464 self.staged_formula_index.update_package_family_count(
9465 &sheet,
9466 source.families.len() + source.partitioned_families.len(),
9467 );
9468 }
9469 }
9470 let empty_sheets = self
9471 .staged_formulas
9472 .iter()
9473 .filter_map(|(sheet, staged)| staged.is_empty().then_some(sheet.clone()))
9474 .collect::<Vec<_>>();
9475 for sheet in empty_sheets {
9476 self.staged_formulas.remove(&sheet);
9477 }
9478 if committed > 0 {
9479 self.mark_topology_edited();
9480 }
9481 self.formula_parse_diagnostics.extend(pending_diagnostics);
9482 if !prepared.is_empty() || !prepared_packages.is_empty() {
9483 let mut ingest_delta = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
9484 ingest_delta.formula_cells_seen = (prepared.len() as u64).saturating_add(
9485 prepared_packages
9486 .iter()
9487 .map(|package| {
9488 (package.replay_records.len() as u64).saturating_add(
9489 if package.selected_points.is_some() {
9490 package.direct_cells
9491 } else {
9492 0
9493 },
9494 )
9495 })
9496 .sum::<u64>(),
9497 );
9498 ingest_delta.graph_formula_cells_materialized = committed as u64;
9499 ingest_delta.graph_vertices_created = new_vertices as u64;
9500 ingest_delta.graph_edges_created = new_edges as u64;
9501 for package in &prepared_packages {
9502 let source = &package.source_report;
9503 ingest_delta.source_formula_events = ingest_delta
9504 .source_formula_events
9505 .saturating_add(source.source_formula_events);
9506 ingest_delta.source_formula_records_spooled = ingest_delta
9507 .source_formula_records_spooled
9508 .saturating_add(source.source_formula_records_spooled);
9509 ingest_delta.source_spool_encoded_bytes = ingest_delta
9510 .source_spool_encoded_bytes
9511 .saturating_add(source.source_spool_encoded_bytes);
9512 ingest_delta.source_spool_peak_memory_bytes = ingest_delta
9513 .source_spool_peak_memory_bytes
9514 .max(source.source_spool_peak_memory_bytes);
9515 ingest_delta.source_spool_spilled_bytes = ingest_delta
9516 .source_spool_spilled_bytes
9517 .saturating_add(source.source_spool_spilled_bytes);
9518 ingest_delta.source_spool_spill_files = ingest_delta
9519 .source_spool_spill_files
9520 .saturating_add(source.source_spool_spill_files);
9521 ingest_delta.source_spool_replays = ingest_delta
9522 .source_spool_replays
9523 .saturating_add(source.source_spool_replays)
9524 .saturating_add(package.spool_replays);
9525 ingest_delta.source_families_seen = ingest_delta
9526 .source_families_seen
9527 .saturating_add(source.families_seen);
9528 ingest_delta.source_family_cells_seen = ingest_delta
9529 .source_family_cells_seen
9530 .saturating_add(source.family_cells_seen);
9531 ingest_delta.source_family_shadow_eligible = ingest_delta
9532 .source_family_shadow_eligible
9533 .saturating_add(source.source_clean_families);
9534 ingest_delta.source_family_shadow_eligible_cells = ingest_delta
9535 .source_family_shadow_eligible_cells
9536 .saturating_add(source.source_clean_cells);
9537 ingest_delta.source_partitioned_families_seen = ingest_delta
9538 .source_partitioned_families_seen
9539 .saturating_add(source.source_fragmentable_families);
9540 ingest_delta.source_partition_holes = ingest_delta
9541 .source_partition_holes
9542 .saturating_add(source.source_hole_exclusions);
9543 ingest_delta.source_partition_ordinary_exceptions = ingest_delta
9544 .source_partition_ordinary_exceptions
9545 .saturating_add(source.source_ordinary_exclusions);
9546 ingest_delta.source_partition_surviving_cells = ingest_delta
9547 .source_partition_surviving_cells
9548 .saturating_add(source.source_fragmentable_cells);
9549 for (reason, count) in &source.fallback_reasons {
9550 let total = ingest_delta
9551 .fallback_reasons
9552 .entry(reason.clone())
9553 .or_default();
9554 *total = total.saturating_add(*count);
9555 }
9556
9557 ingest_delta.source_family_fallback = ingest_delta
9558 .source_family_fallback
9559 .saturating_add(source.families_seen);
9560 ingest_delta.source_family_fallback_cells = ingest_delta
9561 .source_family_fallback_cells
9562 .saturating_add(source.family_cells_seen);
9563 }
9564 self.record_formula_ingest_report(ingest_delta);
9565 }
9566 let commit_window = commit_started.elapsed();
9567 let retained_cells = self
9568 .staged_formula_index
9569 .all_leases()
9570 .into_iter()
9571 .filter_map(|(sheet, lease)| {
9572 formualizer_common::RangeAddress::new(
9573 sheet, lease.row, lease.col, lease.row, lease.col,
9574 )
9575 .ok()
9576 })
9577 .collect::<Vec<_>>();
9578 let observed_scratch_bytes = self
9579 .active_resource_ledger
9580 .as_ref()
9581 .map_or(0, |ledger| ledger.snapshot().scratch_peak)
9582 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9583 let committed_spans = 0u64;
9584 let selected_source_families = prepared_packages
9585 .iter()
9586 .filter(|package| package.selected_points.is_none())
9587 .map(|package| package.lease.family_count)
9588 .sum::<usize>()
9589 + prepared_packages
9590 .iter()
9591 .filter(|package| package.selected_points.is_some())
9592 .flat_map(|package| package.replay_records.iter())
9593 .filter_map(|record| record.partition_owner.or(record.family))
9594 .chain(
9595 prepared_packages
9596 .iter()
9597 .flat_map(|package| package.complete_selections.iter().copied()),
9598 )
9599 .collect::<BTreeSet<_>>()
9600 .len();
9601 let selected_staged_cells = prepared.len().saturating_add(
9602 prepared_packages
9603 .iter()
9604 .map(|package| {
9605 package
9606 .selected_points
9607 .as_ref()
9608 .map_or(package.replay_records.len(), BTreeSet::len)
9609 })
9610 .sum::<usize>(),
9611 );
9612 let actual_commit_work = (new_vertices as u64)
9613 .saturating_add(new_edges as u64)
9614 .saturating_add(committed as u64)
9615 .saturating_add(committed_spans)
9616 .saturating_add(prepared.len() as u64)
9617 .saturating_add(
9618 prepared_packages
9619 .iter()
9620 .map(|package| {
9621 package
9622 .selected_points
9623 .as_ref()
9624 .map_or(1, |points| points.len() as u64)
9625 })
9626 .sum::<u64>(),
9627 );
9628 let report = PreparedTargetGraphReport {
9629 request_id: request_id.unwrap_or_default(),
9630 requested_targets: targets.len(),
9631 normalized_regions: visited_regions.len(),
9632 normalized_target_list: normalized,
9633 selected_staged_cells,
9634 selected_source_families,
9635 retained_staged_cells: self.staged_formula_count(),
9636 selected_cells,
9637 retained_cells,
9638 widened_scope: scope,
9639 widening_reasons: reasons,
9640 revisions: assumptions,
9641 commit_window,
9642 estimated_scratch_bytes: scratch_bytes,
9643 observed_scratch_bytes,
9644 estimated_commit_work: (new_vertices as u64)
9645 .saturating_add(new_edges as u64)
9646 .saturating_add(prepared.len() as u64)
9647 .saturating_add(selected_package_records),
9648 actual_commit_work,
9649 outcome: PreparationOutcome::Prepared,
9650 };
9651 self.observe_target_preparation_report(&report);
9652 Ok(report)
9653 }
9654
9655 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9657 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
9658 engine.build_graph_all_unobserved()
9659 })
9660 }
9661
9662 fn build_graph_all_unobserved(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9663 let selected = self.staged_formula_count();
9664 let started = crate::instant::FzInstant::now();
9665 self.resource_checkpoint(selected as u64)?;
9666 let scratch_bytes = (selected as u64).saturating_mul(256);
9667 let result = self.with_request_scratch(scratch_bytes, |engine| {
9668 let index_snapshot = engine.staged_formula_index.clone();
9669 let collected = std::mem::take(&mut engine.staged_formulas)
9670 .into_iter()
9671 .collect();
9672 engine.staged_formula_index.clear_all();
9673 engine.build_graph_from_staged_batches(collected, false, index_snapshot)
9674 });
9675 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9676 result
9677 }
9678
9679 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
9681 &mut self,
9682 sheets: I,
9683 ) -> Result<(), formualizer_parse::ExcelError> {
9684 let mut sheets = sheets.into_iter();
9685 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, move |engine| {
9686 let name_scratch = (sheets.size_hint().0 as u64).saturating_mul(64);
9687 engine.with_request_scratch(name_scratch, |engine| {
9688 let names = sheets.by_ref().map(str::to_string).collect::<Vec<_>>();
9691 engine.charge_bounded_work(names.len() as u64)?;
9692 engine.build_graph_for_sheet_names_unobserved(names)
9693 })
9694 })
9695 }
9696
9697 fn build_graph_for_sheet_names_unobserved(
9698 &mut self,
9699 sheets: Vec<String>,
9700 ) -> Result<(), formualizer_parse::ExcelError> {
9701 let started = crate::instant::FzInstant::now();
9702 let selected = sheets
9703 .iter()
9704 .filter_map(|sheet| self.staged_formulas.get(sheet))
9705 .map(StagedSheet::len)
9706 .sum::<usize>();
9707 self.resource_checkpoint(selected as u64)?;
9708 let scratch_bytes = (selected as u64).saturating_mul(256);
9709 self.reserve_request_scratch(scratch_bytes)?;
9710 let index_snapshot = self.staged_formula_index.clone();
9711 let mut collected = Vec::new();
9712 for sheet in sheets {
9713 if let Some(staged) = self.staged_formulas.remove(&sheet) {
9714 self.index_removed_staged_sheet(&sheet, &staged);
9715 collected.push((sheet, staged));
9716 }
9717 }
9718 let result = self.build_graph_from_staged_batches(collected, true, index_snapshot);
9719 self.release_request_scratch(scratch_bytes);
9720 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9721 result
9722 }
9723
9724 fn build_graph_from_staged_batches(
9725 &mut self,
9726 collected: StagedFormulaBatches,
9727 share_parse_cache_across_sheets: bool,
9728 staged_index_snapshot: StagedFormulaIndex,
9729 ) -> Result<(), formualizer_parse::ExcelError> {
9730 if collected.is_empty() {
9731 return Ok(());
9732 }
9733 for (sheet, _) in &collected {
9734 let _ = self.add_sheet(sheet);
9735 }
9736
9737 let diagnostics_len = self.formula_parse_diagnostics.len();
9738 let mut collected = collected;
9739 let prepared = match self
9740 .prepare_staged_formula_batches(&mut collected, share_parse_cache_across_sheets)
9741 {
9742 Ok(prepared) => prepared,
9743 Err(error) => {
9744 self.formula_parse_diagnostics.truncate(diagnostics_len);
9745 for (sheet, staged) in collected {
9746 self.restore_staged_sheet(sheet, staged);
9747 }
9748 self.staged_formula_index = staged_index_snapshot;
9749 return Err(error);
9750 }
9751 };
9752 let (ordinary, compressed, direct, may_fail) = prepared;
9753
9754 let first_build = if self.deferred_build_can_be_first_load() {
9764 if let Err(error) = self.check_staged_formula_plans(
9765 ordinary
9766 .iter()
9767 .chain(compressed.iter().map(|(batch, _)| batch)),
9768 &may_fail,
9769 ) {
9770 self.formula_parse_diagnostics.truncate(diagnostics_len);
9771 for (sheet, staged) in collected {
9772 self.restore_staged_sheet(sheet, staged);
9773 }
9774 self.staged_formula_index = staged_index_snapshot;
9775 return Err(error);
9776 }
9777 self.deferred_build_as_first_load()
9778 } else {
9779 None
9780 };
9781 let built_sheets: Vec<String> = if first_build.is_some() {
9782 collected.iter().map(|(sheet, _)| sheet.clone()).collect()
9783 } else {
9784 Vec::new()
9785 };
9786
9787 let result = (|| {
9791 if !ordinary.is_empty() {
9792 self.ingest_formula_batches(ordinary)?;
9793 }
9794 if !compressed.is_empty() {
9795 self.ingest_compressed_formula_source_batches(compressed)?;
9796 }
9797 if !direct.is_empty() {
9798 self.finish_compressed_formula_sources(direct)?;
9799 }
9800 Ok(())
9801 })();
9802 if let Some(saved) = first_build {
9803 self.leave_deferred_first_load(saved, &built_sheets);
9804 }
9805 if let Err(error) = result {
9806 self.formula_parse_diagnostics.truncate(diagnostics_len);
9807 for (sheet, staged) in collected {
9808 self.restore_staged_sheet(sheet, staged);
9809 }
9810 self.staged_formula_index = staged_index_snapshot;
9811 return Err(error);
9812 }
9813 self.dedup_formula_parse_diagnostics_since(diagnostics_len);
9814 Ok(())
9815 }
9816
9817 fn deferred_build_can_be_first_load(&self) -> bool {
9823 !self.graph.first_load_assume_new()
9824 && !self.graph_admission_enabled()
9825 && self.graph.formula_vertex_count() == 0
9826 }
9827
9828 fn check_staged_formula_plans<'b>(
9833 &mut self,
9834 batches: impl Iterator<Item = &'b FormulaIngestBatch>,
9835 may_fail: &FxHashSet<crate::engine::arena::AstNodeId>,
9836 ) -> Result<(), ExcelError> {
9837 if may_fail.is_empty() {
9838 return Ok(());
9839 }
9840 let mut seen: FxHashSet<(SheetId, crate::engine::arena::AstNodeId)> = FxHashSet::default();
9841 for batch in batches {
9842 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
9843 ExcelError::new(ExcelErrorKind::Ref)
9844 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
9845 })?;
9846 let mut pipeline = self.ingest_pipeline();
9847 for record in &batch.formulas {
9848 if record.member_anchor.is_some()
9849 || !may_fail.contains(&record.ast_id)
9850 || !seen.insert((sheet_id, record.ast_id))
9851 {
9852 continue;
9853 }
9854 let placement = CellRef::new(
9855 sheet_id,
9856 Coord::from_excel(record.row, record.col, true, true),
9857 );
9858 pipeline.ingest_formula(
9859 FormulaAstInput::RawArena(record.ast_id),
9860 placement,
9861 None,
9862 )?;
9863 }
9864 }
9865 Ok(())
9866 }
9867
9868 fn deferred_build_as_first_load(&mut self) -> Option<(crate::engine::SheetIndexMode, usize)> {
9869 if !self.deferred_build_can_be_first_load() {
9870 return None;
9871 }
9872 let saved = (
9873 self.graph.get_config().sheet_index_mode,
9874 self.config.range_expansion_limit,
9875 );
9876 self.graph
9877 .set_sheet_index_mode(crate::engine::SheetIndexMode::Lazy);
9878 self.config.range_expansion_limit = 0;
9879 self.graph.set_first_load_assume_new(true);
9880 self.graph.reset_ensure_touched();
9881 Some(saved)
9882 }
9883
9884 fn leave_deferred_first_load(
9887 &mut self,
9888 (index_mode, range_limit): (crate::engine::SheetIndexMode, usize),
9889 sheets: &[String],
9890 ) {
9891 self.graph.set_first_load_assume_new(false);
9892 self.graph.reset_ensure_touched();
9893 self.graph.set_sheet_index_mode(index_mode);
9894 self.config.range_expansion_limit = range_limit;
9895 for sheet in sheets {
9896 self.graph.finalize_sheet_index(sheet);
9897 }
9898 }
9899
9900 fn prepare_staged_formula_batches(
9901 &mut self,
9902 collected: &mut StagedFormulaBatches,
9903 share_parse_cache_across_sheets: bool,
9904 ) -> Result<PreparedStagedFormulaBatches, formualizer_parse::ExcelError> {
9905 let mut ordinary = Vec::new();
9906 let mut compressed = Vec::new();
9907 let mut direct = Vec::new();
9908 let mut may_fail = FxHashSet::default();
9909 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
9910 rustc_hash::FxHashMap::default();
9911 cache.reserve(4096);
9912
9913 for (sheet, staged) in collected {
9914 if !share_parse_cache_across_sheets {
9915 cache.clear();
9916 }
9917 let mut grouper = crate::engine::FormulaFamilyGrouper::new();
9921 let mut replayed_records = Vec::new();
9925 let deferred_source = None;
9926 let mut deferred_fallback = None;
9927 if let Some(package) = staged.deferred_package.as_mut() {
9928 if package.sheet_name != *sheet {
9929 return Err(ExcelError::new(ExcelErrorKind::Value)
9930 .with_message("deferred formula package sheet mismatch"));
9931 }
9932 let eligible: Vec<_> = package
9933 .families
9934 .iter()
9935 .filter(|family| !package.invalidated.contains(&family.source_id))
9936 .cloned()
9937 .collect();
9938 let eligible_partitions: Vec<_> = package
9939 .partitioned_families
9940 .iter()
9941 .filter(|family| !package.invalidated.contains(&family.source_id))
9942 .cloned()
9943 .collect();
9944
9945 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
9946 for partition in &eligible_partitions {
9947 replay_disposition
9948 .register_partition(partition, false)
9949 .map_err(|reason| {
9950 ExcelError::new(ExcelErrorKind::Value).with_message(reason)
9951 })?;
9952 }
9953 replay_disposition
9954 .extend_suppressed_excel_coords(package.suppressed.iter().copied());
9955 let replayed = package
9956 .replay
9957 .lock()
9958 .map_err(|_| {
9959 ExcelError::new(ExcelErrorKind::Value)
9960 .with_message("deferred formula spool lock poisoned")
9961 })?
9962 .replay(&replay_disposition)
9963 .map_err(|message| {
9964 ExcelError::new(ExcelErrorKind::Value).with_message(message)
9965 })?;
9966 replayed_records = replayed;
9967 let mut report = package.accounting_report();
9968 report.source_spool_replays = report.source_spool_replays.saturating_add(1);
9969 deferred_fallback = Some((report, package.families.clone(), eligible_partitions));
9970 }
9971
9972 let n_entries = staged.entries.len() + replayed_records.len();
9973 let entries = staged
9974 .entries
9975 .iter()
9976 .cloned()
9977 .map(|(row, col, text)| (row, col, text, None))
9978 .chain(replayed_records.into_iter().map(|record| {
9979 (
9980 record.row,
9981 record.col,
9982 record.text,
9983 Some((record.source_order, record.family, record.partition_owner)),
9984 )
9985 }));
9986 let mut formulas = Vec::with_capacity(n_entries);
9987 let staged_order_base = u64::MAX.saturating_sub(n_entries as u64);
9988 for (entry_index, (row, col, txt, source_proof)) in entries.enumerate() {
9989 let key = if txt.starts_with('=') {
9990 txt
9991 } else {
9992 format!("={txt}")
9993 };
9994 let staged_record = if let Some(cached) = cache.get(&key) {
9995 cached.map(|ast_id| {
9996 self.note_staged_formula(&mut grouper, row, col, ast_id);
9997 FormulaIngestRecord::new(row, col, ast_id, Some(Arc::<str>::from(key)))
9998 })
9999 } else {
10000 let parsed = match formualizer_parse::parser::parse(&key) {
10001 Ok(parsed) => Some(parsed),
10002 Err(error) => self.handle_formula_parse_error(
10003 sheet,
10004 row,
10005 col,
10006 &key,
10007 error.to_string(),
10008 )?,
10009 };
10010 match parsed {
10011 Some(ast) => {
10012 let record = self.stage_formula_ast(&mut grouper, row, col, &ast, None);
10013 if !record.is_family_member() && self.formula_may_fail_planning(&ast) {
10014 may_fail.insert(record.ast_id);
10015 }
10016 if record.is_family_member() {
10019 Some(record)
10020 } else {
10021 let ast_id = record.ast_id;
10022 cache.insert(key.clone(), Some(ast_id));
10023 Some(FormulaIngestRecord::new(
10024 row,
10025 col,
10026 ast_id,
10027 Some(Arc::<str>::from(key)),
10028 ))
10029 }
10030 }
10031 None => {
10032 cache.insert(key, None);
10033 None
10034 }
10035 }
10036 };
10037
10038 if let Some(mut formula) = staged_record {
10039 if let Some((order, family, owner)) = source_proof {
10040 formula = formula.with_source_proof(order, family, owner);
10041 } else if deferred_source.is_some() {
10042 formula = formula.with_source_proof(
10043 crate::engine::SourceFormulaOrder::new(
10044 staged_order_base.saturating_add(entry_index as u64),
10045 ),
10046 None,
10047 None,
10048 );
10049 }
10050 formulas.push(formula);
10051 }
10052 }
10053
10054 let batch = FormulaIngestBatch::new(sheet.clone(), formulas);
10055 if let Some((report, preparation)) = deferred_source {
10056 direct.push((batch, report, preparation));
10057 } else if let Some((report, families, partitions)) = deferred_fallback {
10058 let source_batch = crate::engine::FormulaCompressedSourceBatch::with_proposals(
10059 batch.sheet_name.clone(),
10060 report,
10061 families,
10062 partitions,
10063 );
10064 compressed.push((batch, source_batch));
10065 } else if !batch.is_empty() {
10066 ordinary.push(batch);
10067 }
10068 }
10069 Ok((ordinary, compressed, direct, may_fail))
10070 }
10071
10072 fn formula_may_fail_planning(&self, ast: &formualizer_parse::parser::ASTNode) -> bool {
10077 use formualizer_parse::parser::{ASTNodeType, ReferenceType};
10078 let sheet_missing = |sheet: &Option<String>| {
10079 sheet
10080 .as_deref()
10081 .is_some_and(|s| self.graph.sheet_id(s).is_none())
10082 };
10083 match &ast.node_type {
10084 ASTNodeType::Literal(_) | ASTNodeType::Omitted => false,
10085 ASTNodeType::Reference { reference, .. } => match reference {
10086 ReferenceType::Cell { sheet, .. } => sheet_missing(sheet),
10087 ReferenceType::Range {
10088 sheet,
10089 start_row,
10090 start_col,
10091 end_row,
10092 end_col,
10093 ..
10094 } => {
10095 sheet_missing(sheet)
10096 || matches!((start_row, end_row), (Some(a), Some(b)) if a > b)
10097 || matches!((start_col, end_col), (Some(a), Some(b)) if a > b)
10098 }
10099 ReferenceType::NamedRange(_) => false,
10100 _ => true,
10101 },
10102 ASTNodeType::UnaryOp { expr, .. } => self.formula_may_fail_planning(expr),
10103 ASTNodeType::BinaryOp { left, right, .. } => {
10104 self.formula_may_fail_planning(left) || self.formula_may_fail_planning(right)
10105 }
10106 ASTNodeType::Function { args, .. } => {
10107 args.iter().any(|a| self.formula_may_fail_planning(a))
10108 }
10109 ASTNodeType::Call { callee, args } => {
10110 self.formula_may_fail_planning(callee)
10111 || args.iter().any(|a| self.formula_may_fail_planning(a))
10112 }
10113 ASTNodeType::Array(rows) => rows
10114 .iter()
10115 .flatten()
10116 .any(|a| self.formula_may_fail_planning(a)),
10117 }
10118 }
10119
10120 pub fn begin_bulk_ingest_arrow(
10122 &mut self,
10123 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
10124 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
10125 }
10126
10127 pub fn begin_bulk_update_arrow(
10129 &mut self,
10130 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
10131 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
10132 }
10133
10134 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
10135 self.graph.sheet_id(sheet).ok_or(
10136 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10137 name: sheet.to_string(),
10138 reason: "Unknown sheet".to_string(),
10139 },
10140 )
10141 }
10142
10143 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
10144 if row_1based == 0 {
10145 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10146 }
10147 Ok(row_1based - 1)
10148 }
10149
10150 fn normalize_row_range_1based(
10151 start_row_1based: u32,
10152 end_row_1based: u32,
10153 ) -> Result<(u32, u32), crate::engine::EditorError> {
10154 if start_row_1based == 0 || end_row_1based == 0 {
10155 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10156 }
10157 if start_row_1based > end_row_1based {
10158 return Err(crate::engine::EditorError::TransactionFailed {
10159 reason: "Row range start is greater than end".to_string(),
10160 });
10161 }
10162 Ok((start_row_1based - 1, end_row_1based - 1))
10163 }
10164
10165 fn invalidate_row_visibility_mask_cache(&self) {
10166 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10167 cache.clear();
10168 }
10169 }
10170
10171 fn set_row_hidden_by_sheet_id(
10172 &mut self,
10173 sheet_id: SheetId,
10174 row0: u32,
10175 hidden: bool,
10176 source: RowVisibilitySource,
10177 ) -> bool {
10178 let changed = {
10179 let state = self.row_visibility.entry(sheet_id).or_default();
10180 state.set_row_hidden(row0, hidden, source)
10181 };
10182
10183 let remove_entry = self
10184 .row_visibility
10185 .get(&sheet_id)
10186 .map(|state| state.is_empty())
10187 .unwrap_or(false);
10188 if remove_entry {
10189 self.row_visibility.remove(&sheet_id);
10190 }
10191
10192 if changed {
10193 self.invalidate_row_visibility_mask_cache();
10194 }
10195
10196 changed
10197 }
10198
10199 fn set_rows_hidden_by_sheet_id(
10200 &mut self,
10201 sheet_id: SheetId,
10202 start_row0: u32,
10203 end_row0: u32,
10204 hidden: bool,
10205 source: RowVisibilitySource,
10206 ) -> bool {
10207 let changed = {
10208 let state = self.row_visibility.entry(sheet_id).or_default();
10209 state.set_rows_hidden(start_row0, end_row0, hidden, source)
10210 };
10211
10212 let remove_entry = self
10213 .row_visibility
10214 .get(&sheet_id)
10215 .map(|state| state.is_empty())
10216 .unwrap_or(false);
10217 if remove_entry {
10218 self.row_visibility.remove(&sheet_id);
10219 }
10220
10221 if changed {
10222 self.invalidate_row_visibility_mask_cache();
10223 }
10224
10225 changed
10226 }
10227
10228 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
10229 if count == 0 {
10230 return;
10231 }
10232 let mut changed = false;
10233 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10234 changed = state.insert_rows(before0, count);
10235 state.is_empty()
10236 } else {
10237 false
10238 };
10239 if remove_entry {
10240 self.row_visibility.remove(&sheet_id);
10241 }
10242 if changed {
10243 self.invalidate_row_visibility_mask_cache();
10244 }
10245 }
10246
10247 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
10248 if count == 0 {
10249 return;
10250 }
10251 let mut changed = false;
10252 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10253 changed = state.delete_rows(start0, count);
10254 state.is_empty()
10255 } else {
10256 false
10257 };
10258 if remove_entry {
10259 self.row_visibility.remove(&sheet_id);
10260 }
10261 if changed {
10262 self.invalidate_row_visibility_mask_cache();
10263 }
10264 }
10265
10266 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10267 if let crate::engine::ChangeEvent::SetRowVisibility {
10268 sheet_id,
10269 row0,
10270 source,
10271 old_hidden,
10272 ..
10273 } = event
10274 {
10275 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
10276 }
10277 }
10278
10279 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10280 if let crate::engine::ChangeEvent::SetRowVisibility {
10281 sheet_id,
10282 row0,
10283 source,
10284 new_hidden,
10285 ..
10286 } = event
10287 {
10288 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
10289 }
10290 }
10291
10292 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10293 for event in events.iter().rev() {
10294 self.apply_inverse_row_visibility_event(event);
10295 }
10296 }
10297
10298 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10299 for event in events {
10300 self.apply_forward_row_visibility_event(event);
10301 }
10302 }
10303
10304 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10305 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10306 sheet,
10307 row,
10308 col,
10309 old,
10310 ..
10311 } = event
10312 {
10313 self.apply_staged_formula_cell(sheet, *row, *col, old.as_deref());
10314 }
10315 }
10316
10317 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10318 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10319 sheet,
10320 row,
10321 col,
10322 new,
10323 ..
10324 } = event
10325 {
10326 self.apply_staged_formula_cell(sheet, *row, *col, new.as_deref());
10327 }
10328 }
10329
10330 fn apply_staged_formula_cell(&mut self, sheet: &str, row: u32, col: u32, target: Option<&str>) {
10333 match target {
10334 Some(text) => self.stage_formula_text(sheet, row, col, text.to_string()),
10335 None => {
10336 self.clear_staged_formula_text(sheet, row, col);
10337 }
10338 }
10339 }
10340
10341 pub fn set_row_hidden(
10342 &mut self,
10343 sheet: &str,
10344 row_1based: u32,
10345 hidden: bool,
10346 source: RowVisibilitySource,
10347 ) -> Result<(), crate::engine::EditorError> {
10348 self.observe_function_semantic_epoch()
10349 .map_err(crate::engine::EditorError::Excel)?;
10350 self.observe_function_semantic_epoch()
10351 .map_err(crate::engine::EditorError::Excel)?;
10352 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10353 let row0 = Self::normalize_row_1based(row_1based)?;
10354 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
10355 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10356 sheet_id, row0,
10357 )));
10358 self.mark_data_edited();
10359 }
10360 Ok(())
10361 }
10362
10363 pub fn set_rows_hidden(
10364 &mut self,
10365 sheet: &str,
10366 start_row_1based: u32,
10367 end_row_1based: u32,
10368 hidden: bool,
10369 source: RowVisibilitySource,
10370 ) -> Result<(), crate::engine::EditorError> {
10371 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10372 let (start_row0, end_row0) =
10373 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
10374 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
10375 if start_row0 == end_row0 {
10376 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10377 sheet_id, start_row0,
10378 )));
10379 } else {
10380 self.record_structural_change(StructuralScope::Sheet(sheet_id));
10381 }
10382 self.mark_data_edited();
10383 }
10384 Ok(())
10385 }
10386
10387 pub fn is_row_hidden(
10388 &self,
10389 sheet: &str,
10390 row_1based: u32,
10391 source: Option<RowVisibilitySource>,
10392 ) -> Option<bool> {
10393 let sheet_id = self.graph.sheet_id(sheet)?;
10394 let row0 = row_1based.checked_sub(1)?;
10395 Some(
10396 self.row_visibility
10397 .get(&sheet_id)
10398 .map(|state| state.is_row_hidden(row0, source))
10399 .unwrap_or(false),
10400 )
10401 }
10402
10403 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
10404 let sheet_id = self.graph.sheet_id(sheet)?;
10405 Some(
10406 self.row_visibility
10407 .get(&sheet_id)
10408 .map(|state| state.version())
10409 .unwrap_or(0),
10410 )
10411 }
10412
10413 fn build_row_visibility_mask_for_view(
10414 &self,
10415 view: &RangeView<'_>,
10416 mode: VisibilityMaskMode,
10417 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
10418 let sheet_rows = view.sheet().nrows as usize;
10419 if sheet_rows == 0 || view.start_row() >= sheet_rows {
10420 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
10421 }
10422
10423 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
10424 let start_row0 = view.start_row() as u32;
10425 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
10426 let version = self
10427 .row_visibility
10428 .get(&sheet_id)
10429 .map(|state| state.version())
10430 .unwrap_or(0);
10431 let key = VisibilityMaskCacheKey {
10432 sheet_id,
10433 start_row0,
10434 end_row0,
10435 mode,
10436 version,
10437 };
10438
10439 if let Ok(cache) = self.row_visibility_mask_cache.read()
10440 && let Some(mask) = cache.get(&key)
10441 {
10442 #[cfg(test)]
10443 visibility_mask_test_hooks::inc_hit();
10444 return Some(mask.clone());
10445 }
10446
10447 #[cfg(test)]
10448 visibility_mask_test_hooks::inc_miss();
10449
10450 let state = self.row_visibility.get(&sheet_id);
10451 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
10452 for row0 in start_row0..=end_row0 {
10453 let manual_hidden = state
10454 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
10455 .unwrap_or(false);
10456 let filter_hidden = state
10457 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
10458 .unwrap_or(false);
10459
10460 let include = match mode {
10461 VisibilityMaskMode::IncludeAll => true,
10462 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
10463 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
10464 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
10465 !(manual_hidden || filter_hidden)
10466 }
10467 };
10468 out.push(include);
10469 }
10470
10471 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
10472 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10473 const MAX_CACHE_ENTRIES: usize = 4096;
10474 if cache.len() >= MAX_CACHE_ENTRIES {
10475 cache.clear();
10476 #[cfg(test)]
10477 visibility_mask_test_hooks::inc_eviction();
10478 }
10479 cache.insert(key, mask.clone());
10480 }
10481
10482 Some(mask)
10483 }
10484
10485 fn observe_function_semantic_epoch(&mut self) -> Result<bool, ExcelError> {
10486 let changes =
10487 crate::function_registry::semantic_changes_since(self.function_semantic_epoch_seen);
10488 let global_changed = changes.epoch != self.function_semantic_epoch_seen;
10489 let provider_revision = self.resolver.planning_semantic_revision();
10490 let provider_changed = provider_revision != self.function_provider_revision_seen;
10491 if !global_changed && !provider_changed {
10492 return Ok(false);
10493 }
10494
10495 let changed = !changes.keys.is_empty();
10496 if global_changed && changed || provider_changed {
10497 self.cached_static_schedule = None;
10498 self.recent_schedules.clear();
10499 self.base_schedule = None;
10500 }
10501 self.function_semantic_epoch_seen = changes.epoch;
10502 self.function_provider_revision_seen = provider_revision;
10503 Ok(false)
10504 }
10505
10506 pub(crate) fn ast_uses_changed_function(
10507 ast: &ASTNode,
10508 changed: &BTreeSet<(String, String)>,
10509 ) -> bool {
10510 match &ast.node_type {
10511 ASTNodeType::Function { name, args } => {
10512 let normalized = name.to_uppercase();
10513 let mut spellings = vec![(String::new(), normalized.clone())];
10514 let mut stripped = normalized.as_str();
10515 loop {
10516 let Some(rest) = ["_XLFN.", "_XLL.", "_XLWS."]
10517 .iter()
10518 .find_map(|prefix| stripped.strip_prefix(prefix))
10519 else {
10520 break;
10521 };
10522 stripped = rest;
10523 spellings.push((String::new(), stripped.to_string()));
10524 }
10525 let resolved = crate::function_registry::resolve("", name);
10526 let directly_changed = spellings.iter().any(|spelling| changed.contains(spelling))
10527 || resolved.as_ref().is_some_and(|resolved| {
10528 changed.contains(&(
10529 resolved.namespace.clone(),
10530 resolved.canonical_name.clone(),
10531 ))
10532 });
10533 directly_changed
10534 || resolved.is_none()
10535 || args
10536 .iter()
10537 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10538 }
10539 ASTNodeType::Call { callee, args } => {
10540 Self::ast_uses_changed_function(callee, changed)
10541 || args
10542 .iter()
10543 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10544 }
10545 ASTNodeType::UnaryOp { expr, .. } => Self::ast_uses_changed_function(expr, changed),
10546 ASTNodeType::BinaryOp { left, right, .. } => {
10547 Self::ast_uses_changed_function(left, changed)
10548 || Self::ast_uses_changed_function(right, changed)
10549 }
10550 ASTNodeType::Array(rows) => rows
10551 .iter()
10552 .flatten()
10553 .any(|node| Self::ast_uses_changed_function(node, changed)),
10554 _ => false,
10555 }
10556 }
10557
10558 fn ast_contains_function(ast: &ASTNode) -> bool {
10559 match &ast.node_type {
10560 ASTNodeType::Function { .. } => true,
10561 ASTNodeType::Call { callee, args } => {
10562 Self::ast_contains_function(callee) || args.iter().any(Self::ast_contains_function)
10563 }
10564 ASTNodeType::UnaryOp { expr, .. } => Self::ast_contains_function(expr),
10565 ASTNodeType::BinaryOp { left, right, .. } => {
10566 Self::ast_contains_function(left) || Self::ast_contains_function(right)
10567 }
10568 ASTNodeType::Array(rows) => rows.iter().flatten().any(Self::ast_contains_function),
10569 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => false,
10570 }
10571 }
10572
10573 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
10574 Region::rows_from(sheet_id, start_row0)
10575 }
10576
10577 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
10578 Region::cols_from(sheet_id, start_col0)
10579 }
10580
10581 #[cfg(test)]
10582 pub(crate) fn force_non_cycle_schedule_fallback_for_test(&mut self) {
10583 self.force_non_cycle_schedule_fallback_for_test = true;
10584 }
10585
10586 fn materialize_deferred_sheet_before_structural_edit(
10587 &mut self,
10588 sheet: &str,
10589 ) -> Result<(), crate::engine::EditorError> {
10590 if self.staged_formulas.contains_key(sheet) {
10591 self.build_graph_for_sheets([sheet])?;
10592 }
10593 Ok(())
10594 }
10595
10596 fn structural_row_occupancy(
10597 &self,
10598 sheet: &str,
10599 sheet_id: SheetId,
10600 ) -> crate::engine::graph::StructuralOccupancy {
10601 if !self.graph.has_compressed_range_readers() {
10602 return crate::engine::graph::StructuralOccupancy::default();
10603 }
10604 let mut occupancy = self.graph.structural_occupancy(sheet_id);
10605 if let Some(arrow_sheet) = self.arrow_sheets.sheet(sheet) {
10606 occupancy.include_arrow_sheet(arrow_sheet);
10607 occupancy
10608 } else {
10609 crate::engine::graph::StructuralOccupancy::conservative()
10611 }
10612 }
10613
10614 fn structural_column_occupancy(&self) -> crate::engine::graph::StructuralOccupancy {
10615 crate::engine::graph::StructuralOccupancy::conservative()
10619 }
10620
10621 pub fn insert_rows(
10623 &mut self,
10624 sheet: &str,
10625 before: u32,
10626 count: u32,
10627 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10628 {
10629 if count == 0 {
10630 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10631 }
10632 self.observe_function_semantic_epoch()
10633 .map_err(crate::engine::EditorError::Excel)?;
10634 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10635 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10636 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10637 let before0 = before.saturating_sub(1);
10638 let affected_region = Self::structural_row_region(sheet_id, before0);
10639 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10640 let summary = {
10641 let mut editor =
10642 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10643 editor.insert_rows(sheet_id, before0, count)?
10644 };
10645 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10646 let before0 = before0 as usize;
10647 asheet.insert_rows(before0, count as usize);
10648 }
10649 self.purge_derived_formats_after_row(sheet_id, before0);
10650 self.mark_moved_formula_vertices_dirty(&summary);
10651 self.clear_computed_overlay_after_row(sheet, before0 as usize);
10652 self.shift_row_visibility_insert(sheet_id, before0, count);
10653 self.record_structural_change(StructuralScope::Region(affected_region));
10654 self.mark_topology_edited();
10655 Ok(summary)
10656 }
10657
10658 pub fn delete_rows(
10660 &mut self,
10661 sheet: &str,
10662 start: u32,
10663 count: u32,
10664 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10665 {
10666 if count == 0 {
10667 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10668 }
10669 self.observe_function_semantic_epoch()
10670 .map_err(crate::engine::EditorError::Excel)?;
10671 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10672 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10673 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10674 let start0 = start.saturating_sub(1);
10675 let affected_region = Self::structural_row_region(sheet_id, start0);
10676 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10677 let summary = {
10678 let mut editor =
10679 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10680 editor.delete_rows(sheet_id, start0, count)?
10681 };
10682 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10683 let start0 = start0 as usize;
10684 asheet.delete_rows(start0, count as usize);
10685 }
10686 self.purge_derived_formats_after_row(sheet_id, start0);
10687 self.mark_moved_formula_vertices_dirty(&summary);
10688 self.clear_computed_overlay_after_row(sheet, start0 as usize);
10689 self.shift_row_visibility_delete(sheet_id, start0, count);
10690 self.record_structural_change(StructuralScope::Region(affected_region));
10691 self.mark_topology_edited();
10692 Ok(summary)
10693 }
10694
10695 pub fn insert_columns(
10697 &mut self,
10698 sheet: &str,
10699 before: u32,
10700 count: u32,
10701 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10702 {
10703 if count == 0 {
10704 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10705 }
10706 self.observe_function_semantic_epoch()
10707 .map_err(crate::engine::EditorError::Excel)?;
10708 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10709 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10710 let sheet_id = self.graph.sheet_id(sheet).ok_or(
10711 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10712 name: sheet.to_string(),
10713 reason: "Unknown sheet".to_string(),
10714 },
10715 )?;
10716 let before0 = before.saturating_sub(1);
10717 let affected_region = Self::structural_col_region(sheet_id, before0);
10718 let occupancy = self.structural_column_occupancy();
10719 let summary = {
10720 let mut editor =
10721 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10722 editor.insert_columns(sheet_id, before0, count)?
10723 };
10724 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10725 let before0 = before0 as usize;
10726 asheet.insert_columns(before0, count as usize);
10727 }
10728 self.purge_derived_formats_after_col(sheet_id, before0);
10729 self.mark_moved_formula_vertices_dirty(&summary);
10730 self.clear_computed_overlay_after_col(sheet, before0 as usize);
10731 self.record_structural_change(StructuralScope::Region(affected_region));
10732 self.mark_topology_edited();
10733 Ok(summary)
10734 }
10735
10736 pub fn delete_columns(
10738 &mut self,
10739 sheet: &str,
10740 start: u32,
10741 count: u32,
10742 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10743 {
10744 if count == 0 {
10745 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10746 }
10747 self.observe_function_semantic_epoch()
10748 .map_err(crate::engine::EditorError::Excel)?;
10749 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10750 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10751 let sheet_id = self.graph.sheet_id(sheet).ok_or(
10752 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10753 name: sheet.to_string(),
10754 reason: "Unknown sheet".to_string(),
10755 },
10756 )?;
10757 let start0 = start.saturating_sub(1);
10758 let affected_region = Self::structural_col_region(sheet_id, start0);
10759 let occupancy = self.structural_column_occupancy();
10760 let summary = {
10761 let mut editor =
10762 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10763 editor.delete_columns(sheet_id, start0, count)?
10764 };
10765 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10766 let start0 = start0 as usize;
10767 asheet.delete_columns(start0, count as usize);
10768 }
10769 self.purge_derived_formats_after_col(sheet_id, start0);
10770 self.mark_moved_formula_vertices_dirty(&summary);
10771 self.clear_computed_overlay_after_col(sheet, start0 as usize);
10772 self.record_structural_change(StructuralScope::Region(affected_region));
10773 self.mark_topology_edited();
10774 Ok(summary)
10775 }
10776 fn arrow_used_row_bounds(
10778 &self,
10779 sheet: &str,
10780 start_col: u32,
10781 end_col: u32,
10782 ) -> Option<(u32, u32)> {
10783 let a = self.sheet_store().sheet(sheet)?;
10784 if a.columns.is_empty() {
10785 return None;
10786 }
10787 let sc0 = start_col.saturating_sub(1) as usize;
10788 let ec0 = end_col.saturating_sub(1) as usize;
10789 let col_hi = a.columns.len().saturating_sub(1);
10790 if sc0 > col_hi {
10791 return None;
10792 }
10793 let ec0 = ec0.min(col_hi);
10794 let snap = self.data_snapshot_id();
10796 let mut min_r0: Option<usize> = None;
10797 for ci in sc0..=ec0 {
10798 let sheet_id = self.graph.sheet_id(sheet)?;
10799 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
10800 g.as_ref()
10801 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
10802 }) {
10803 let mv = mv as usize;
10804 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
10805 continue;
10806 }
10807 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
10809 if let Ok(mut g) = self.row_bounds_cache.write() {
10810 g.get_or_insert_with(|| RowBoundsCache::new(snap))
10811 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
10812 }
10813 if let Some(m) = min_c {
10814 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
10815 }
10816 }
10817 min_r0?;
10818 let mut max_r0: Option<usize> = None;
10819 for ci in sc0..=ec0 {
10820 let sheet_id = self.graph.sheet_id(sheet)?;
10821 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
10822 g.as_ref()
10823 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
10824 }) {
10825 let mv = mv as usize;
10826 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
10827 continue;
10828 }
10829 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
10830 if let Ok(mut g) = self.row_bounds_cache.write() {
10831 g.get_or_insert_with(|| RowBoundsCache::new(snap))
10832 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
10833 }
10834 if let Some(m) = max_c {
10835 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
10836 }
10837 }
10838 match (min_r0, max_r0) {
10839 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
10840 _ => None,
10841 }
10842 }
10843
10844 fn scan_column_used_bounds(
10845 a: &crate::arrow_store::ArrowSheet,
10846 ci: usize,
10847 ) -> (Option<u32>, Option<u32>) {
10848 let col = &a.columns[ci];
10849
10850 let mut min_r0: Option<u32> = None;
10852 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
10853 let tags = chunk.type_tag.values();
10854 for (off, &t) in tags.iter().enumerate() {
10855 let overlay_non_empty = chunk
10856 .overlay
10857 .get(off)
10858 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10859 .unwrap_or(false)
10860 || chunk
10861 .computed_overlay
10862 .get(off)
10863 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10864 .unwrap_or(false);
10865 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10866 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10867 break;
10868 };
10869 let row0 = chunk_start + off;
10870 min_r0 = Some(row0 as u32);
10871 break;
10872 }
10873 }
10874 if min_r0.is_some() {
10875 break;
10876 }
10877 }
10878 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
10879 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
10880 sparse_idxs.sort_unstable();
10881 for chunk_idx in sparse_idxs {
10882 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
10883 continue;
10884 };
10885 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10886 continue;
10887 };
10888 let tags = chunk.type_tag.values();
10889 for (off, &t) in tags.iter().enumerate() {
10890 let overlay_non_empty = chunk
10891 .overlay
10892 .get(off)
10893 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10894 .unwrap_or(false)
10895 || chunk
10896 .computed_overlay
10897 .get(off)
10898 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10899 .unwrap_or(false);
10900 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10901 let row0 = chunk_start + off;
10902 min_r0 = Some(row0 as u32);
10903 break;
10904 }
10905 }
10906 if min_r0.is_some() {
10907 break;
10908 }
10909 }
10910 }
10911
10912 let mut max_r0: Option<u32> = None;
10914 if !col.sparse_chunks.is_empty() {
10915 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
10916 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
10917 for chunk_idx in sparse_idxs {
10918 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
10919 continue;
10920 };
10921 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10922 continue;
10923 };
10924 let tags = chunk.type_tag.values();
10925 for (rev_idx, &t) in tags.iter().enumerate().rev() {
10926 let overlay_non_empty = chunk
10927 .overlay
10928 .get(rev_idx)
10929 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10930 .unwrap_or(false)
10931 || chunk
10932 .computed_overlay
10933 .get(rev_idx)
10934 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10935 .unwrap_or(false);
10936 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10937 let row0 = chunk_start + rev_idx;
10938 max_r0 = Some(row0 as u32);
10939 break;
10940 }
10941 }
10942 if max_r0.is_some() {
10943 break;
10944 }
10945 }
10946 }
10947 if max_r0.is_none() {
10948 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
10949 let tags = chunk.type_tag.values();
10950 for (rev_idx, &t) in tags.iter().enumerate().rev() {
10951 let overlay_non_empty = chunk
10952 .overlay
10953 .get(rev_idx)
10954 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10955 .unwrap_or(false)
10956 || chunk
10957 .computed_overlay
10958 .get(rev_idx)
10959 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10960 .unwrap_or(false);
10961 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10962 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10963 break;
10964 };
10965 let row0 = chunk_start + rev_idx;
10966 max_r0 = Some(row0 as u32);
10967 break;
10968 }
10969 }
10970 if max_r0.is_some() {
10971 break;
10972 }
10973 }
10974 }
10975
10976 (min_r0, max_r0)
10977 }
10978
10979 fn arrow_used_col_bounds(
10981 &self,
10982 sheet: &str,
10983 start_row: u32,
10984 end_row: u32,
10985 ) -> Option<(u32, u32)> {
10986 let a = self.sheet_store().sheet(sheet)?;
10987 if a.columns.is_empty() {
10988 return None;
10989 }
10990 let sr0 = start_row.saturating_sub(1) as usize;
10991 let er0 = end_row.saturating_sub(1) as usize;
10992 if sr0 > er0 {
10993 return None;
10994 }
10995 let mut min_c0: Option<usize> = None;
10998 let mut max_c0: Option<usize> = None;
10999 for (ci, col) in a.columns.iter().enumerate() {
11001 let mut any_in_range = false;
11002
11003 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
11004 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11005 return false;
11006 };
11007 let chunk_len = chunk.type_tag.len();
11008 if chunk_len == 0 {
11009 return false;
11010 }
11011 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
11012 if sr0 > chunk_end || er0 < chunk_start {
11014 return false;
11015 }
11016 let start_off = sr0.max(chunk_start) - chunk_start;
11017 let end_off = er0.min(chunk_end) - chunk_start;
11018 let tags = chunk.type_tag.values();
11019 for off in start_off..=end_off {
11020 let overlay_non_empty = chunk
11021 .overlay
11022 .get(off)
11023 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11024 .unwrap_or(false)
11025 || chunk
11026 .computed_overlay
11027 .get(off)
11028 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11029 .unwrap_or(false);
11030 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
11031 return true;
11032 }
11033 }
11034 false
11035 };
11036
11037 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
11038 if scan_chunk(chunk_idx, chunk) {
11039 any_in_range = true;
11040 break;
11041 }
11042 }
11043
11044 if !any_in_range && !col.sparse_chunks.is_empty() {
11045 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
11046 if scan_chunk(chunk_idx, chunk) {
11047 any_in_range = true;
11048 break;
11049 }
11050 }
11051 }
11052
11053 if any_in_range {
11054 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
11055 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
11056 }
11057 }
11058 match (min_c0, max_c0) {
11059 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
11060 _ => None,
11061 }
11062 }
11063
11064 fn formula_row_bounds_for_columns(
11065 &self,
11066 sheet: &str,
11067 start_col: u32,
11068 end_col: u32,
11069 ) -> Option<(u32, u32)> {
11070 let sheet_id = self.graph.sheet_id(sheet)?;
11071 let sc0 = start_col.saturating_sub(1);
11072 let ec0 = end_col.saturating_sub(1);
11073 let mut min_r0: Option<u32> = None;
11074 let mut max_r0: Option<u32> = None;
11075
11076 if self.graph.sheet_index(sheet_id).is_some() {
11077 for vid in self.graph.vertices_in_cols(sheet_id, sc0, ec0) {
11078 if !matches!(
11079 self.graph.get_vertex_kind(vid),
11080 VertexKind::FormulaScalar | VertexKind::FormulaArray
11081 ) {
11082 continue;
11083 }
11084 let Some(row0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.row()) else {
11085 continue;
11086 };
11087 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11088 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11089 }
11090 } else {
11091 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11092 if !matches!(
11093 self.graph.get_vertex_kind(vid),
11094 VertexKind::FormulaScalar | VertexKind::FormulaArray
11095 ) {
11096 continue;
11097 }
11098 let col0 = coord.col();
11099 if col0 < sc0 || col0 > ec0 {
11100 continue;
11101 }
11102 let row0 = coord.row();
11103 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11104 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11105 }
11106 }
11107
11108 match (min_r0, max_r0) {
11109 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11110 _ => None,
11111 }
11112 }
11113
11114 fn formula_col_bounds_for_rows(
11115 &self,
11116 sheet: &str,
11117 start_row: u32,
11118 end_row: u32,
11119 ) -> Option<(u32, u32)> {
11120 let sheet_id = self.graph.sheet_id(sheet)?;
11121 let sr0 = start_row.saturating_sub(1);
11122 let er0 = end_row.saturating_sub(1);
11123 let mut min_c0: Option<u32> = None;
11124 let mut max_c0: Option<u32> = None;
11125
11126 if self.graph.sheet_index(sheet_id).is_some() {
11127 for vid in self.graph.vertices_in_rows(sheet_id, sr0, er0) {
11128 if !matches!(
11129 self.graph.get_vertex_kind(vid),
11130 VertexKind::FormulaScalar | VertexKind::FormulaArray
11131 ) {
11132 continue;
11133 }
11134 let Some(col0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.col()) else {
11135 continue;
11136 };
11137 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11138 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11139 }
11140 } else {
11141 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11142 if !matches!(
11143 self.graph.get_vertex_kind(vid),
11144 VertexKind::FormulaScalar | VertexKind::FormulaArray
11145 ) {
11146 continue;
11147 }
11148 let row0 = coord.row();
11149 if row0 < sr0 || row0 > er0 {
11150 continue;
11151 }
11152 let col0 = coord.col();
11153 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11154 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11155 }
11156 }
11157
11158 match (min_c0, max_c0) {
11159 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11160 _ => None,
11161 }
11162 }
11163
11164 fn union_used_bounds(
11165 first: Option<(u32, u32)>,
11166 second: Option<(u32, u32)>,
11167 ) -> Option<(u32, u32)> {
11168 match (first, second) {
11169 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
11170 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
11171 (None, None) => None,
11172 }
11173 }
11174
11175 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
11178 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11179 return;
11180 }
11181 if self.arrow_sheets.sheet(sheet).is_none() {
11182 self.arrow_sheets
11183 .sheets
11184 .push(crate::arrow_store::ArrowSheet {
11185 name: std::sync::Arc::<str>::from(sheet),
11186 date_system: self.config.date_system,
11187 columns: Vec::new(),
11188 nrows: 0,
11189 chunk_starts: Vec::new(),
11190 chunk_rows: 32 * 1024,
11191 });
11192 }
11193
11194 let row0 = row.saturating_sub(1) as usize;
11195 let col0 = col.saturating_sub(1) as usize;
11196
11197 let asheet = self
11198 .arrow_sheets
11199 .sheet_mut(sheet)
11200 .expect("ArrowSheet must exist");
11201
11202 let cur_cols = asheet.columns.len();
11203 if col0 >= cur_cols {
11204 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11205 }
11206
11207 if row0 >= asheet.nrows as usize {
11208 if asheet.columns.is_empty() {
11209 asheet.insert_columns(0, 1);
11210 }
11211 asheet.ensure_row_capacity(row0 + 1);
11212 }
11213 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
11214 let ov =
11215 crate::arrow_store::OverlayValue::from_literal_value(value, asheet.date_system);
11216 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
11217 let _ = ch.overlay.set(in_off, ov);
11218 let format = match value {
11219 LiteralValue::Date(_) => Some(crate::format::FormatId::DATE),
11220 LiteralValue::DateTime(_) => Some(crate::format::FormatId::DATETIME),
11221 LiteralValue::Time(_) => Some(crate::format::FormatId::TIME),
11222 LiteralValue::Duration(_) => Some(crate::format::FormatId::DURATION),
11223 _ => None,
11224 };
11225 ch.overlay.set_format(in_off, format);
11226 ch.computed_overlay.remove(in_off)
11231 } else {
11232 return;
11233 };
11234 let abs_threshold = 1024usize;
11236 let frac_den = 50usize;
11237 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
11238 if freed > 0 {
11239 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
11240 }
11241 self.adjust_computed_overlay_bytes(computed_delta);
11242 }
11243 }
11244
11245 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
11250 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11251 return;
11252 }
11253 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11254 return;
11255 };
11256 let row0 = row.saturating_sub(1) as usize;
11257 let col0 = col.saturating_sub(1) as usize;
11258 if row0 >= asheet.nrows as usize {
11259 return;
11260 }
11261 if col0 >= asheet.columns.len() {
11262 return;
11263 }
11264 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11265 return;
11266 };
11267 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
11268 let _ = ch.overlay.remove(in_off);
11269 }
11270 }
11271
11272 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
11273 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11274 return;
11275 }
11276
11277 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11278 return;
11279 };
11280 if start_row0 >= asheet.nrows as usize {
11281 return;
11282 }
11283
11284 let starts = asheet.chunk_starts.clone();
11285 let nrows = asheet.nrows as usize;
11286 let mut delta = 0isize;
11287 for col in &mut asheet.columns {
11288 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
11289 let Some(&chunk_start) = starts.get(chunk_idx) else {
11290 continue;
11291 };
11292 let chunk_end = starts
11293 .get(chunk_idx + 1)
11294 .copied()
11295 .unwrap_or(nrows)
11296 .min(chunk_start.saturating_add(ch.len()));
11297 if chunk_end <= start_row0 {
11298 continue;
11299 }
11300 if chunk_start >= start_row0 {
11301 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11302 } else {
11303 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11304 delta = delta
11305 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11306 }
11307 }
11308
11309 for (chunk_idx, ch) in &mut col.sparse_chunks {
11310 let Some(&chunk_start) = starts.get(*chunk_idx) else {
11311 continue;
11312 };
11313 let chunk_end = starts
11314 .get(*chunk_idx + 1)
11315 .copied()
11316 .unwrap_or(nrows)
11317 .min(chunk_start.saturating_add(ch.len()));
11318 if chunk_end <= start_row0 {
11319 continue;
11320 }
11321 if chunk_start >= start_row0 {
11322 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11323 } else {
11324 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11325 delta = delta
11326 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11327 }
11328 }
11329 }
11330 self.adjust_computed_overlay_bytes(delta);
11331 }
11332
11333 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
11334 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11335 return;
11336 }
11337
11338 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11339 return;
11340 };
11341 if start_col0 >= asheet.columns.len() {
11342 return;
11343 }
11344
11345 let mut delta = 0isize;
11346 for col in asheet.columns.iter_mut().skip(start_col0) {
11347 for ch in &mut col.chunks {
11348 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11349 }
11350 for ch in col.sparse_chunks.values_mut() {
11351 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11352 }
11353 }
11354 self.adjust_computed_overlay_bytes(delta);
11355 }
11356
11357 #[inline]
11358 fn literal_to_overlay_value(
11359 value: &LiteralValue,
11360 date_system: crate::engine::DateSystem,
11361 ) -> crate::arrow_store::OverlayValue {
11362 crate::arrow_store::OverlayValue::from_literal_value(value, date_system)
11363 }
11364
11365 fn arrow_sheet_date_system(&self, sheet: &str) -> crate::engine::DateSystem {
11366 self.arrow_sheets
11367 .sheet(sheet)
11368 .map(|sheet| sheet.date_system)
11369 .unwrap_or(self.config.date_system)
11370 }
11371
11372 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11375 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11376 return None;
11377 }
11378 let asheet = self.arrow_sheets.sheet(sheet)?;
11379 let row0 = row.saturating_sub(1) as usize;
11380 let col0 = col.saturating_sub(1) as usize;
11381 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11382 return None;
11383 }
11384 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11385 let ch = asheet.columns[col0].chunk(ch_idx)?;
11386 ch.overlay
11387 .get_scalar(in_off)
11388 .map(|ov| ov.to_literal_for(asheet.date_system))
11389 }
11390
11391 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11394 if !(self.config.arrow_storage_enabled
11395 && self.config.delta_overlay_enabled
11396 && self.config.write_formula_overlay_enabled)
11397 {
11398 return None;
11399 }
11400 let asheet = self.arrow_sheets.sheet(sheet)?;
11401 let row0 = row.saturating_sub(1) as usize;
11402 let col0 = col.saturating_sub(1) as usize;
11403 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11404 return None;
11405 }
11406 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11407 let ch = asheet.columns[col0].chunk(ch_idx)?;
11408 ch.computed_overlay
11409 .get_scalar(in_off)
11410 .map(|ov| ov.to_literal_for(asheet.date_system))
11411 }
11412
11413 fn set_delta_overlay_cell_raw(
11414 &mut self,
11415 sheet: &str,
11416 row: u32,
11417 col: u32,
11418 value: Option<LiteralValue>,
11419 ) {
11420 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11421 return;
11422 }
11423
11424 self.ensure_arrow_sheet(sheet);
11425 let date_system = self.arrow_sheet_date_system(sheet);
11426 let ov_opt = value
11427 .as_ref()
11428 .map(|value| Self::literal_to_overlay_value(value, date_system));
11429 let row0 = row.saturating_sub(1) as usize;
11430 let col0 = col.saturating_sub(1) as usize;
11431 let asheet = self
11432 .arrow_sheets
11433 .sheet_mut(sheet)
11434 .expect("ArrowSheet must exist");
11435
11436 let cur_cols = asheet.columns.len();
11437 if col0 >= cur_cols {
11438 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11439 }
11440 if row0 >= asheet.nrows as usize {
11441 if asheet.columns.is_empty() {
11442 asheet.insert_columns(0, 1);
11443 }
11444 asheet.ensure_row_capacity(row0 + 1);
11445 }
11446
11447 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11448 return;
11449 };
11450 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11451 return;
11452 };
11453
11454 if let Some(ov) = ov_opt {
11455 let _ = ch.overlay.set(in_off, ov);
11456 } else {
11457 let _ = ch.overlay.remove(in_off);
11458 }
11459 }
11460
11461 fn set_computed_overlay_cell_raw(
11462 &mut self,
11463 sheet: &str,
11464 row: u32,
11465 col: u32,
11466 value: Option<LiteralValue>,
11467 ) {
11468 if !(self.config.arrow_storage_enabled
11469 && self.config.delta_overlay_enabled
11470 && self.config.write_formula_overlay_enabled)
11471 {
11472 return;
11473 }
11474
11475 self.ensure_arrow_sheet(sheet);
11476 let date_system = self.arrow_sheet_date_system(sheet);
11477 let ov_opt = value
11478 .as_ref()
11479 .map(|value| Self::literal_to_overlay_value(value, date_system));
11480 let row0 = row.saturating_sub(1) as usize;
11481 let col0 = col.saturating_sub(1) as usize;
11482 let asheet = self
11483 .arrow_sheets
11484 .sheet_mut(sheet)
11485 .expect("ArrowSheet must exist");
11486
11487 let cur_cols = asheet.columns.len();
11488 if col0 >= cur_cols {
11489 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11490 }
11491 if row0 >= asheet.nrows as usize {
11492 if asheet.columns.is_empty() {
11493 asheet.insert_columns(0, 1);
11494 }
11495 asheet.ensure_row_capacity(row0 + 1);
11496 }
11497
11498 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11499 return;
11500 };
11501 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11502 return;
11503 };
11504
11505 let delta = if let Some(ov) = ov_opt {
11506 ch.computed_overlay.set(in_off, ov)
11507 } else {
11508 ch.computed_overlay.remove(in_off)
11509 };
11510 self.adjust_computed_overlay_bytes(delta);
11511 }
11512
11513 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
11514 use crate::engine::ArrowOp;
11515
11516 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
11517 Box::new(batch.ops.iter().rev())
11518 } else {
11519 Box::new(batch.ops.iter())
11520 };
11521
11522 for op in iter {
11523 match op {
11524 ArrowOp::SetDeltaCell {
11525 sheet_id,
11526 row0,
11527 col0,
11528 old,
11529 new,
11530 } => {
11531 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11532 let v = if undo { old.clone() } else { new.clone() };
11533 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11534 }
11535 ArrowOp::SetComputedCell {
11536 sheet_id,
11537 row0,
11538 col0,
11539 old,
11540 new,
11541 } => {
11542 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11543 let v = if undo { old.clone() } else { new.clone() };
11544 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11545 }
11546 ArrowOp::RestoreComputedRect {
11547 sheet_id,
11548 sr0,
11549 sc0,
11550 er0,
11551 ec0,
11552 old,
11553 new,
11554 } => {
11555 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11556 let vals = if undo { old } else { new };
11557 let height = (*er0).saturating_sub(*sr0) as usize + 1;
11558 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
11559 for r in 0..height {
11560 for c in 0..width {
11561 let v = vals
11562 .get(r)
11563 .and_then(|row| row.get(c))
11564 .cloned()
11565 .unwrap_or(LiteralValue::Empty);
11566 self.set_computed_overlay_cell_raw(
11567 &sheet,
11568 *sr0 + 1 + r as u32,
11569 *sc0 + 1 + c as u32,
11570 Some(v),
11571 );
11572 }
11573 }
11574 }
11575 ArrowOp::InsertRows {
11576 sheet_id,
11577 before0,
11578 count,
11579 } => {
11580 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11581 self.ensure_arrow_sheet(&sheet);
11582 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11583 if undo {
11584 asheet.delete_rows(*before0 as usize, *count as usize);
11585 } else {
11586 asheet.insert_rows(*before0 as usize, *count as usize);
11587 }
11588 }
11589 self.purge_derived_formats_after_row(*sheet_id, *before0);
11590 }
11591 ArrowOp::InsertCols {
11592 sheet_id,
11593 before0,
11594 count,
11595 } => {
11596 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11597 self.ensure_arrow_sheet(&sheet);
11598 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11599 if undo {
11600 asheet.delete_columns(*before0 as usize, *count as usize);
11601 } else {
11602 asheet.insert_columns(*before0 as usize, *count as usize);
11603 }
11604 }
11605 self.purge_derived_formats_after_col(*sheet_id, *before0);
11606 }
11607 }
11608 }
11609 }
11610
11611 fn record_spill_ops_into_arrow_undo(
11612 &mut self,
11613 undo: &mut crate::engine::ArrowUndoBatch,
11614 events: &[crate::engine::ChangeEvent],
11615 ) {
11616 use crate::engine::ChangeEvent;
11617 use formualizer_common::LiteralValue;
11618
11619 #[allow(clippy::type_complexity)]
11620 let rect_from_snapshot =
11621 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
11622 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
11623 if snap.target_cells.is_empty() {
11624 return None;
11625 }
11626 let sheet_id = snap.target_cells[0].sheet_id;
11627 let sr0 = snap.target_cells[0].coord.row();
11628 let sc0 = snap.target_cells[0].coord.col();
11629 if snap.values.is_empty() || snap.values[0].is_empty() {
11630 return None;
11631 }
11632 let h = snap.values.len() as u32;
11633 let w = snap.values[0].len() as u32;
11634 let er0 = sr0.saturating_add(h.saturating_sub(1));
11635 let ec0 = sc0.saturating_add(w.saturating_sub(1));
11636 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
11637 };
11638
11639 for ev in events {
11640 match ev {
11641 ChangeEvent::SpillCommitted { old, new, .. } => {
11642 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
11643 let old_vals = if let Some(old_snap) = old {
11644 rect_from_snapshot(old_snap)
11645 .map(|(_, _, _, _, _, v)| v)
11646 .unwrap_or_else(|| {
11647 vec![
11648 vec![LiteralValue::Empty; new_vals[0].len()];
11649 new_vals.len()
11650 ]
11651 })
11652 } else {
11653 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
11654 };
11655 undo.record_restore_computed_rect(
11656 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11657 );
11658 }
11659 }
11660 ChangeEvent::SpillCleared { old, .. } => {
11661 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
11662 let new_vals =
11663 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
11664 undo.record_restore_computed_rect(
11665 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11666 );
11667 }
11668 }
11669 _ => {}
11670 }
11671 }
11672 }
11673
11674 fn mirror_value_to_computed_overlay(
11679 &mut self,
11680 sheet: &str,
11681 row: u32,
11682 col: u32,
11683 value: &LiteralValue,
11684 ) {
11685 if !(self.config.arrow_storage_enabled
11686 && self.config.delta_overlay_enabled
11687 && self.config.write_formula_overlay_enabled)
11688 {
11689 return;
11690 }
11691 if self.computed_overlay_mirroring_disabled {
11692 return;
11693 }
11694
11695 let date_system = self.arrow_sheet_date_system(sheet);
11696 let ov = Self::literal_to_overlay_value(value, date_system);
11697 self.write_computed_overlay_value_0based(
11698 sheet,
11699 row.saturating_sub(1),
11700 col.saturating_sub(1),
11701 ov,
11702 );
11703 }
11704
11705 fn record_derived_format(&self, vertex_id: VertexId, format: Option<crate::format::FormatId>) {
11706 if let Some(cell) = self.graph.get_cell_ref(vertex_id) {
11707 self.record_derived_format_at(cell, format);
11708 }
11709 }
11710
11711 fn record_derived_format_at(&self, cell: CellRef, format: Option<crate::format::FormatId>) {
11712 #[cfg(test)]
11713 self.derived_format_operations_for_test
11714 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
11715 let format = format.filter(|id| *id != crate::format::FormatId::GENERAL);
11716 self.derived_formats.set(cell, format);
11717 }
11718
11719 fn clear_cell_format_state(&mut self, sheet: &str, cell: CellRef) {
11720 self.derived_formats.set(cell, None);
11721 if let Some(arrow) = self.arrow_sheets.sheet_mut(sheet) {
11722 arrow.clear_format(cell.coord.row() as usize, cell.coord.col() as usize);
11723 }
11724 }
11725
11726 fn clear_logged_cell_format_states(&mut self, events: &[ChangeEvent]) {
11727 let cells = events
11728 .iter()
11729 .filter_map(|event| match event {
11730 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
11731 Some(*addr)
11732 }
11733 _ => None,
11734 })
11735 .collect::<FxHashSet<_>>();
11736 for cell in cells {
11737 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
11738 self.clear_cell_format_state(&sheet, cell);
11739 }
11740 }
11741
11742 fn purge_derived_formats_after_row(&mut self, sheet_id: SheetId, start0: u32) {
11743 self.derived_formats
11744 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.row() < start0);
11745 }
11746
11747 fn purge_derived_formats_after_col(&mut self, sheet_id: SheetId, start0: u32) {
11748 self.derived_formats
11749 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.col() < start0);
11750 }
11751
11752 fn purge_derived_formats_for_sheet(&mut self, sheet_id: SheetId) {
11753 self.derived_formats
11754 .retain(|cell| cell.sheet_id != sheet_id);
11755 }
11756
11757 #[cfg(test)]
11758 pub(crate) fn debug_computed_overlay_format_0based(
11759 &self,
11760 sheet: &str,
11761 row0: u32,
11762 col0: u32,
11763 ) -> Option<crate::format::FormatId> {
11764 let sheet = self.arrow_sheets.sheet(sheet)?;
11765 let (chunk_idx, row_in_chunk) = sheet.chunk_of_row(row0 as usize)?;
11766 sheet
11767 .columns
11768 .get(col0 as usize)?
11769 .chunk(chunk_idx)?
11770 .computed_overlay
11771 .get_format(row_in_chunk)
11772 }
11773
11774 #[cfg(test)]
11775 pub(crate) fn debug_computed_overlay_chunk_has_formats_0based(
11776 &self,
11777 sheet: &str,
11778 row0: u32,
11779 col0: u32,
11780 ) -> bool {
11781 let Some(sheet) = self.arrow_sheets.sheet(sheet) else {
11782 return false;
11783 };
11784 let Some((chunk_idx, _)) = sheet.chunk_of_row(row0 as usize) else {
11785 return false;
11786 };
11787 sheet
11788 .columns
11789 .get(col0 as usize)
11790 .and_then(|column| column.chunk(chunk_idx))
11791 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
11792 }
11793
11794 #[cfg(test)]
11795 pub(crate) fn debug_clear_derived_format_0based(&mut self, sheet: &str, row0: u32, col0: u32) {
11796 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
11797 self.derived_formats
11798 .set(CellRef::new_absolute(sheet_id, row0, col0), None);
11799 }
11800 }
11801
11802 #[cfg(test)]
11803 pub(crate) fn debug_record_derived_format_0based(
11804 &self,
11805 sheet: &str,
11806 row0: u32,
11807 col0: u32,
11808 format: Option<crate::format::FormatId>,
11809 ) {
11810 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
11811 self.record_derived_format_at(CellRef::new_absolute(sheet_id, row0, col0), format);
11812 }
11813 }
11814
11815 #[cfg(test)]
11816 pub(crate) fn debug_derived_format_0based(
11817 &self,
11818 sheet: &str,
11819 row0: u32,
11820 col0: u32,
11821 ) -> Option<crate::format::FormatId> {
11822 let sheet_id = self.graph.sheet_id(sheet)?;
11823 self.derived_formats
11824 .get(&CellRef::new_absolute(sheet_id, row0, col0))
11825 }
11826
11827 #[cfg(test)]
11828 pub(crate) fn debug_reset_format_write_operation_counts(&mut self) {
11829 self.derived_format_operations_for_test
11830 .store(0, std::sync::atomic::Ordering::Relaxed);
11831 self.computed_overlay_set_explicit_entry_operations_for_test = 0;
11832 self.computed_overlay_stale_clear_range_effects_for_test = 0;
11833 self.computed_overlay_stale_clear_offset_attempts_for_test = 0;
11834 self.computed_format_vector_allocations_for_test
11835 .store(0, std::sync::atomic::Ordering::Relaxed);
11836 }
11837
11838 #[cfg(test)]
11839 pub(crate) fn debug_format_write_operation_counts(&self) -> (u64, u64, u64, u64, u64) {
11840 (
11841 self.derived_format_operations_for_test
11842 .load(std::sync::atomic::Ordering::Relaxed),
11843 self.computed_overlay_set_explicit_entry_operations_for_test,
11844 self.computed_format_vector_allocations_for_test
11845 .load(std::sync::atomic::Ordering::Relaxed),
11846 self.computed_overlay_stale_clear_range_effects_for_test,
11847 self.computed_overlay_stale_clear_offset_attempts_for_test,
11848 )
11849 }
11850
11851 fn write_computed_overlay_format_0based(
11852 &mut self,
11853 sheet: &str,
11854 row0: u32,
11855 col0: u32,
11856 format: Option<crate::format::FormatId>,
11857 ) {
11858 self.ensure_arrow_sheet(sheet);
11859 let (row0, col0) = (row0 as usize, col0 as usize);
11860 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11861 return;
11862 };
11863 if col0 >= asheet.columns.len() {
11864 asheet.insert_columns(asheet.columns.len(), col0 + 1 - asheet.columns.len());
11865 }
11866 if row0 >= asheet.nrows as usize {
11867 asheet.ensure_row_capacity(row0 + 1);
11868 }
11869 let Some((chunk, offset)) = asheet.chunk_of_row(row0) else {
11870 return;
11871 };
11872 if let Some(chunk) = asheet.ensure_column_chunk_mut(col0, chunk) {
11873 chunk.computed_overlay.set_format(offset, format);
11874 }
11875 }
11876
11877 fn write_computed_cell_0based(
11882 &mut self,
11883 cell: CellRef,
11884 value: &LiteralValue,
11885 format: Option<crate::format::FormatId>,
11886 ) {
11887 if !(self.config.arrow_storage_enabled
11888 && self.config.delta_overlay_enabled
11889 && self.config.write_formula_overlay_enabled)
11890 || self.computed_overlay_mirroring_disabled
11891 {
11892 return;
11893 }
11894 let sheet = self.graph.sheet_name(cell.sheet_id);
11895 let index = match self
11896 .arrow_sheets
11897 .sheets
11898 .iter()
11899 .position(|s| s.name.as_ref() == sheet)
11900 {
11901 Some(index) => index,
11902 None => {
11903 let sheet = sheet.to_string();
11904 self.ensure_arrow_sheet(&sheet);
11905 self.arrow_sheets.sheets.len() - 1
11906 }
11907 };
11908 let (row0, col0) = (cell.coord.row() as usize, cell.coord.col() as usize);
11909 let asheet = &mut self.arrow_sheets.sheets[index];
11910 let ov = Self::literal_to_overlay_value(value, asheet.date_system);
11911 let cur_cols = asheet.columns.len();
11912 if col0 >= cur_cols {
11913 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11914 }
11915 if row0 >= asheet.nrows as usize {
11916 asheet.ensure_row_capacity(row0 + 1);
11917 }
11918 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11919 return;
11920 };
11921 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11922 return;
11923 };
11924 let delta = ch.computed_overlay.set_scalar(in_off, ov);
11925 ch.computed_overlay.set_format(in_off, format);
11926 self.adjust_computed_overlay_bytes(delta);
11927 if let Some(cap) = self.config.max_overlay_memory_bytes
11928 && self.computed_overlay_bytes_estimate > cap
11929 {
11930 self.disable_computed_overlay_mirroring_due_to_budget(cap);
11931 }
11932 }
11933
11934 fn write_computed_overlay_value_0based(
11935 &mut self,
11936 sheet: &str,
11937 row0: u32,
11938 col0: u32,
11939 value: OverlayValue,
11940 ) {
11941 if !(self.config.arrow_storage_enabled
11942 && self.config.delta_overlay_enabled
11943 && self.config.write_formula_overlay_enabled)
11944 {
11945 return;
11946 }
11947 if self.computed_overlay_mirroring_disabled {
11948 return;
11949 }
11950
11951 self.ensure_arrow_sheet(sheet);
11952
11953 let row0 = row0 as usize;
11954 let col0 = col0 as usize;
11955 let asheet = self
11956 .arrow_sheets
11957 .sheet_mut(sheet)
11958 .expect("ArrowSheet must exist");
11959
11960 let cur_cols = asheet.columns.len();
11961 if col0 >= cur_cols {
11962 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11963 }
11964
11965 if row0 >= asheet.nrows as usize {
11966 if asheet.columns.is_empty() {
11967 asheet.insert_columns(0, 1);
11968 }
11969 asheet.ensure_row_capacity(row0 + 1);
11970 }
11971
11972 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11973 return;
11974 };
11975 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11976 return;
11977 };
11978
11979 let delta = ch.computed_overlay.set_scalar(in_off, value);
11980 self.adjust_computed_overlay_bytes(delta);
11981
11982 if let Some(cap) = self.config.max_overlay_memory_bytes
11983 && self.computed_overlay_bytes_estimate > cap
11984 {
11985 self.disable_computed_overlay_mirroring_due_to_budget(cap);
11986 }
11987 }
11988
11989 pub(crate) fn plan_computed_write_coalescing(
11990 &self,
11991 buffer: &ComputedWriteBuffer,
11992 ) -> ComputedWriteCoalescingPlan {
11993 self.plan_computed_write_coalescing_from_writes(
11994 buffer.writes().iter().cloned(),
11995 buffer.formats_present,
11996 )
11997 }
11998
11999 fn plan_owned_computed_write_coalescing(
12000 &self,
12001 writes: Vec<ComputedWrite>,
12002 formats_present: bool,
12003 ) -> ComputedWriteCoalescingPlan {
12004 self.plan_computed_write_coalescing_from_writes(writes, formats_present)
12005 }
12006
12007 fn plan_computed_write_coalescing_from_writes(
12008 &self,
12009 writes: impl IntoIterator<Item = ComputedWrite>,
12010 formats_present: bool,
12011 ) -> ComputedWriteCoalescingPlan {
12012 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
12013 BTreeMap::new();
12014 let mut input_cells = 0usize;
12015 let mut located: Option<(SheetId, Option<&crate::arrow_store::ArrowSheet>)> = None;
12017
12018 for write in writes {
12019 match write {
12020 ComputedWrite::Cell {
12021 seq,
12022 sheet_id,
12023 row0,
12024 col0,
12025 value,
12026 format_id,
12027 } => {
12028 input_cells = input_cells.saturating_add(1);
12029 let sheet = match located {
12030 Some((id, sheet)) if id == sheet_id => sheet,
12031 _ => {
12032 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12033 located = Some((sheet_id, sheet));
12034 sheet
12035 }
12036 };
12037 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12038 Some(sheet) => {
12039 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize)
12040 }
12041 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12042 row0 as usize,
12043 32 * 1024,
12044 ),
12045 };
12046 groups
12047 .entry(ComputedWriteChunkKey {
12048 sheet_id,
12049 col0,
12050 chunk_idx,
12051 chunk_start_row0,
12052 })
12053 .or_default()
12054 .push(ComputedWriteChunkEntryPlan {
12055 row_in_chunk,
12056 seq,
12057 value,
12058 format_id,
12059 });
12060 }
12061 ComputedWrite::Run {
12062 seq,
12063 sheet_id,
12064 row0,
12065 col0,
12066 entries,
12067 } => {
12068 input_cells = input_cells.saturating_add(entries.len());
12069 let sheet = match located {
12070 Some((id, sheet)) if id == sheet_id => sheet,
12071 _ => {
12072 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12073 located = Some((sheet_id, sheet));
12074 sheet
12075 }
12076 };
12077 let mut segment: Vec<ComputedWriteChunkEntryPlan> = Vec::new();
12081 let mut segment_key: Option<ComputedWriteChunkKey> = None;
12082 for (k, (value, format_id)) in entries.into_iter().enumerate() {
12083 let row = row0.saturating_add(k as u32) as usize;
12084 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12085 Some(sheet) => {
12086 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row)
12087 }
12088 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12089 row,
12090 32 * 1024,
12091 ),
12092 };
12093 let key = ComputedWriteChunkKey {
12094 sheet_id,
12095 col0,
12096 chunk_idx,
12097 chunk_start_row0,
12098 };
12099 if segment_key != Some(key)
12100 && let Some(done) = segment_key.replace(key)
12101 {
12102 groups.entry(done).or_default().append(&mut segment);
12103 }
12104 segment.push(ComputedWriteChunkEntryPlan {
12105 row_in_chunk,
12106 seq,
12107 value,
12108 format_id,
12109 });
12110 }
12111 if let Some(done) = segment_key {
12112 groups.entry(done).or_default().append(&mut segment);
12113 }
12114 }
12115 ComputedWrite::Rect {
12116 seq,
12117 sheet_id,
12118 sr0,
12119 sc0,
12120 values,
12121 } => {
12122 for (r_off, row) in values.into_iter().enumerate() {
12123 for (c_off, value) in row.into_iter().enumerate() {
12124 input_cells = input_cells.saturating_add(1);
12125 self.push_computed_write_plan_entry(
12126 &mut groups,
12127 seq,
12128 sheet_id,
12129 sr0.saturating_add(r_off as u32),
12130 sc0.saturating_add(c_off as u32),
12131 value,
12132 None,
12133 );
12134 }
12135 }
12136 }
12137 }
12138 }
12139
12140 let mut plan = ComputedWriteCoalescingPlan {
12141 chunks: Vec::with_capacity(groups.len()),
12142 input_cells,
12143 coalesced_cells: 0,
12144 overwritten_cells: 0,
12145 };
12146 for (key, entries) in groups {
12147 let computed_lane_has_formats =
12148 self.computed_overlay_chunk_has_formats(key.sheet_id, key.col0, key.chunk_idx);
12149 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(
12150 key,
12151 entries,
12152 formats_present,
12153 computed_lane_has_formats,
12154 );
12155 #[cfg(test)]
12156 if matches!(
12157 &chunk_plan.format_effect,
12158 ComputedWriteChunkFormatEffect::SetExplicit(_)
12159 ) {
12160 self.computed_format_vector_allocations_for_test
12161 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
12162 }
12163 plan.coalesced_cells = plan
12164 .coalesced_cells
12165 .saturating_add(chunk_plan.entries.len());
12166 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
12167 plan.chunks.push(chunk_plan);
12168 }
12169 debug_assert_eq!(
12170 plan.input_cells,
12171 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
12172 );
12173 plan
12174 }
12175
12176 fn push_computed_write_plan_entry(
12177 &self,
12178 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
12179 seq: u64,
12180 sheet_id: SheetId,
12181 row0: u32,
12182 col0: u32,
12183 value: OverlayValue,
12184 format_id: Option<crate::format::FormatId>,
12185 ) {
12186 let (chunk_idx, chunk_start_row0, row_in_chunk) =
12187 self.locate_computed_write_chunk(sheet_id, row0);
12188 let key = ComputedWriteChunkKey {
12189 sheet_id,
12190 col0,
12191 chunk_idx,
12192 chunk_start_row0,
12193 };
12194 groups
12195 .entry(key)
12196 .or_default()
12197 .push(ComputedWriteChunkEntryPlan {
12198 row_in_chunk,
12199 seq,
12200 value,
12201 format_id,
12202 });
12203 }
12204
12205 fn computed_overlay_chunk_has_formats(
12206 &self,
12207 sheet_id: SheetId,
12208 col0: u32,
12209 chunk_idx: usize,
12210 ) -> bool {
12211 let sheet_name = self.graph.sheet_name(sheet_id);
12212 self.arrow_sheets
12213 .sheet(sheet_name)
12214 .and_then(|sheet| sheet.columns.get(col0 as usize))
12215 .and_then(|column| column.chunk(chunk_idx))
12216 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
12217 }
12218
12219 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
12220 let sheet_name = self.graph.sheet_name(sheet_id);
12221 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
12222 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
12223 }
12224 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
12225 }
12226
12227 fn locate_row_in_sheet_for_computed_write_plan(
12228 sheet: &crate::arrow_store::ArrowSheet,
12229 row0: usize,
12230 ) -> (usize, u32, usize) {
12231 if row0 < sheet.nrows as usize
12232 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
12233 {
12234 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
12235 return (chunk_idx, chunk_start as u32, row_in_chunk);
12236 }
12237
12238 let chunk_rows = sheet.chunk_rows.max(1);
12239 if sheet.chunk_starts.is_empty() {
12240 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
12241 }
12242
12243 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
12244 let mut chunk_start = sheet.chunk_starts[chunk_idx];
12245 while chunk_start.saturating_add(chunk_rows) <= row0 {
12246 chunk_idx = chunk_idx.saturating_add(1);
12247 chunk_start = chunk_start.saturating_add(chunk_rows);
12248 }
12249 (
12250 chunk_idx,
12251 chunk_start as u32,
12252 row0.saturating_sub(chunk_start),
12253 )
12254 }
12255
12256 fn locate_row_in_empty_sheet_for_computed_write_plan(
12257 row0: usize,
12258 chunk_rows: usize,
12259 ) -> (usize, u32, usize) {
12260 let chunk_rows = chunk_rows.max(1);
12261 let chunk_idx = row0 / chunk_rows;
12262 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
12263 (
12264 chunk_idx,
12265 chunk_start as u32,
12266 row0.saturating_sub(chunk_start),
12267 )
12268 }
12269
12270 #[cfg(test)]
12271 pub(crate) fn debug_plan_computed_write_coalescing(
12272 &self,
12273 buffer: &ComputedWriteBuffer,
12274 ) -> ComputedWriteCoalescingPlan {
12275 self.plan_computed_write_coalescing(buffer)
12276 }
12277
12278 pub(crate) fn flush_computed_write_buffer(
12279 &mut self,
12280 buffer: &mut ComputedWriteBuffer,
12281 ) -> Result<(), ExcelError> {
12282 if buffer.is_empty() {
12283 return Ok(());
12284 }
12285
12286 self.resource_checkpoint(0)?;
12291 let commit_started = self.preflight_evaluation_commit_window(buffer.len())?;
12292 let (writes, formats_present) = buffer.take_writes();
12293 let plan = self.plan_owned_computed_write_coalescing(writes, formats_present);
12294 self.flush_computed_write_plan(plan);
12295 self.observe_evaluation_commit_window(commit_started);
12296
12297 Ok(())
12298 }
12299
12300 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
12301 for chunk in plan.chunks {
12302 self.flush_computed_write_chunk_plan(chunk);
12303 }
12304 }
12305
12306 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
12307 match &chunk.shape {
12308 ComputedWriteChunkPlanShape::Point => {
12309 self.flush_computed_write_chunk_plan_as_points(chunk);
12310 }
12311 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
12312 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
12313 }
12314 ComputedWriteChunkPlanShape::DenseRange { .. } => {
12315 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12316 }
12317 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
12318 if Self::should_emit_computed_run_fragment(*len, *runs) {
12319 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
12320 } else {
12321 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12322 }
12323 }
12324 }
12325 }
12326
12327 #[inline]
12328 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
12329 runs <= len / 2
12330 }
12331
12332 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
12333 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
12334 for entry in chunk.entries {
12335 let row0 = chunk
12336 .chunk_start_row0
12337 .saturating_add(entry.row_in_chunk as u32);
12338 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
12339 }
12340 self.apply_computed_overlay_format_effect(
12341 chunk.sheet_id,
12342 chunk.col0,
12343 chunk.chunk_idx,
12344 chunk.format_effect,
12345 );
12346 }
12347
12348 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
12349 &mut self,
12350 chunk: ComputedWriteChunkPlan,
12351 ) {
12352 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
12353 let sheet_id = chunk.sheet_id;
12354 let col0 = chunk.col0;
12355 let chunk_idx = chunk.chunk_idx;
12356 let chunk_start_row0 = chunk.chunk_start_row0;
12357 let format_effect = chunk.format_effect;
12358 let items: Vec<(usize, OverlayValue)> = chunk
12359 .entries
12360 .into_iter()
12361 .map(|entry| (entry.row_in_chunk, entry.value))
12362 .collect();
12363 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
12364 items,
12365 point_estimate,
12366 ) {
12367 Some(Ok(fragment)) => {
12368 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
12369 }
12370 Some(Err(cells)) => {
12371 self.flush_computed_overlay_cells_as_points(
12372 sheet_id,
12373 col0,
12374 chunk_start_row0,
12375 cells,
12376 );
12377 }
12378 None => {}
12379 }
12380 self.apply_computed_overlay_format_effect(sheet_id, col0, chunk_idx, format_effect);
12381 }
12382
12383 #[inline]
12384 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
12385 chunk
12386 .entries
12387 .iter()
12388 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
12389 .fold(0usize, usize::saturating_add)
12390 }
12391
12392 fn flush_computed_overlay_cells_as_points(
12393 &mut self,
12394 sheet_id: SheetId,
12395 col0: u32,
12396 chunk_start_row0: u32,
12397 cells: Vec<(usize, OverlayValue)>,
12398 ) {
12399 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12400 for (row_in_chunk, value) in cells {
12401 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
12402 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
12403 }
12404 }
12405
12406 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12407 if chunk.entries.is_empty() {
12408 return;
12409 }
12410 let start = chunk.entries[0].row_in_chunk;
12411 let values: Vec<OverlayValue> =
12412 chunk.entries.into_iter().map(|entry| entry.value).collect();
12413 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
12414 self.apply_computed_overlay_fragment(
12415 chunk.sheet_id,
12416 chunk.col0,
12417 chunk.chunk_idx,
12418 fragment,
12419 );
12420 }
12421 self.apply_computed_overlay_format_effect(
12422 chunk.sheet_id,
12423 chunk.col0,
12424 chunk.chunk_idx,
12425 chunk.format_effect,
12426 );
12427 }
12428
12429 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12430 if chunk.entries.is_empty() {
12431 return;
12432 }
12433 let start = chunk.entries[0].row_in_chunk;
12434 let values: Vec<OverlayValue> =
12435 chunk.entries.into_iter().map(|entry| entry.value).collect();
12436 if let Some(fragment) = OverlayFragment::run_range(start, values) {
12437 self.apply_computed_overlay_fragment(
12438 chunk.sheet_id,
12439 chunk.col0,
12440 chunk.chunk_idx,
12441 fragment,
12442 );
12443 }
12444 self.apply_computed_overlay_format_effect(
12445 chunk.sheet_id,
12446 chunk.col0,
12447 chunk.chunk_idx,
12448 chunk.format_effect,
12449 );
12450 }
12451
12452 fn apply_computed_overlay_format_effect(
12453 &mut self,
12454 sheet_id: SheetId,
12455 col0: u32,
12456 chunk_idx: usize,
12457 effect: ComputedWriteChunkFormatEffect,
12458 ) {
12459 if !(self.config.arrow_storage_enabled
12460 && self.config.delta_overlay_enabled
12461 && self.config.write_formula_overlay_enabled)
12462 || self.computed_overlay_mirroring_disabled
12463 {
12464 return;
12465 }
12466
12467 let sheet_name = self.graph.sheet_name(sheet_id);
12468 let Some(sheet) = self.arrow_sheets.sheet_mut(sheet_name) else {
12469 return;
12470 };
12471 let Some(chunk) = sheet
12472 .columns
12473 .get_mut(col0 as usize)
12474 .and_then(|column| column.chunk_mut(chunk_idx))
12475 else {
12476 return;
12477 };
12478 match effect {
12479 ComputedWriteChunkFormatEffect::NoFormatWork => {}
12480 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Range {
12481 start,
12482 end,
12483 }) => {
12484 #[cfg(test)]
12485 {
12486 self.computed_overlay_stale_clear_range_effects_for_test = self
12487 .computed_overlay_stale_clear_range_effects_for_test
12488 .saturating_add(1);
12489 }
12490 chunk.computed_overlay.clear_format_range(start, end);
12491 }
12492 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Offsets(
12493 offsets,
12494 )) => {
12495 #[cfg(test)]
12496 {
12497 self.computed_overlay_stale_clear_offset_attempts_for_test = self
12498 .computed_overlay_stale_clear_offset_attempts_for_test
12499 .saturating_add(offsets.len() as u64);
12500 }
12501 chunk.computed_overlay.clear_format_offsets(&offsets);
12502 }
12503 ComputedWriteChunkFormatEffect::SetExplicit(formats) => {
12504 #[cfg(test)]
12505 {
12506 self.computed_overlay_set_explicit_entry_operations_for_test = self
12507 .computed_overlay_set_explicit_entry_operations_for_test
12508 .saturating_add(formats.len() as u64);
12509 }
12510 for (row_in_chunk, format_id) in formats {
12511 chunk.computed_overlay.set_format(row_in_chunk, format_id);
12512 }
12513 }
12514 }
12515 }
12516
12517 fn apply_computed_overlay_fragment(
12518 &mut self,
12519 sheet_id: SheetId,
12520 col0: u32,
12521 chunk_idx: usize,
12522 fragment: OverlayFragment,
12523 ) {
12524 if !(self.config.arrow_storage_enabled
12525 && self.config.delta_overlay_enabled
12526 && self.config.write_formula_overlay_enabled)
12527 {
12528 return;
12529 }
12530 if self.computed_overlay_mirroring_disabled {
12531 return;
12532 }
12533
12534 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12535 self.ensure_arrow_sheet(&sheet_name);
12536
12537 let col0 = col0 as usize;
12538 let asheet = self
12539 .arrow_sheets
12540 .sheet_mut(&sheet_name)
12541 .expect("ArrowSheet must exist");
12542
12543 let cur_cols = asheet.columns.len();
12544 if col0 >= cur_cols {
12545 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
12546 }
12547
12548 let start_row0 = asheet
12549 .chunk_starts
12550 .get(chunk_idx)
12551 .copied()
12552 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
12553 let required_rows =
12554 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
12555 if required_rows > asheet.nrows as usize {
12556 if asheet.columns.is_empty() {
12557 asheet.insert_columns(0, 1);
12558 }
12559 asheet.ensure_row_capacity(required_rows);
12560 }
12561
12562 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
12563 return;
12564 };
12565 let delta = ch.computed_overlay.apply_fragment(fragment);
12566 self.adjust_computed_overlay_bytes(delta);
12567
12568 if let Some(cap) = self.config.max_overlay_memory_bytes
12569 && self.computed_overlay_bytes_estimate > cap
12570 {
12571 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12572 }
12573 }
12574
12575 #[inline]
12576 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
12577 if delta >= 0 {
12578 self.computed_overlay_bytes_estimate = self
12579 .computed_overlay_bytes_estimate
12580 .saturating_add(delta as usize);
12581 } else {
12582 self.computed_overlay_bytes_estimate = self
12583 .computed_overlay_bytes_estimate
12584 .saturating_sub((-delta) as usize);
12585 }
12586 }
12587
12588 fn clear_all_computed_overlays(&mut self) {
12589 let mut freed_total = 0usize;
12590 for sh in self.arrow_sheets.sheets.iter_mut() {
12591 for col in sh.columns.iter_mut() {
12592 for ch in col.chunks.iter_mut() {
12593 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12594 }
12595 for ch in col.sparse_chunks.values_mut() {
12596 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12597 }
12598 }
12599 }
12600 self.computed_overlay_bytes_estimate = self
12601 .computed_overlay_bytes_estimate
12602 .saturating_sub(freed_total);
12603 }
12604
12605 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
12606 self.compact_all_computed_overlays();
12609 }
12610
12611 fn compact_all_computed_overlays(&mut self) {
12614 let mut freed_total = 0usize;
12615 for sheet in self.arrow_sheets.sheets.iter_mut() {
12616 for col_idx in 0..sheet.columns.len() {
12617 let num_dense = sheet.columns[col_idx].chunks.len();
12619 for ch_idx in 0..num_dense {
12620 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
12621 }
12622 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
12624 .sparse_chunks
12625 .keys()
12626 .copied()
12627 .collect();
12628 for ch_idx in sparse_keys {
12629 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
12630 }
12631 }
12632 }
12633 self.computed_overlay_bytes_estimate = self
12634 .computed_overlay_bytes_estimate
12635 .saturating_sub(freed_total);
12636 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
12637 }
12638
12639 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
12640 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
12641 }
12642
12643 fn record_vertex_value_to_overlay(
12644 &mut self,
12645 vertex_id: VertexId,
12646 value: &LiteralValue,
12647 computed_writes: Option<&mut ComputedWriteBuffer>,
12648 ) -> Result<(), ExcelError> {
12649 if !(self.config.arrow_storage_enabled
12650 && self.config.delta_overlay_enabled
12651 && self.config.write_formula_overlay_enabled)
12652 {
12653 return Ok(());
12654 }
12655 if self.computed_overlay_mirroring_disabled {
12656 return Ok(());
12657 }
12658 if !matches!(
12659 self.graph.get_vertex_kind(vertex_id),
12660 VertexKind::FormulaScalar | VertexKind::FormulaArray
12661 ) {
12662 return Ok(());
12663 }
12664 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
12665 return Ok(());
12666 };
12667 let Some(buffer) = computed_writes else {
12668 let format_id = self.derived_formats.get(&cell);
12670 self.write_computed_cell_0based(cell, value, format_id);
12671 return Ok(());
12672 };
12673 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12674 let date_system = self.arrow_sheet_date_system(&sheet_name);
12675 let ov = Self::literal_to_overlay_value(value, date_system);
12676 {
12677 let format_id = self.derived_formats.get(&cell);
12678 buffer.push_cell_with_format(
12679 cell.sheet_id,
12680 cell.coord.row(),
12681 cell.coord.col(),
12682 ov,
12683 format_id,
12684 );
12685 if self.should_flush_computed_write_buffer(buffer) {
12686 self.flush_computed_write_buffer(buffer)?;
12687 }
12688 }
12689 Ok(())
12690 }
12691
12692 #[inline]
12693 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
12694 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
12695 if cap == 0 {
12696 return false;
12697 }
12698 self.computed_overlay_bytes_estimate
12699 .saturating_add(buffer.estimated_bytes())
12700 > cap
12701 })
12702 }
12703
12704 pub fn overlay_memory_usage(&self) -> usize {
12706 self.computed_overlay_bytes_estimate
12707 }
12708
12709 #[cfg(test)]
12710 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
12711 self.overlay_compactions
12712 }
12713
12714 #[cfg(test)]
12715 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
12716 let mut total = 0usize;
12717 for sheet in &self.arrow_sheets.sheets {
12718 for column in &sheet.columns {
12719 for chunk in &column.chunks {
12720 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
12721 }
12722 for chunk in column.sparse_chunks.values() {
12723 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
12724 }
12725 }
12726 }
12727 self.computed_overlay_bytes_estimate = total;
12728 total
12729 }
12730
12731 fn resolve_sheet_locator_for_write(
12732 &mut self,
12733 loc: formualizer_common::SheetLocator<'_>,
12734 current_sheet: &str,
12735 ) -> Result<SheetId, ExcelError> {
12736 Ok(match loc {
12737 formualizer_common::SheetLocator::Id(id) => id,
12738 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
12739 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
12740 })
12741 }
12742
12743 fn resolve_sheet_locator_for_read(
12744 &self,
12745 loc: formualizer_common::SheetLocator<'_>,
12746 current_sheet: &str,
12747 ) -> Result<SheetId, ExcelError> {
12748 match loc {
12749 formualizer_common::SheetLocator::Id(id) => Ok(id),
12750 formualizer_common::SheetLocator::Name(name) => self
12751 .graph
12752 .sheet_id(name.as_ref())
12753 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
12754 formualizer_common::SheetLocator::Current => self
12755 .graph
12756 .sheet_id(current_sheet)
12757 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
12758 }
12759 }
12760
12761 pub fn set_cell_value(
12763 &mut self,
12764 sheet: &str,
12765 row: u32,
12766 col: u32,
12767 value: LiteralValue,
12768 ) -> Result<(), ExcelError> {
12769 self.observe_function_semantic_epoch()?;
12770 let sheet_existed = self.graph.sheet_id(sheet).is_some();
12771 let sheet_id = self.graph.sheet_id_mut(sheet);
12772 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
12773 let replaced_formula =
12774 self.graph
12775 .get_vertex_id_for_address(&cell_ref)
12776 .is_some_and(|vertex| {
12777 matches!(
12778 self.graph.get_vertex_kind(vertex),
12779 VertexKind::FormulaScalar | VertexKind::FormulaArray
12780 )
12781 });
12782 self.graph.set_cell_value(sheet, row, col, value.clone())?;
12783 self.clear_cell_format_state(sheet, cell_ref);
12784 self.record_changed_cell(sheet, row, col);
12785 if !sheet_existed || replaced_formula {
12786 self.mark_topology_edited();
12787 }
12788 self.mirror_value_to_overlay(sheet, row, col, &value);
12790 self.mark_data_edited();
12792 Ok(())
12793 }
12794
12795 fn record_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
12797 let sheet_id = self.graph.sheet_id_mut(sheet);
12798 self.record_structural_change(StructuralScope::Cell {
12799 sheet: sheet_id,
12800 row: row.saturating_sub(1),
12801 col: col.saturating_sub(1),
12802 });
12803 }
12804
12805 fn record_change_for_event(&mut self, event: &ChangeEvent) {
12806 match event {
12807 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
12808 self.record_structural_change(StructuralScope::Cell {
12809 sheet: addr.sheet_id,
12810 row: addr.coord.row(),
12811 col: addr.coord.col(),
12812 });
12813 }
12814 ChangeEvent::SpillCommitted { new, .. } => {
12815 if let Some(scope) = Self::structural_scope_from_cells(&new.target_cells) {
12816 self.record_structural_change(scope);
12817 }
12818 }
12819 ChangeEvent::SpillCleared { old, .. } => {
12820 if let Some(scope) = Self::structural_scope_from_cells(&old.target_cells) {
12821 self.record_structural_change(scope);
12822 }
12823 }
12824 ChangeEvent::DefineName { .. }
12825 | ChangeEvent::UpdateName { .. }
12826 | ChangeEvent::DeleteName { .. }
12827 | ChangeEvent::NamedRangeAdjusted { .. } => {
12828 self.record_structural_change(StructuralScope::AllSheets);
12832 }
12833 ChangeEvent::VertexMoved { .. } | ChangeEvent::FormulaAdjusted { .. } => {
12834 }
12837 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
12838 self.record_structural_change(StructuralScope::Region(Region::whole_row(
12839 *sheet_id, *row0,
12840 )));
12841 }
12842 ChangeEvent::AddVertex { .. }
12843 | ChangeEvent::RemoveVertex { .. }
12844 | ChangeEvent::EdgeAdded { .. }
12845 | ChangeEvent::EdgeRemoved { .. }
12846 | ChangeEvent::CompoundStart { .. }
12847 | ChangeEvent::CompoundEnd { .. }
12848 | ChangeEvent::StagedFormulaCellChanged { .. } => {}
12849 }
12850 }
12851
12852 fn record_structural_change(&mut self, scope: StructuralScope) {
12853 self.invalidate_pending_spills(scope);
12854 }
12855
12856 fn structural_scope_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
12857 let first = cells.first()?;
12858 let sheet_id = first.sheet_id;
12859 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
12860 return Some(StructuralScope::OpaqueGlobal);
12861 }
12862 let mut row_start = first.coord.row();
12863 let mut row_end = row_start;
12864 let mut col_start = first.coord.col();
12865 let mut col_end = col_start;
12866 for cell in cells.iter().skip(1) {
12867 row_start = row_start.min(cell.coord.row());
12868 row_end = row_end.max(cell.coord.row());
12869 col_start = col_start.min(cell.coord.col());
12870 col_end = col_end.max(cell.coord.col());
12871 }
12872 Some(StructuralScope::Region(Region::rect(
12873 sheet_id, row_start, row_end, col_start, col_end,
12874 )))
12875 }
12876
12877 pub fn set_cell_value_ref(
12878 &mut self,
12879 cell: formualizer_common::SheetCellRef<'_>,
12880 current_sheet: &str,
12881 value: LiteralValue,
12882 ) -> Result<(), ExcelError> {
12883 let owned = cell.into_owned();
12884 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
12885 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12886 self.set_cell_value(
12887 &sheet_name,
12888 owned.coord.row() + 1,
12889 owned.coord.col() + 1,
12890 value,
12891 )
12892 }
12893
12894 pub fn set_cell_formula_ref(
12895 &mut self,
12896 cell: formualizer_common::SheetCellRef<'_>,
12897 current_sheet: &str,
12898 ast: ASTNode,
12899 ) -> Result<(), ExcelError> {
12900 let owned = cell.into_owned();
12901 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
12902 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12903 self.set_cell_formula(
12904 &sheet_name,
12905 owned.coord.row() + 1,
12906 owned.coord.col() + 1,
12907 ast,
12908 )
12909 }
12910
12911 pub fn get_cell_value_ref(
12912 &self,
12913 cell: formualizer_common::SheetCellRef<'_>,
12914 current_sheet: &str,
12915 ) -> Result<Option<LiteralValue>, ExcelError> {
12916 let owned = cell.into_owned();
12917 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
12918 let sheet_name = self.graph.sheet_name(sheet_id);
12919 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
12920 }
12921
12922 pub fn resolve_range_view_sheet_ref<'c>(
12923 &'c self,
12924 r: &formualizer_common::SheetRef<'_>,
12925 current_sheet: &str,
12926 ) -> Result<RangeView<'c>, ExcelError> {
12927 use formualizer_common::SheetLocator;
12928
12929 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
12930 match loc {
12931 SheetLocator::Current => Ok(None),
12932 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
12933 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
12934 }
12935 };
12936
12937 let rt = match r {
12938 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
12939 sheet: sheet_to_opt_name(cell.sheet.clone())?,
12940 row: cell.coord.row() + 1,
12941 col: cell.coord.col() + 1,
12942 row_abs: cell.coord.row_abs(),
12943 col_abs: cell.coord.col_abs(),
12944 },
12945 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
12946 sheet: sheet_to_opt_name(range.sheet.clone())?,
12947 start_row: range.start_row.map(|b| b.index + 1),
12948 start_col: range.start_col.map(|b| b.index + 1),
12949 end_row: range.end_row.map(|b| b.index + 1),
12950 end_col: range.end_col.map(|b| b.index + 1),
12951 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
12952 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
12953 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
12954 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
12955 },
12956 };
12957
12958 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
12959 }
12960
12961 pub fn set_cell_formula(
12963 &mut self,
12964 sheet: &str,
12965 row: u32,
12966 col: u32,
12967 ast: ASTNode,
12968 ) -> Result<(), ExcelError> {
12969 self.observe_function_semantic_epoch()?;
12970 let sheet_id = self.graph.sheet_id_mut(sheet);
12971 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
12972 let ingested = {
12973 let mut pipeline = self.ingest_pipeline();
12974 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
12975 };
12976 self.graph.set_cell_formula_with_plan(
12977 sheet,
12978 row,
12979 col,
12980 ingested.ast_id,
12981 &ingested.dep_plan,
12982 ingested.dep_plan.volatile,
12983 ingested.dep_plan.dynamic,
12984 )?;
12985 self.clear_cell_format_state(sheet, placement);
12986 self.record_changed_cell(sheet, row, col);
12987
12988 self.clear_delta_overlay_cell(sheet, row, col);
12993
12994 self.mark_topology_edited();
12996 Ok(())
12997 }
12998
12999 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
13001 where
13002 I: IntoIterator<Item = (u32, u32, ASTNode)>,
13003 {
13004 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
13005 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
13006 let sheet_id = self.graph.sheet_id_mut(sheet);
13007 let ingested = {
13008 let mut pipeline = self.ingest_pipeline();
13009 let inputs = collected.into_iter().map(|(row, col, ast)| {
13010 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13011 (FormulaAstInput::Tree(ast), placement, None)
13012 });
13013 pipeline.ingest_batch(inputs)?
13014 };
13015 let planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)> = ingested
13016 .into_iter()
13017 .map(|formula| {
13018 (
13019 formula.placement.coord.row() + 1,
13020 formula.placement.coord.col() + 1,
13021 formula.ast_id,
13022 formula.dep_plan,
13023 )
13024 })
13025 .collect();
13026 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
13027 for (row, col) in edited_cells {
13028 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13029 self.clear_cell_format_state(sheet, cell);
13030 self.record_changed_cell(sheet, row, col);
13031 }
13032 if n > 0 {
13034 self.mark_topology_edited();
13035 }
13036 Ok(n)
13037 }
13038
13039 #[inline]
13040 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
13041 match v {
13042 LiteralValue::Empty => None,
13043 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
13044 other => Some(other),
13045 }
13046 }
13047
13048 fn materialize_temporal_egress(
13049 value: LiteralValue,
13050 class: Option<&formualizer_common::numfmt::FormatClass>,
13051 policy: crate::engine::TemporalEgress,
13052 date_system: crate::engine::DateSystem,
13053 ) -> LiteralValue {
13054 use formualizer_common::numfmt::FormatClass;
13055 if policy == crate::engine::TemporalEgress::Serial {
13056 return value;
13057 }
13058 let LiteralValue::Number(serial) = value else {
13059 return value;
13060 };
13061 match class {
13062 Some(FormatClass::Date) => {
13063 formualizer_common::try_serial_to_date_for(date_system, serial)
13064 .map(LiteralValue::Date)
13065 .unwrap_or(LiteralValue::Number(serial))
13066 }
13067 Some(FormatClass::DateTime) => {
13068 formualizer_common::try_serial_to_datetime_for(date_system, serial)
13069 .map(LiteralValue::DateTime)
13070 .unwrap_or(LiteralValue::Number(serial))
13071 }
13072 Some(FormatClass::Time) => {
13073 let seconds = (serial.rem_euclid(1.0) * 86_400.0).round() as u32 % 86_400;
13074 chrono::NaiveTime::from_num_seconds_from_midnight_opt(seconds, 0)
13075 .map(LiteralValue::Time)
13076 .unwrap_or(LiteralValue::Number(serial))
13077 }
13078 Some(FormatClass::Duration) => {
13079 let nanos = (serial * 86_400.0 * 1_000_000_000.0).round();
13080 if nanos.is_finite() && nanos >= i64::MIN as f64 && nanos <= i64::MAX as f64 {
13081 LiteralValue::Duration(chrono::Duration::nanoseconds(nanos as i64))
13082 } else {
13083 LiteralValue::Number(serial)
13084 }
13085 }
13086 _ => LiteralValue::Number(serial),
13087 }
13088 }
13089
13090 pub(crate) fn effective_format_id(
13091 &self,
13092 sheet: &str,
13093 row: u32,
13094 col: u32,
13095 ) -> Option<crate::format::FormatId> {
13096 let arrow = self.arrow_sheets.sheet(sheet).and_then(|arrow| {
13097 arrow.format_id(
13098 row.saturating_sub(1) as usize,
13099 col.saturating_sub(1) as usize,
13100 )
13101 });
13102 arrow.or_else(|| {
13103 let sheet_id = self.graph.sheet_id(sheet)?;
13104 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13105 self.derived_formats.get(&cell)
13106 })
13107 }
13108
13109 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13111 let raw = self.read_cell_value(sheet, row, col)?;
13112 let format = self.effective_format_id(sheet, row, col);
13113 let class = format.and_then(|id| self.format_registry.class(id));
13114 Self::normalize_public_cell_read(Self::materialize_temporal_egress(
13115 raw,
13116 class,
13117 self.config.temporal_egress,
13118 self.config.date_system,
13119 ))
13120 }
13121
13122 pub fn get_range_values(
13124 &self,
13125 sheet: &str,
13126 sr: u32,
13127 sc: u32,
13128 er: u32,
13129 ec: u32,
13130 ) -> Vec<Vec<LiteralValue>> {
13131 let height = er.saturating_sub(sr).saturating_add(1) as usize;
13132 let width = ec.saturating_sub(sc).saturating_add(1) as usize;
13133 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13134 return vec![vec![LiteralValue::Empty; width]; height];
13135 };
13136 let view = asheet.range_view(
13137 sr.saturating_sub(1) as usize,
13138 sc.saturating_sub(1) as usize,
13139 er.saturating_sub(1) as usize,
13140 ec.saturating_sub(1) as usize,
13141 );
13142 let sheet_id = self.graph.sheet_id(sheet);
13143 let derived_formats = &self.derived_formats;
13144 let has_derived_formats =
13145 sheet_id.is_some_and(|sheet_id| derived_formats.any(|cell| cell.sheet_id == sheet_id));
13146 let mut out = Vec::with_capacity(height);
13147 if !asheet.has_formats() && !has_derived_formats {
13148 for rr in 0..height {
13149 let mut row = Vec::with_capacity(width);
13150 for cc in 0..width {
13151 row.push(view.get_cell(rr, cc));
13152 }
13153 out.push(row);
13154 }
13155 return out;
13156 }
13157 let format_registry = &self.format_registry;
13158 for rr in 0..height {
13159 let mut row = Vec::with_capacity(width);
13160 for cc in 0..width {
13161 let raw = view.get_cell(rr, cc);
13162 let row0 = sr.saturating_sub(1).saturating_add(rr as u32);
13163 let col0 = sc.saturating_sub(1).saturating_add(cc as u32);
13164 let format = asheet.format_id(row0 as usize, col0 as usize).or_else(|| {
13165 let cell = CellRef::new(sheet_id?, Coord::new(row0, col0, true, true));
13166 derived_formats.get(&cell)
13167 });
13168 let class = format.and_then(|id| format_registry.class(id));
13169 row.push(Self::materialize_temporal_egress(
13170 raw,
13171 class,
13172 self.config.temporal_egress,
13173 self.config.date_system,
13174 ));
13175 }
13176 out.push(row);
13177 }
13178 out
13179 }
13180
13181 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13183 let asheet = self.sheet_store().sheet(sheet)?;
13184 let r0 = row.saturating_sub(1) as usize;
13185 let c0 = col.saturating_sub(1) as usize;
13186 let v = asheet.get_cell_value(r0, c0);
13187 if matches!(v, LiteralValue::Empty) {
13188 None
13189 } else {
13190 Some(v)
13191 }
13192 }
13193
13194 pub(crate) fn read_range_values(
13196 &self,
13197 sheet: &str,
13198 sr: u32,
13199 sc: u32,
13200 er: u32,
13201 ec: u32,
13202 ) -> RangeView<'_> {
13203 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13204 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
13205 };
13206 if er < sr || ec < sc {
13207 return asheet.range_view(1, 1, 0, 0);
13208 }
13209 let sr0 = sr.saturating_sub(1) as usize;
13210 let sc0 = sc.saturating_sub(1) as usize;
13211 let er0 = er.saturating_sub(1) as usize;
13212 let ec0 = ec.saturating_sub(1) as usize;
13213 asheet.range_view(sr0, sc0, er0, ec0)
13214 }
13215
13216 pub fn get_cell(
13218 &self,
13219 sheet: &str,
13220 row: u32,
13221 col: u32,
13222 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
13223 let v = self.get_cell_value(sheet, row, col);
13224 let sheet_id = self.graph.sheet_id(sheet)?;
13225 let coord = Coord::from_excel(row, col, true, true);
13226 let cell = CellRef::new(sheet_id, coord);
13227 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
13228 let ast = self.graph.get_formula(vid);
13229 Some((ast, v))
13230 } else if v.is_some() || self.graph.had_legacy_cell_vertex(&cell) {
13231 Some((None, v))
13235 } else {
13236 None
13237 }
13238 }
13239
13240 pub fn begin_batch(&mut self) {
13242 self.graph.begin_batch();
13243 }
13244
13245 pub fn end_batch(&mut self) {
13247 self.graph.end_batch();
13248 }
13249
13250 pub fn begin_deferred_dirty(&mut self) {
13259 self.graph.begin_deferred_dirty();
13260 }
13261
13262 pub fn end_deferred_dirty(&mut self) {
13265 let _ = self.graph.end_deferred_dirty();
13266 }
13267
13268 pub fn dirty_propagation_visits(&self) -> u64 {
13272 self.graph.dirty_propagation_visits()
13273 }
13274
13275 #[inline]
13278 fn record_cell_if_changed(
13279 delta: &mut DeltaCollector,
13280 cell: &CellRef,
13281 old: &LiteralValue,
13282 new: &LiteralValue,
13283 ) {
13284 if old != new {
13285 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13286 }
13287 }
13288
13289 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
13290 self.observe_evaluation_resource_request(EvaluationRequestKind::Vertex, |engine| {
13291 engine.observe_function_semantic_epoch()?;
13292 engine.resource_checkpoint(1)?;
13294 if !engine.graph.vertex_exists(vertex_id) {
13295 return engine.evaluate_vertex_impl(vertex_id, None);
13296 }
13297 let is_formula = matches!(
13298 engine.graph.get_vertex_kind(vertex_id),
13299 VertexKind::FormulaScalar | VertexKind::FormulaArray
13300 );
13301 if is_formula {
13302 engine.begin_evaluation_request();
13303 #[cfg(any(test, feature = "legacy_oracle"))]
13304 engine.graph.flush_pending_edge_deltas();
13305 let roots = [crate::engine::target_preparation::TargetProducer::Legacy(
13306 vertex_id,
13307 )];
13308 engine.evaluate_legacy_target_roots(&roots, None)?;
13309 }
13310 engine.evaluate_vertex_impl(vertex_id, None)
13311 })
13312 }
13313
13314 fn evaluate_vertex_impl(
13315 &mut self,
13316 vertex_id: VertexId,
13317 delta: Option<&mut DeltaCollector>,
13318 ) -> Result<LiteralValue, ExcelError> {
13319 if !self.graph.vertex_exists(vertex_id) {
13323 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
13324 .with_message(format!("Vertex not found: {vertex_id:?}")));
13325 }
13326 if self.active_resource_ledger.is_some()
13327 && matches!(
13328 self.graph.get_vertex_kind(vertex_id),
13329 VertexKind::FormulaScalar | VertexKind::FormulaArray
13330 )
13331 {
13332 let value = self
13333 .evaluate_vertex_immutable(vertex_id)
13334 .unwrap_or_else(LiteralValue::Error);
13335 let effects = self.plan_vertex_effects(vertex_id, value.clone(), None)?;
13336 self.resource_checkpoint(0)?;
13338 let mut delta = delta;
13339 for effect in &effects {
13340 self.apply_effect_with_computed_writes(effect, delta.as_deref_mut(), None, None)?;
13341 }
13342 return Ok(value);
13343 }
13344
13345 let mut delta = delta;
13346
13347 let kind = self.graph.get_vertex_kind(vertex_id);
13349 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
13350
13351 let view = match kind {
13352 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
13353 if let Some(view) = self.graph.formula_view(vertex_id) {
13354 view
13355 } else {
13356 return Ok(LiteralValue::Number(0.0));
13357 }
13358 }
13359 VertexKind::Empty | VertexKind::Cell => {
13360 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
13361 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13362 let row = cell_ref.coord.row() + 1;
13363 let col = cell_ref.coord.col() + 1;
13364 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
13365 return Ok(v);
13366 }
13367 }
13368 return Ok(LiteralValue::Number(0.0));
13369 }
13370 VertexKind::NamedScalar => {
13371 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
13372 return Ok(value);
13373 }
13374 VertexKind::NamedArray => {
13375 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
13376 return Ok(value);
13377 }
13378 VertexKind::InfiniteRange
13379 | VertexKind::Range
13380 | VertexKind::External
13381 | VertexKind::Table => {
13382 return Ok(LiteralValue::Number(0.0));
13384 }
13385 };
13386
13387 let sheet_name = self.graph.sheet_name(sheet_id);
13389 let cell_ref = self
13390 .graph
13391 .get_cell_ref(vertex_id)
13392 .expect("cell ref for vertex");
13393 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13394
13395 let result = interpreter.evaluate_formula_view(
13396 view,
13397 self.graph.data_store(),
13398 self.graph.sheet_reg(),
13399 );
13400
13401 match result {
13403 Ok(cv) => {
13404 let derived_format = cv.format_id();
13405 self.record_derived_format(vertex_id, derived_format);
13406 let result_literal =
13407 crate::engine::result_finalization::finalize_formula_result(cv.into_literal());
13408 let output_sheet_name = sheet_name.to_string();
13409 self.write_computed_overlay_format_0based(
13410 &output_sheet_name,
13411 cell_ref.coord.row(),
13412 cell_ref.coord.col(),
13413 derived_format,
13414 );
13415 match result_literal {
13416 LiteralValue::Array(rows) => {
13417 self.graph
13419 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
13420 let anchor = self
13422 .graph
13423 .get_cell_ref(vertex_id)
13424 .expect("cell ref for vertex");
13425 let sheet_id = anchor.sheet_id;
13426 let h = rows.len() as u32;
13427 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
13428
13429 let spill_cells = (h as u64).saturating_mul(w as u64);
13431 if spill_cells > self.config.spill.max_spill_cells as u64 {
13432 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
13433 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13434 .with_message("SpillTooLarge")
13435 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13436 expected_rows: h,
13437 expected_cols: w,
13438 });
13439 let spill_val = LiteralValue::Error(spill_err.clone());
13440 if let Some(d) = delta.as_deref_mut() {
13441 let old = self
13442 .read_cell_value(
13443 self.graph.sheet_name(anchor.sheet_id),
13444 anchor.coord.row() + 1,
13445 anchor.coord.col() + 1,
13446 )
13447 .unwrap_or(LiteralValue::Empty);
13448 if old != spill_val {
13449 d.record_cell(
13450 anchor.sheet_id,
13451 anchor.coord.row(),
13452 anchor.coord.col(),
13453 );
13454 }
13455 }
13456 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13457 if self.config.arrow_storage_enabled
13458 && self.config.delta_overlay_enabled
13459 && self.config.write_formula_overlay_enabled
13460 {
13461 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13462 self.mirror_value_to_computed_overlay(
13463 &sheet_name,
13464 anchor.coord.row() + 1,
13465 anchor.coord.col() + 1,
13466 &spill_val,
13467 );
13468 }
13469 return Ok(spill_val);
13470 }
13471 const PACKED_MAX_ROW: u32 = 1_048_575; const PACKED_MAX_COL: u32 = 16_383; let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
13475 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
13476 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
13477 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
13478 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13479 .with_message("Spill exceeds sheet bounds")
13480 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13481 expected_rows: h,
13482 expected_cols: w,
13483 });
13484 let spill_val = LiteralValue::Error(spill_err.clone());
13485 if let Some(d) = delta.as_deref_mut() {
13486 let old = self
13487 .read_cell_value(
13488 self.graph.sheet_name(anchor.sheet_id),
13489 anchor.coord.row() + 1,
13490 anchor.coord.col() + 1,
13491 )
13492 .unwrap_or(LiteralValue::Empty);
13493 if old != spill_val {
13494 d.record_cell(
13495 anchor.sheet_id,
13496 anchor.coord.row(),
13497 anchor.coord.col(),
13498 );
13499 }
13500 }
13501 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13502 if self.config.arrow_storage_enabled
13503 && self.config.delta_overlay_enabled
13504 && self.config.write_formula_overlay_enabled
13505 {
13506 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13507 self.mirror_value_to_computed_overlay(
13508 &sheet_name,
13509 anchor.coord.row() + 1,
13510 anchor.coord.col() + 1,
13511 &spill_val,
13512 );
13513 }
13514 return Ok(spill_val);
13515 }
13516 let mut targets = Vec::new();
13517 for r in 0..h {
13518 for c in 0..w {
13519 targets.push(self.graph.make_cell_ref_internal(
13520 sheet_id,
13521 anchor.coord.row() + r,
13522 anchor.coord.col() + c,
13523 ));
13524 }
13525 }
13526
13527 match self.spill_mgr.reserve(
13529 vertex_id,
13530 anchor,
13531 SpillShape { rows: h, cols: w },
13532 SpillMeta {
13533 epoch: self.recalc_epoch,
13534 config: self.config.spill,
13535 },
13536 ) {
13537 Ok(()) => {
13538 if let Err(e) = self.commit_spill_and_mirror(
13542 vertex_id,
13543 &targets,
13544 rows.clone(),
13545 delta.as_deref_mut(),
13546 None,
13547 ) {
13548 if e.kind != ExcelErrorKind::Spill {
13549 return Err(e);
13550 }
13551 self.clear_spill_projection_and_mirror(
13553 vertex_id,
13554 delta.as_deref_mut(),
13555 );
13556 if let Some(d) = delta.as_deref_mut() {
13557 let old = self
13558 .read_cell_value(
13559 self.graph.sheet_name(anchor.sheet_id),
13560 anchor.coord.row() + 1,
13561 anchor.coord.col() + 1,
13562 )
13563 .unwrap_or(LiteralValue::Empty);
13564 let new = LiteralValue::Error(e.clone());
13565 if old != new {
13566 d.record_cell(
13567 anchor.sheet_id,
13568 anchor.coord.row(),
13569 anchor.coord.col(),
13570 );
13571 }
13572 }
13573 let err_val = LiteralValue::Error(e.clone());
13574 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13575 if self.config.arrow_storage_enabled
13576 && self.config.delta_overlay_enabled
13577 && self.config.write_formula_overlay_enabled
13578 {
13579 let sheet_name =
13580 self.graph.sheet_name(anchor.sheet_id).to_string();
13581 self.mirror_value_to_computed_overlay(
13582 &sheet_name,
13583 anchor.coord.row() + 1,
13584 anchor.coord.col() + 1,
13585 &err_val,
13586 );
13587 }
13588 return Ok(err_val);
13589 }
13590 let top_left = rows
13592 .first()
13593 .and_then(|r| r.first())
13594 .cloned()
13595 .unwrap_or(LiteralValue::Empty);
13596 self.graph.update_vertex_value_ref(vertex_id, &top_left);
13597 Ok(top_left)
13598 }
13599 Err(e) => {
13600 self.clear_spill_projection_and_mirror(
13601 vertex_id,
13602 delta.as_deref_mut(),
13603 );
13604 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13605 .with_message(
13606 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
13607 )
13608 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13609 expected_rows: h,
13610 expected_cols: w,
13611 });
13612 let spill_val = LiteralValue::Error(spill_err.clone());
13613 if let Some(d) = delta.as_deref_mut() {
13614 let old = self
13615 .read_cell_value(
13616 self.graph.sheet_name(anchor.sheet_id),
13617 anchor.coord.row() + 1,
13618 anchor.coord.col() + 1,
13619 )
13620 .unwrap_or(LiteralValue::Empty);
13621 if old != spill_val {
13622 d.record_cell(
13623 anchor.sheet_id,
13624 anchor.coord.row(),
13625 anchor.coord.col(),
13626 );
13627 }
13628 }
13629 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13630 if self.config.arrow_storage_enabled
13631 && self.config.delta_overlay_enabled
13632 && self.config.write_formula_overlay_enabled
13633 {
13634 let sheet_name =
13635 self.graph.sheet_name(anchor.sheet_id).to_string();
13636 self.mirror_value_to_computed_overlay(
13637 &sheet_name,
13638 anchor.coord.row() + 1,
13639 anchor.coord.col() + 1,
13640 &spill_val,
13641 );
13642 }
13643 Ok(spill_val)
13644 }
13645 }
13646 }
13647 other => {
13648 let spill_cells = self
13650 .graph
13651 .spill_cells_for_anchor(vertex_id)
13652 .map(|cells| cells.to_vec())
13653 .unwrap_or_default();
13654 if let Some(d) = delta.as_deref_mut()
13655 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
13656 {
13657 if spill_cells.is_empty() {
13658 let old = self
13659 .read_cell_value(
13660 self.graph.sheet_name(anchor.sheet_id),
13661 anchor.coord.row() + 1,
13662 anchor.coord.col() + 1,
13663 )
13664 .unwrap_or(LiteralValue::Empty);
13665 if old != other {
13666 d.record_cell(
13667 anchor.sheet_id,
13668 anchor.coord.row(),
13669 anchor.coord.col(),
13670 );
13671 }
13672 } else {
13673 for cell in spill_cells.iter() {
13674 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13675 let old = self
13676 .get_cell_value(
13677 sheet_name,
13678 cell.coord.row() + 1,
13679 cell.coord.col() + 1,
13680 )
13681 .unwrap_or(LiteralValue::Empty);
13682 let new = if cell.sheet_id == anchor.sheet_id
13683 && cell.coord.row() == anchor.coord.row()
13684 && cell.coord.col() == anchor.coord.col()
13685 {
13686 other.clone()
13687 } else {
13688 LiteralValue::Empty
13689 };
13690 Self::record_cell_if_changed(d, cell, &old, &new);
13691 }
13692 }
13693 }
13694 self.graph.clear_spill_region(vertex_id);
13695 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
13696 self.record_structural_change(scope);
13697 }
13698 if self.config.arrow_storage_enabled
13699 && self.config.delta_overlay_enabled
13700 && self.config.write_formula_overlay_enabled
13701 {
13702 let empty = LiteralValue::Empty;
13703 for cell in spill_cells.iter() {
13704 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13705 self.mirror_value_to_computed_overlay(
13706 &sheet_name,
13707 cell.coord.row() + 1,
13708 cell.coord.col() + 1,
13709 &empty,
13710 );
13711 }
13712 }
13713 self.graph.update_vertex_value_ref(vertex_id, &other);
13714 if self.config.arrow_storage_enabled
13716 && self.config.delta_overlay_enabled
13717 && self.config.write_formula_overlay_enabled
13718 {
13719 let anchor = self
13720 .graph
13721 .get_cell_ref(vertex_id)
13722 .expect("cell ref for vertex");
13723 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13724 self.mirror_value_to_computed_overlay(
13725 &sheet_name,
13726 anchor.coord.row() + 1,
13727 anchor.coord.col() + 1,
13728 &other,
13729 );
13730 }
13731 Ok(other)
13732 }
13733 }
13734 }
13735 Err(e) => {
13736 let spill_cells = self
13739 .graph
13740 .spill_cells_for_anchor(vertex_id)
13741 .map(|cells| cells.to_vec())
13742 .unwrap_or_default();
13743 let err_val = LiteralValue::Error(e.clone());
13744 if let Some(d) = delta
13745 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
13746 {
13747 if spill_cells.is_empty() {
13748 let old = self
13749 .read_cell_value(
13750 self.graph.sheet_name(anchor.sheet_id),
13751 anchor.coord.row() + 1,
13752 anchor.coord.col() + 1,
13753 )
13754 .unwrap_or(LiteralValue::Empty);
13755 if old != err_val {
13756 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
13757 }
13758 } else {
13759 for cell in spill_cells.iter() {
13760 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13761 let old = self
13762 .get_cell_value(
13763 sheet_name,
13764 cell.coord.row() + 1,
13765 cell.coord.col() + 1,
13766 )
13767 .unwrap_or(LiteralValue::Empty);
13768 let new = if cell.sheet_id == anchor.sheet_id
13769 && cell.coord.row() == anchor.coord.row()
13770 && cell.coord.col() == anchor.coord.col()
13771 {
13772 err_val.clone()
13773 } else {
13774 LiteralValue::Empty
13775 };
13776 Self::record_cell_if_changed(d, cell, &old, &new);
13777 }
13778 }
13779 }
13780 self.graph.clear_spill_region(vertex_id);
13781 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
13782 self.record_structural_change(scope);
13783 }
13784 if self.config.arrow_storage_enabled
13785 && self.config.delta_overlay_enabled
13786 && self.config.write_formula_overlay_enabled
13787 {
13788 let empty = LiteralValue::Empty;
13789 for cell in spill_cells.iter() {
13790 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13791 self.mirror_value_to_computed_overlay(
13792 &sheet_name,
13793 cell.coord.row() + 1,
13794 cell.coord.col() + 1,
13795 &empty,
13796 );
13797 }
13798 }
13799 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13800 if self.config.arrow_storage_enabled
13801 && self.config.delta_overlay_enabled
13802 && self.config.write_formula_overlay_enabled
13803 {
13804 let anchor = self
13805 .graph
13806 .get_cell_ref(vertex_id)
13807 .expect("cell ref for vertex");
13808 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13809 self.mirror_value_to_computed_overlay(
13810 &sheet_name,
13811 anchor.coord.row() + 1,
13812 anchor.coord.col() + 1,
13813 &err_val,
13814 );
13815 }
13816 Ok(err_val)
13817 }
13818 }
13819 }
13820
13821 fn evaluate_named_scalar(
13822 &mut self,
13823 vertex_id: VertexId,
13824 sheet_id: SheetId,
13825 ) -> Result<LiteralValue, ExcelError> {
13826 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
13827 ExcelError::new(ExcelErrorKind::Name)
13828 .with_message("Named range metadata missing".to_string())
13829 })?;
13830
13831 match &named_range.definition {
13832 NamedDefinition::Cell(cell_ref) => {
13833 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13834 let row = cell_ref.coord.row() + 1;
13835 let col = cell_ref.coord.col() + 1;
13836
13837 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
13838 && matches!(
13839 self.graph.get_vertex_kind(dep_vertex),
13840 VertexKind::FormulaScalar | VertexKind::FormulaArray
13841 )
13842 {
13843 let value = self.evaluate_vertex(dep_vertex)?;
13845 self.graph.update_vertex_value_ref(vertex_id, &value);
13846 Ok(value)
13847 } else {
13848 let value = self
13849 .get_cell_value(sheet_name, row, col)
13850 .unwrap_or(LiteralValue::Empty);
13851 self.graph.update_vertex_value_ref(vertex_id, &value);
13852 Ok(value)
13853 }
13854 }
13855 NamedDefinition::Literal(v) => {
13856 let out = v.clone();
13857 self.graph.update_vertex_value_ref(vertex_id, &out);
13858 Ok(out)
13859 }
13860 NamedDefinition::Formula { ast, .. } => {
13861 let context_sheet = match named_range.scope {
13862 NameScope::Sheet(id) => id,
13863 NameScope::Workbook => sheet_id,
13864 };
13865 let sheet_name = self.graph.sheet_name(context_sheet);
13866 let cell_ref = self
13867 .graph
13868 .get_cell_ref(vertex_id)
13869 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
13870 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13871 match interpreter.evaluate_ast(ast) {
13872 Ok(cv) => {
13873 let value = cv.into_literal();
13874 match value {
13875 LiteralValue::Array(_) => {
13876 let err = ExcelError::new(ExcelErrorKind::NImpl)
13877 .with_message("Array result in scalar named range".to_string());
13878 let err_val = LiteralValue::Error(err.clone());
13879 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13880 Ok(err_val)
13881 }
13882 other => {
13883 self.graph.update_vertex_value_ref(vertex_id, &other);
13884 Ok(other)
13885 }
13886 }
13887 }
13888 Err(err) => {
13889 let err_val = LiteralValue::Error(err.clone());
13890 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13891 Ok(err_val)
13892 }
13893 }
13894 }
13895 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
13896 .with_message("Range-valued name evaluated as scalar".to_string())),
13897 }
13898 }
13899
13900 fn evaluate_named_array(
13901 &mut self,
13902 vertex_id: VertexId,
13903 sheet_id: SheetId,
13904 ) -> Result<LiteralValue, ExcelError> {
13905 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
13906 ExcelError::new(ExcelErrorKind::Name)
13907 .with_message("Named range metadata missing".to_string())
13908 })?;
13909
13910 let out = match &named_range.definition {
13911 NamedDefinition::Range(range_ref) => {
13912 if range_ref.start.sheet_id != range_ref.end.sheet_id {
13913 return Err(ExcelError::new(ExcelErrorKind::Ref)
13914 .with_message("Named range cannot span sheets".to_string()));
13915 }
13916
13917 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
13918 let sr0 = range_ref.start.coord.row();
13919 let sc0 = range_ref.start.coord.col();
13920 let er0 = range_ref.end.coord.row();
13921 let ec0 = range_ref.end.coord.col();
13922 if sr0 > er0 || sc0 > ec0 {
13923 return Err(ExcelError::new(ExcelErrorKind::Ref)
13924 .with_message("Invalid named range bounds".to_string()));
13925 }
13926
13927 let h = (er0 - sr0 + 1) as usize;
13928 let w = (ec0 - sc0 + 1) as usize;
13929 let cell_count = (h as u64).saturating_mul(w as u64);
13930 if cell_count > self.config.spill.max_spill_cells as u64 {
13931 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
13932 "Named range too large to materialize as an array".to_string(),
13933 ));
13934 }
13935
13936 let mut rows = Vec::with_capacity(h);
13937 for r0 in sr0..=er0 {
13938 let mut row = Vec::with_capacity(w);
13939 for c0 in sc0..=ec0 {
13940 let v = self
13941 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
13942 .unwrap_or(LiteralValue::Empty);
13943 row.push(v);
13944 }
13945 rows.push(row);
13946 }
13947 LiteralValue::Array(rows)
13948 }
13949 NamedDefinition::Cell(cell_ref) => {
13950 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13951 let row = cell_ref.coord.row() + 1;
13952 let col = cell_ref.coord.col() + 1;
13953 let v = self
13954 .get_cell_value(sheet_name, row, col)
13955 .unwrap_or(LiteralValue::Empty);
13956 LiteralValue::Array(vec![vec![v]])
13957 }
13958 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
13959 NamedDefinition::Formula { ast, .. } => {
13960 let context_sheet = match named_range.scope {
13961 NameScope::Sheet(id) => id,
13962 NameScope::Workbook => sheet_id,
13963 };
13964 let sheet_name = self.graph.sheet_name(context_sheet);
13965 let cell_ref = self
13966 .graph
13967 .get_cell_ref(vertex_id)
13968 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
13969 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13970 match interpreter.evaluate_ast(ast) {
13971 Ok(cv) => {
13972 let v = cv.into_literal();
13973 match v {
13974 LiteralValue::Array(_) => v,
13975 other => LiteralValue::Array(vec![vec![other]]),
13976 }
13977 }
13978 Err(err) => LiteralValue::Error(err),
13979 }
13980 }
13981 };
13982
13983 self.graph.update_vertex_value_ref(vertex_id, &out);
13984 Ok(out)
13985 }
13986
13987 fn replan_exhausted_error(&self, limit: usize, context: &str) -> ExcelError {
13988 crate::engine::ResourceLedgerError::Exhausted(
13989 formualizer_common::ResourceExhaustionDetail {
13990 reason: formualizer_common::ResourceExhaustionReason::WorkUnits,
13991 limit: limit as u64,
13992 observed: limit.saturating_add(1) as u64,
13993 request_id: self
13994 .active_evaluation_resource_request
13995 .as_ref()
13996 .map(|stats| stats.request_id),
13997 },
13998 )
13999 .into_excel_error()
14000 .with_message(format!("{context} did not converge after {limit} replans"))
14001 }
14002
14003 fn transient_target_preparation_stale(error: &ExcelError) -> bool {
14004 matches!(
14005 &error.extra,
14006 formualizer_common::ExcelErrorExtra::PreparationStale {
14007 reason: formualizer_common::PreparationStaleReason::Semantic
14008 | formualizer_common::PreparationStaleReason::Provider
14009 }
14010 )
14011 }
14012
14013 fn prepare_graph_for_routed_evaluation(
14014 &mut self,
14015 targets: &[crate::engine::EvaluationTarget],
14016 options: &crate::engine::TargetEvalOptions<'_>,
14017 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
14018 const MAX_TRANSIENT_PREPARATION_RETRIES: usize = 2;
14019 let mut retries = 0usize;
14020 loop {
14021 match self.prepare_graph_for_targets_unobserved(targets, options) {
14022 Err(error)
14023 if Self::transient_target_preparation_stale(&error)
14024 && retries < MAX_TRANSIENT_PREPARATION_RETRIES =>
14025 {
14026 retries = retries.saturating_add(1);
14027 }
14028 result => return result,
14029 }
14030 }
14031 }
14032
14033 fn evaluate_mixed_targets(
14034 &mut self,
14035 targets: &[crate::engine::EvaluationTarget],
14036 delta: Option<&mut DeltaCollector>,
14037 ) -> Result<EvalResult, ExcelError> {
14038 let _source_cache = self.source_cache_session();
14039 let cancel = self.active_cancel_flag.clone();
14040 let options = crate::engine::TargetEvalOptions {
14041 request_id: self
14042 .active_evaluation_resource_request
14043 .as_ref()
14044 .map(|stats| stats.request_id),
14045 cancel,
14046 deadline: None,
14047 budgets: None,
14048 opaque_policy: crate::engine::OpaquePreparePolicy::Widen,
14049 };
14050 self.prepare_and_execute_target_recipe(targets, &options, delta)
14051 }
14052
14053 fn prepare_and_execute_target_recipe(
14054 &mut self,
14055 targets: &[crate::engine::EvaluationTarget],
14056 options: &crate::engine::TargetEvalOptions<'_>,
14057 delta: Option<&mut DeltaCollector>,
14058 ) -> Result<EvalResult, ExcelError> {
14059 let preparation = self.prepare_graph_for_routed_evaluation(targets, options)?;
14060 self.execute_prepared_target_recipe(targets, &preparation.widened_scope, delta)
14061 }
14062
14063 fn execute_prepared_target_recipe(
14064 &mut self,
14065 targets: &[crate::engine::EvaluationTarget],
14066 scope: &crate::engine::PrepareScope,
14067 delta: Option<&mut DeltaCollector>,
14068 ) -> Result<EvalResult, ExcelError> {
14069 self.require_unified_authority()?;
14070 if matches!(scope, crate::engine::PrepareScope::Workbook)
14071 && let Some(stats) = self.active_evaluation_resource_request.as_mut()
14072 {
14073 stats.workbook_exact_attempts = stats.workbook_exact_attempts.max(1);
14074 }
14075 let mut roots = self.resolve_target_producers(targets)?;
14076 if let crate::engine::PrepareScope::Sheets(sheets) = scope {
14077 let request_id = self
14078 .active_evaluation_resource_request
14079 .as_ref()
14080 .map(|request| request.request_id);
14081 let root_count = roots.len();
14082 let mut widened_roots =
14083 OrderedTargetProducers::from_ordered(std::mem::take(&mut roots))
14084 .map_err(|_| target_root_allocation_error(root_count, request_id))?;
14085 let sheet_ids = sheets
14086 .iter()
14087 .filter_map(|sheet| self.graph.sheet_id(sheet))
14088 .collect::<FxHashSet<_>>();
14089 for vertex in self.graph.formula_vertices() {
14090 if sheet_ids.contains(&self.graph.get_vertex_sheet_id(vertex)) {
14091 widened_roots
14092 .push(crate::engine::target_preparation::TargetProducer::Legacy(
14093 vertex,
14094 ))
14095 .map_err(|_| {
14096 target_root_allocation_error(widened_roots.len() + 1, request_id)
14097 })?;
14098 }
14099 }
14100 roots = widened_roots.into_vec();
14101 }
14102 self.begin_evaluation_request();
14103 #[cfg(any(test, feature = "legacy_oracle"))]
14104 self.graph.flush_pending_edge_deltas();
14105 let workbook_scope = matches!(scope, crate::engine::PrepareScope::Workbook);
14106 if workbook_scope {
14107 if let Some(delta) = delta {
14108 self.evaluate_all_with_delta_collector(delta)
14109 } else {
14110 self.evaluate_all_legacy_impl()
14111 }
14112 } else {
14113 self.evaluate_legacy_target_roots(&roots, delta)
14114 }
14115 }
14116
14117 fn legacy_coordinate_targets(
14118 &mut self,
14119 targets: &[(&str, u32, u32)],
14120 ) -> Vec<crate::engine::EvaluationTarget> {
14121 targets
14122 .iter()
14123 .map(|(sheet, row, col)| {
14124 self.graph.sheet_id_mut(sheet);
14127 crate::engine::EvaluationTarget::Cell {
14128 sheet: (*sheet).to_string(),
14129 row: *row,
14130 col: *col,
14131 }
14132 })
14133 .collect()
14134 }
14135
14136 pub fn evaluate_targets(
14138 &mut self,
14139 targets: &[crate::engine::EvaluationTarget],
14140 ) -> Result<EvalResult, ExcelError> {
14141 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14142 engine.observe_function_semantic_epoch()?;
14143 engine.validate_deterministic_mode()?;
14144 engine.evaluate_mixed_targets(targets, None)
14145 })
14146 }
14147
14148 pub fn evaluate_targets_with_options(
14150 &mut self,
14151 targets: &[crate::engine::EvaluationTarget],
14152 options: crate::engine::TargetEvalOptions<'_>,
14153 ) -> Result<EvalResult, ExcelError> {
14154 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14155 engine.active_cancel_flag = options.cancel.clone();
14156 engine.active_evaluation_deadline = options.deadline;
14157 let result = (|| {
14158 engine.cancellation_checkpoint("Evaluation cancelled before target preparation")?;
14159 engine.observe_function_semantic_epoch()?;
14160 engine.validate_deterministic_mode()?;
14161 let _source_cache = engine.source_cache_session();
14162 engine.prepare_and_execute_target_recipe(targets, &options, None)
14163 })();
14164 engine.active_cancel_flag = None;
14165 engine.active_evaluation_deadline = None;
14166 result
14167 })
14168 }
14169
14170 pub fn evaluate_targets_with_delta(
14172 &mut self,
14173 targets: &[crate::engine::EvaluationTarget],
14174 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
14175 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
14176 engine.observe_function_semantic_epoch()?;
14177 engine.validate_deterministic_mode()?;
14178 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14179 let result = engine.evaluate_mixed_targets(targets, Some(&mut collector))?;
14180 Ok((result, collector.finish_target()))
14181 })
14182 }
14183
14184 pub fn evaluate_until(
14186 &mut self,
14187 targets: &[(&str, u32, u32)],
14188 ) -> Result<EvalResult, ExcelError> {
14189 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14190 engine.evaluate_until_unobserved(targets)
14191 })
14192 }
14193
14194 fn evaluate_until_unobserved(
14195 &mut self,
14196 targets: &[(&str, u32, u32)],
14197 ) -> Result<EvalResult, ExcelError> {
14198 self.observe_function_semantic_epoch()?;
14199 let targets = self.legacy_coordinate_targets(targets);
14200 self.evaluate_mixed_targets(&targets, None)
14201 }
14202
14203 fn evaluate_until_with_delta_collector(
14204 &mut self,
14205 targets: &[(&str, u32, u32)],
14206 delta: &mut DeltaCollector,
14207 ) -> Result<EvalResult, ExcelError> {
14208 let targets = self.legacy_coordinate_targets(targets);
14209 self.evaluate_mixed_targets(&targets, Some(delta))
14210 }
14211
14212 fn evaluate_legacy_target_roots(
14213 &mut self,
14214 roots: &[crate::engine::target_preparation::TargetProducer],
14215 mut delta: Option<&mut DeltaCollector>,
14216 ) -> Result<EvalResult, ExcelError> {
14217 use crate::engine::target_preparation::TargetProducer;
14218 #[cfg(any(test, feature = "benchmark_internal"))]
14219 {
14220 self.recalc_reuse_probe
14221 .get_mut()
14222 .unwrap()
14223 .legacy_target_requests += 1;
14224 }
14225 let start = crate::instant::FzInstant::now();
14226 let root_vertices = roots
14227 .iter()
14228 .filter_map(|root| match root {
14229 TargetProducer::Legacy(vertex) | TargetProducer::Symbol(vertex) => Some(*vertex),
14230 TargetProducer::ValueOnly(_) => None,
14231 })
14232 .collect::<Vec<_>>();
14233 let mut computed_vertices = 0usize;
14234 let mut cycle_errors = 0usize;
14235 let mut replans = 0usize;
14236 const MAX_REPLAN: usize = 5;
14237 self.graph.authority_sync();
14238 loop {
14239 let (precedents_to_eval, old_vdeps) = self.demand_subgraph(&root_vertices)?;
14240 if precedents_to_eval.is_empty() {
14241 break;
14242 }
14243 #[cfg(any(test, feature = "benchmark_internal"))]
14244 {
14245 self.recalc_reuse_probe
14246 .get_mut()
14247 .unwrap()
14248 .target_schedule_builds += 1;
14249 }
14250 let schedule = {
14251 self.graph.authority_sync();
14252 let mut ledger = self.active_resource_ledger.take();
14253 let result = self.create_authority_schedule(
14254 &precedents_to_eval,
14255 &old_vdeps,
14256 ledger.as_mut(),
14257 );
14258 self.active_resource_ledger = ledger;
14259 result?
14260 };
14261 self.begin_pass(&schedule);
14262 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14263 self.cancellation_checkpoint("Evaluation cancelled before target schedule unit")?;
14264 match unit {
14265 ScheduleUnit::Cycle(index) => {
14266 if self.handle_cycle_unit(
14267 schedule.unit_cycle(index),
14268 delta.as_deref_mut(),
14269 None,
14270 None,
14271 )? > 0
14272 {
14273 cycle_errors = cycle_errors.saturating_add(1);
14274 }
14275 }
14276 ScheduleUnit::Layer(index) => {
14277 let layer = schedule.unit_layer(index);
14278 let evaluated = if let Some(delta) = delta.as_deref_mut() {
14279 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14280 self.evaluate_layer_parallel_with_delta(layer, delta)?
14281 } else {
14282 self.evaluate_layer_sequential_with_delta(layer, delta)?
14283 }
14284 } else if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14285 self.evaluate_layer_parallel(layer)?
14286 } else {
14287 self.evaluate_layer_sequential(layer)?
14288 };
14289 computed_vertices = computed_vertices.saturating_add(evaluated);
14290 }
14291 }
14292 if self.stop_after_unit(&schedule, unit_index) {
14293 break;
14294 }
14295 }
14296 let changed = self.changed_virtual_dep_vertices(&precedents_to_eval, &old_vdeps);
14297 self.resource_checkpoint(0)?;
14298 if !self.finish_target_pass_dirty(&precedents_to_eval, &changed) {
14299 break;
14300 }
14301 if replans >= MAX_REPLAN {
14302 return Err(self.replan_exhausted_error(
14303 MAX_REPLAN,
14304 "targeted legacy dynamic dependency evaluation",
14305 ));
14306 }
14307 replans = replans.saturating_add(1);
14308 }
14309 self.redirty_for_next_recalc();
14310 Ok(EvalResult {
14311 computed_vertices,
14312 cycle_errors,
14313 elapsed: start.elapsed(),
14314 })
14315 }
14316
14317 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
14319 if self.has_staged_formulas() || self.staged_formula_index.has_packages() {
14320 return Err(
14321 Self::plan_stale(formualizer_common::PlanStaleReason::Staged).with_message(
14322 "compatibility recalculation plans require all staged formulas to be prepared",
14323 ),
14324 );
14325 }
14326 let key = self.recalc_plan_key();
14327 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
14328 vertices.sort_unstable();
14329 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
14330 let schedule = if vertices.is_empty() {
14331 crate::engine::Schedule {
14332 units: Vec::new(),
14333 layers: Vec::new(),
14334 cycles: Vec::new(),
14335 }
14336 } else {
14337 self.create_evaluation_schedule_uncached(&vertices, None)?.0
14338 };
14339 self.validate_recalc_plan_key(&key)?;
14340 Ok(RecalcPlan {
14341 key,
14342 kind: RecalcPlanKind::CompatibilityFull {
14343 schedule,
14344 has_dynamic_refs,
14345 },
14346 })
14347 }
14348
14349 pub fn build_recalc_plan_for_targets(
14351 &mut self,
14352 targets: &[crate::engine::EvaluationTarget],
14353 ) -> Result<RecalcPlan, ExcelError> {
14354 self.build_recalc_plan_for_targets_with_options(
14355 targets,
14356 crate::engine::TargetEvalOptions::default(),
14357 )
14358 }
14359
14360 pub fn build_recalc_plan_for_targets_with_options(
14361 &mut self,
14362 targets: &[crate::engine::EvaluationTarget],
14363 options: crate::engine::TargetEvalOptions<'_>,
14364 ) -> Result<RecalcPlan, ExcelError> {
14365 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14366 engine.observe_function_semantic_epoch()?;
14367 engine.validate_deterministic_mode()?;
14368 let _source_cache = engine.source_cache_session();
14369 let preparation = engine.prepare_graph_for_routed_evaluation(targets, &options)?;
14370 #[cfg(any(test, feature = "legacy_oracle"))]
14371 engine.graph.flush_pending_edge_deltas();
14372 let topology = if matches!(
14373 preparation.widened_scope,
14374 crate::engine::PrepareScope::Workbook
14375 ) {
14376 RecalcTopology::Workbook
14377 } else {
14378 RecalcTopology::RunLocalRecipe
14379 };
14380 Ok(RecalcPlan {
14381 key: engine.recalc_plan_key(),
14382 kind: RecalcPlanKind::Target {
14383 targets: targets.to_vec(),
14384 scope: preparation.widened_scope,
14385 topology,
14386 dynamic_policy: DynamicPlanPolicy::BoundedTargetReplan,
14387 },
14388 })
14389 })
14390 }
14391
14392 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
14394 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14395 engine.evaluate_recalc_plan_unobserved(plan)
14396 })
14397 }
14398
14399 pub fn evaluate_recalc_plan_with_controls(
14400 &mut self,
14401 plan: &RecalcPlan,
14402 cancel: Option<crate::engine::CancelToken>,
14403 deadline: Option<Instant>,
14404 ) -> Result<EvalResult, ExcelError> {
14405 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14406 engine.active_cancel_flag = cancel.clone();
14407 engine.active_evaluation_deadline = deadline;
14408 let result = engine.evaluate_recalc_plan_unobserved(plan);
14409 engine.active_cancel_flag = None;
14410 engine.active_evaluation_deadline = None;
14411 result
14412 })
14413 }
14414
14415 fn evaluate_recalc_plan_unobserved(
14416 &mut self,
14417 plan: &RecalcPlan,
14418 ) -> Result<EvalResult, ExcelError> {
14419 #[cfg(any(test, feature = "legacy_oracle"))]
14420 self.graph.flush_pending_edge_deltas();
14421 self.validate_recalc_plan_key(&plan.key)?;
14422 self.cancellation_checkpoint("Evaluation cancelled before recalculation plan execution")?;
14423 self.validate_deterministic_mode()?;
14424
14425 match &plan.kind {
14426 RecalcPlanKind::Target {
14427 targets,
14428 scope,
14429 topology,
14430 dynamic_policy,
14431 } => {
14432 debug_assert_eq!(*dynamic_policy, DynamicPlanPolicy::BoundedTargetReplan);
14433 debug_assert_eq!(
14434 matches!(topology, RecalcTopology::Workbook),
14435 matches!(scope, crate::engine::PrepareScope::Workbook)
14436 );
14437 let _source_cache = self.source_cache_session();
14438 self.execute_prepared_target_recipe(targets, scope, None)
14439 }
14440 RecalcPlanKind::CompatibilityFull {
14441 schedule,
14442 has_dynamic_refs,
14443 } => {
14444 let _source_cache = self.source_cache_session();
14445 self.begin_evaluation_request();
14446 if *has_dynamic_refs {
14447 self.virtual_dep_fallback_activations =
14448 self.virtual_dep_fallback_activations.saturating_add(1);
14449 return self.evaluate_all_coordinator();
14450 }
14451
14452 let start = crate::instant::FzInstant::now();
14453 let dirty_vertices = self.graph.get_evaluation_vertices();
14454 if dirty_vertices.is_empty() {
14455 return Ok(EvalResult {
14456 computed_vertices: 0,
14457 cycle_errors: 0,
14458 elapsed: start.elapsed(),
14459 });
14460 }
14461
14462 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
14463 let mut computed_vertices = 0;
14464 let mut cycle_errors = 0;
14465 for &unit in &schedule.units {
14466 self.cancellation_checkpoint(
14467 "Evaluation cancelled before recalculation plan schedule unit",
14468 )?;
14469 match unit {
14470 ScheduleUnit::Cycle(i) => {
14471 let stamped = self.handle_cycle_unit(
14472 schedule.unit_cycle(i),
14473 None,
14474 Some(&dirty_set),
14475 None,
14476 )?;
14477 if stamped > 0 {
14478 cycle_errors += 1;
14479 }
14480 }
14481 ScheduleUnit::Layer(i) => {
14482 let work: Vec<VertexId> = schedule
14483 .unit_layer(i)
14484 .vertices
14485 .iter()
14486 .copied()
14487 .filter(|v| dirty_set.contains(v))
14488 .collect();
14489 if work.is_empty() {
14490 continue;
14491 }
14492 let temp_layer = crate::engine::scheduler::Layer::new(work);
14493 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
14494 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
14495 } else {
14496 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
14497 }
14498 }
14499 }
14500 }
14501
14502 self.resource_checkpoint(0)?;
14503 self.graph.clear_dirty_flags(&dirty_vertices);
14504 self.redirty_for_next_recalc();
14505 Ok(EvalResult {
14506 computed_vertices,
14507 cycle_errors,
14508 elapsed: start.elapsed(),
14509 })
14510 }
14511 }
14512 }
14513}
14514
14515impl<R> Engine<R>
14516where
14517 R: EvaluationContext,
14518{
14519 fn require_unified_authority(&mut self) -> Result<(), ExcelError> {
14524 self.graph
14525 .authority()
14526 .map(|_| ())
14527 .map_err(Self::authority_excel_error)
14528 }
14529
14530 fn authority_excel_error(error: crate::engine::authority::store::AuthorityError) -> ExcelError {
14531 use crate::engine::authority::store::AuthorityError;
14532 #[cfg(test)]
14536 if std::env::var_os("FZ_AUTHORITY_DEFERRED_TRACE").is_some() {
14537 eprintln!("M1B_AUTHORITY_ERROR {error:?}");
14538 }
14539 let kind = match error {
14540 AuthorityError::Unsupported { .. } => ExcelErrorKind::NImpl,
14541 _ => ExcelErrorKind::Error,
14542 };
14543 ExcelError::new(kind).with_message(format!("unified_authority: {error:?}"))
14544 }
14545
14546 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
14548 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
14550 engine.evaluate_all_unobserved()
14551 })
14552 }
14553
14554 fn evaluate_all_unobserved(&mut self) -> Result<EvalResult, ExcelError> {
14555 debug_assert!(
14556 !self.graph.deferred_dirty_active(),
14557 "deferred-dirty scope leaked into evaluate_all: a begin_deferred_dirty \
14558 was not balanced by end_deferred_dirty"
14559 );
14560 self.observe_function_semantic_epoch()?;
14561 self.lookup_index_cache.reset_counters();
14562 let _source_cache = self.source_cache_session();
14563 self.validate_deterministic_mode()?;
14564 if self.config.defer_graph_building {
14565 self.build_graph_all()?;
14567 }
14568 self.evaluate_all_coordinator()
14569 }
14570
14571 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
14574 self.require_unified_authority()?;
14575 self.begin_evaluation_request();
14576 self.evaluate_all_legacy_impl()
14577 }
14578
14579 fn legacy_pass_run_units(
14585 &mut self,
14586 schedule: &crate::engine::scheduler::Schedule,
14587 ) -> Result<(usize, usize), ExcelError> {
14588 let mut computed_vertices = 0;
14589 let mut cycle_count = 0;
14590 self.begin_pass(schedule);
14591 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14592 match unit {
14593 ScheduleUnit::Cycle(i) => {
14594 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
14595 cycle_count += 1;
14596 }
14597 }
14598 ScheduleUnit::Layer(i) => {
14599 let layer = schedule.unit_layer(i);
14600 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14601 computed_vertices += self.evaluate_layer_parallel(layer)?;
14602 } else {
14603 computed_vertices += self.evaluate_layer_sequential(layer)?;
14604 }
14605 }
14606 }
14607 if self.stop_after_unit(schedule, unit_index) {
14608 break;
14609 }
14610 }
14611 Ok((computed_vertices, cycle_count))
14612 }
14613
14614 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
14621 self.reset_virtual_dep_telemetry_if_disabled();
14622 let _span_eval =
14623 crate::engine::trace::fz_span!(tracing::Level::INFO, "evaluate", "evaluate.legacy");
14624 let start = crate::instant::FzInstant::now();
14625 let mut computed_vertices = 0;
14626 let mut cycle_errors = 0;
14627 let mut replan_iterations = 0;
14628 const MAX_REPLAN: usize = 5;
14629 let mut telemetry = self
14630 .config
14631 .enable_virtual_dep_telemetry
14632 .then(|| self.start_virtual_dep_telemetry());
14633
14634 loop {
14635 let to_evaluate = self.graph.get_evaluation_vertices();
14636 if to_evaluate.is_empty() {
14637 if let Some(t) = telemetry.as_mut()
14638 && t.bailout_reason.is_none()
14639 {
14640 t.bailout_reason = Some("no_work");
14641 }
14642 break;
14643 }
14644
14645 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14646 if let Some(t) = telemetry.as_mut() {
14647 Self::accumulate_schedule_meta(t, &meta);
14648 }
14649
14650 let (pass_computed, pass_cycles) = self.legacy_pass_run_units(&schedule)?;
14651 computed_vertices += pass_computed;
14652 cycle_errors += pass_cycles;
14653
14654 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14656 if let Some(t) = telemetry.as_mut() {
14657 t.changed_vdeps_total += changed_vertices.len();
14658 }
14659
14660 self.resource_checkpoint(0)?;
14661 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
14662 if let Some(t) = telemetry.as_mut() {
14663 t.bailout_reason = Some("converged");
14664 }
14665 break;
14666 }
14667 if replan_iterations >= MAX_REPLAN {
14668 if let Some(mut t) = telemetry.take() {
14669 t.bailout_reason = Some("max_replan");
14670 t.replan_iterations = replan_iterations;
14671 self.last_virtual_dep_telemetry = t;
14672 }
14673 return Err(
14674 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
14675 );
14676 }
14677
14678 replan_iterations += 1;
14679 }
14680
14681 if let Some(mut t) = telemetry {
14682 t.replan_iterations = replan_iterations;
14683 self.last_virtual_dep_telemetry = t;
14684 }
14685
14686 self.redirty_for_next_recalc();
14688
14689 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14691
14692 Ok(EvalResult {
14693 computed_vertices,
14694 cycle_errors,
14695 elapsed: start.elapsed(),
14696 })
14697 }
14698
14699 pub fn evaluate_all_with_target_delta(
14700 &mut self,
14701 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
14702 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
14703 engine.observe_function_semantic_epoch()?;
14704 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14705 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
14706 Ok((result, collector.finish_target()))
14707 })
14708 }
14709
14710 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
14711 self.evaluate_all_with_delta_policy(EvalDeltaCompatibilityPolicy::Unlimited)
14712 }
14713
14714 pub fn evaluate_all_with_delta_policy(
14715 &mut self,
14716 policy: EvalDeltaCompatibilityPolicy,
14717 ) -> Result<(EvalResult, EvalDelta), ExcelError> {
14718 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
14719 engine.observe_function_semantic_epoch()?;
14720 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14721 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
14722 Ok((result, collector.finish_with_policy(policy)?))
14723 })
14724 }
14725
14726 fn evaluate_all_with_delta_collector(
14727 &mut self,
14728 delta: &mut DeltaCollector,
14729 ) -> Result<EvalResult, ExcelError> {
14730 let _source_cache = self.source_cache_session();
14731 if self.config.defer_graph_building {
14732 self.build_graph_all()?;
14733 }
14734 self.require_unified_authority()?;
14735 self.begin_evaluation_request();
14736 self.reset_virtual_dep_telemetry_if_disabled();
14737 let _span_eval = crate::engine::trace::fz_span!(
14738 tracing::Level::INFO,
14739 "evaluate",
14740 "evaluate.legacy_delta"
14741 );
14742 let start = crate::instant::FzInstant::now();
14743 let mut computed_vertices = 0;
14744 let mut cycle_errors = 0;
14745
14746 let mut replan_iterations = 0;
14747 const MAX_REPLAN: usize = 5;
14748 let mut telemetry = self
14749 .config
14750 .enable_virtual_dep_telemetry
14751 .then(|| self.start_virtual_dep_telemetry());
14752
14753 loop {
14754 let to_evaluate = self.graph.get_evaluation_vertices();
14755 if to_evaluate.is_empty() {
14756 if let Some(t) = telemetry.as_mut()
14757 && t.bailout_reason.is_none()
14758 {
14759 t.bailout_reason = Some("no_work");
14760 }
14761 break;
14762 }
14763
14764 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14765 if let Some(t) = telemetry.as_mut() {
14766 Self::accumulate_schedule_meta(t, &meta);
14767 }
14768
14769 self.begin_pass(&schedule);
14770 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14771 match unit {
14772 ScheduleUnit::Cycle(i) => {
14773 if self.handle_cycle_unit(
14774 schedule.unit_cycle(i),
14775 Some(delta),
14776 None,
14777 None,
14778 )? > 0
14779 {
14780 cycle_errors += 1;
14781 }
14782 }
14783 ScheduleUnit::Layer(i) => {
14784 let layer = schedule.unit_layer(i);
14785 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14786 computed_vertices +=
14787 self.evaluate_layer_parallel_with_delta(layer, delta)?;
14788 } else {
14789 computed_vertices +=
14790 self.evaluate_layer_sequential_with_delta(layer, delta)?;
14791 }
14792 }
14793 }
14794 if self.stop_after_unit(&schedule, unit_index) {
14795 break;
14796 }
14797 }
14798
14799 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14800 if let Some(t) = telemetry.as_mut() {
14801 t.changed_vdeps_total += changed_vertices.len();
14802 }
14803 self.resource_checkpoint(0)?;
14804 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
14805 if let Some(t) = telemetry.as_mut() {
14806 t.bailout_reason = Some("converged");
14807 }
14808 break;
14809 }
14810 if replan_iterations >= MAX_REPLAN {
14811 if let Some(mut t) = telemetry.take() {
14812 t.bailout_reason = Some("max_replan");
14813 t.replan_iterations = replan_iterations;
14814 self.last_virtual_dep_telemetry = t;
14815 }
14816 return Err(
14817 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
14818 );
14819 }
14820 replan_iterations += 1;
14821 }
14822
14823 if let Some(mut t) = telemetry {
14824 t.replan_iterations = replan_iterations;
14825 self.last_virtual_dep_telemetry = t;
14826 }
14827
14828 self.redirty_for_next_recalc();
14829 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14830
14831 Ok(EvalResult {
14832 computed_vertices,
14833 cycle_errors,
14834 elapsed: start.elapsed(),
14835 })
14836 }
14837
14838 pub fn evaluate_cell(
14848 &mut self,
14849 sheet: &str,
14850 row: u32,
14851 col: u32,
14852 ) -> Result<Option<LiteralValue>, ExcelError> {
14853 self.observe_evaluation_resource_request(EvaluationRequestKind::Cell, |engine| {
14854 engine.evaluate_cell_unobserved(sheet, row, col)
14855 })
14856 }
14857
14858 fn evaluate_cell_unobserved(
14859 &mut self,
14860 sheet: &str,
14861 row: u32,
14862 col: u32,
14863 ) -> Result<Option<LiteralValue>, ExcelError> {
14864 if row == 0 || col == 0 {
14865 return Err(ExcelError::new(ExcelErrorKind::Ref)
14866 .with_message("Row and column must be >= 1".to_string()));
14867 }
14868
14869 let result = self.evaluate_cells(&[(sheet, row, col)])?;
14870
14871 match result.len() {
14872 0 => Ok(None),
14873 1 => {
14874 let v = result.into_iter().next().unwrap();
14875 Ok(v)
14876 }
14877 _ => unreachable!("evaluate_cells returned unexpected length"),
14878 }
14879 }
14880
14881 pub fn evaluate_cells(
14888 &mut self,
14889 targets: &[(&str, u32, u32)],
14890 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14891 self.observe_evaluation_resource_request(EvaluationRequestKind::Cells, |engine| {
14892 engine.evaluate_cells_unobserved(targets)
14893 })
14894 }
14895
14896 fn evaluate_cells_unobserved(
14897 &mut self,
14898 targets: &[(&str, u32, u32)],
14899 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14900 self.observe_function_semantic_epoch()?;
14901 debug_assert!(
14902 !self.graph.deferred_dirty_active(),
14903 "deferred-dirty scope leaked into evaluate_cells: a begin_deferred_dirty \
14904 was not balanced by end_deferred_dirty"
14905 );
14906 self.validate_deterministic_mode()?;
14907 if targets.is_empty() {
14908 return Ok(Vec::new());
14909 }
14910 let typed_targets = self.legacy_coordinate_targets(targets);
14911 self.evaluate_mixed_targets(&typed_targets, None)?;
14912 Ok(targets
14913 .iter()
14914 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
14915 .collect())
14916 }
14917
14918 pub fn evaluate_cells_cancellable(
14919 &mut self,
14920 targets: &[(&str, u32, u32)],
14921 cancel: crate::engine::CancelToken,
14922 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14923 self.observe_evaluation_resource_request(
14924 EvaluationRequestKind::CellsCancellable,
14925 |engine| {
14926 engine.observe_function_semantic_epoch()?;
14927 engine.active_cancel_flag = Some(cancel.clone());
14928 let res = engine.evaluate_cells_cancellable_impl(targets, cancel.as_flag());
14929 engine.active_cancel_flag = None;
14930 res
14931 },
14932 )
14933 }
14934
14935 fn evaluate_cells_cancellable_impl(
14936 &mut self,
14937 targets: &[(&str, u32, u32)],
14938 cancel_flag: &AtomicBool,
14939 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14940 self.validate_deterministic_mode()?;
14941 if targets.is_empty() {
14942 return Ok(Vec::new());
14943 }
14944 if cancel_flag.load(Ordering::Relaxed) {
14945 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
14946 .with_message("Evaluation cancelled before target preparation"));
14947 }
14948 let typed_targets = self.legacy_coordinate_targets(targets);
14949 self.evaluate_mixed_targets(&typed_targets, None)?;
14950 Ok(targets
14951 .iter()
14952 .map(|(sheet, row, col)| self.get_cell_value(sheet, *row, *col))
14953 .collect())
14954 }
14955
14956 pub fn evaluate_cells_with_target_delta(
14957 &mut self,
14958 targets: &[(&str, u32, u32)],
14959 ) -> Result<(Vec<Option<LiteralValue>>, crate::engine::TargetEvalDelta), ExcelError> {
14960 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
14961 engine.observe_function_semantic_epoch()?;
14962 engine.validate_deterministic_mode()?;
14963 if targets.is_empty() {
14964 return Ok((Vec::new(), crate::engine::TargetEvalDelta::default()));
14965 }
14966 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14967 engine.evaluate_until_with_delta_collector(targets, &mut collector)?;
14968 let values = targets
14969 .iter()
14970 .map(|(sheet, row, col)| engine.get_cell_value(sheet, *row, *col))
14971 .collect();
14972 Ok((values, collector.finish_target()))
14973 })
14974 }
14975
14976 pub fn evaluate_cells_with_delta(
14977 &mut self,
14978 targets: &[(&str, u32, u32)],
14979 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
14980 self.evaluate_cells_with_delta_policy(targets, EvalDeltaCompatibilityPolicy::Unlimited)
14981 }
14982
14983 pub fn evaluate_cells_with_delta_policy(
14984 &mut self,
14985 targets: &[(&str, u32, u32)],
14986 policy: EvalDeltaCompatibilityPolicy,
14987 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
14988 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
14989 engine.evaluate_cells_with_delta_unobserved(targets, policy)
14990 })
14991 }
14992
14993 fn evaluate_cells_with_delta_unobserved(
14994 &mut self,
14995 targets: &[(&str, u32, u32)],
14996 policy: EvalDeltaCompatibilityPolicy,
14997 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
14998 self.observe_function_semantic_epoch()?;
14999 self.validate_deterministic_mode()?;
15000 if targets.is_empty() {
15001 return Ok((Vec::new(), EvalDelta::default()));
15002 }
15003 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15004 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
15005 let values = targets
15006 .iter()
15007 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
15008 .collect();
15009 Ok((values, collector.finish_with_policy(policy)?))
15010 }
15011
15012 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
15014 if targets.is_empty() {
15015 return Ok(EvalPlan {
15016 total_vertices_to_evaluate: 0,
15017 layers: Vec::new(),
15018 cycles_detected: 0,
15019 dirty_count: 0,
15020 volatile_count: 0,
15021 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15022 estimated_parallel_layers: 0,
15023 target_cells: Vec::new(),
15024 });
15025 }
15026 if self.config.defer_graph_building && self.has_staged_formulas() {
15027 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
15028 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
15029 ));
15030 }
15031
15032 let addresses: Vec<String> = targets
15034 .iter()
15035 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
15036 .collect();
15037
15038 let mut target_addrs = Vec::new();
15040 for (sheet, row, col) in targets {
15041 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
15042 let coord = Coord::from_excel(*row, *col, true, true);
15043 target_addrs.push(CellRef::new(sheet_id, coord));
15044 }
15045 }
15046
15047 let mut target_vertex_ids = Vec::new();
15049 for addr in &target_addrs {
15050 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
15051 target_vertex_ids.push(vertex_id);
15052 }
15053 }
15054
15055 if target_vertex_ids.is_empty() {
15056 return Ok(EvalPlan {
15057 total_vertices_to_evaluate: 0,
15058 layers: Vec::new(),
15059 cycles_detected: 0,
15060 dirty_count: 0,
15061 volatile_count: 0,
15062 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15063 estimated_parallel_layers: 0,
15064 target_cells: addresses,
15065 });
15066 }
15067
15068 let (precedents_to_eval, vdeps) = self.demand_subgraph(&target_vertex_ids)?;
15070
15071 if precedents_to_eval.is_empty() {
15072 return Ok(EvalPlan {
15073 total_vertices_to_evaluate: 0,
15074 layers: Vec::new(),
15075 cycles_detected: 0,
15076 dirty_count: 0,
15077 volatile_count: 0,
15078 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15079 estimated_parallel_layers: 0,
15080 target_cells: addresses,
15081 });
15082 }
15083
15084 let mut dirty_count = 0;
15086 let mut volatile_count = 0;
15087 for &vertex_id in &precedents_to_eval {
15088 if self.graph.is_dirty(vertex_id) {
15089 dirty_count += 1;
15090 }
15091 if self.graph.is_volatile(vertex_id) {
15092 volatile_count += 1;
15093 }
15094 }
15095
15096 let schedule = self.create_authority_schedule(&precedents_to_eval, &vdeps, None)?;
15098
15099 let mut layers = Vec::new();
15101 let mut estimated_parallel_layers = 0;
15102 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
15103
15104 for layer in &schedule.layers {
15105 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
15106 if parallel_eligible {
15107 estimated_parallel_layers += 1;
15108 }
15109
15110 let sample_cells: Vec<String> = layer
15112 .vertices
15113 .iter()
15114 .take(5)
15115 .filter_map(|&vertex_id| {
15116 self.graph
15117 .get_cell_ref_for_vertex(vertex_id)
15118 .map(|cell_ref| {
15119 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
15120 format!(
15121 "{}!{}{}",
15122 sheet_name,
15123 Self::col_to_letters(cell_ref.coord.col().saturating_add(1)),
15124 cell_ref.coord.row() + 1
15125 )
15126 })
15127 })
15128 .collect();
15129
15130 layers.push(LayerInfo {
15131 vertex_count: layer.vertices.len(),
15132 parallel_eligible,
15133 sample_cells,
15134 });
15135 }
15136
15137 Ok(EvalPlan {
15138 total_vertices_to_evaluate: precedents_to_eval.len(),
15139 layers,
15140 cycles_detected: schedule.cycles.len(),
15141 dirty_count,
15142 volatile_count,
15143 parallel_enabled,
15144 estimated_parallel_layers,
15145 target_cells: addresses,
15146 })
15147 }
15148 fn create_evaluation_schedule(
15150 &mut self,
15151 to_evaluate: &[VertexId],
15152 ) -> Result<EvaluationScheduleBuildOutput, ExcelError> {
15153 #[cfg(any(test, feature = "benchmark_internal"))]
15154 {
15155 self.recalc_reuse_probe.get_mut().unwrap().schedule_requests += 1;
15156 }
15157 #[cfg(any(test, feature = "legacy_oracle"))]
15160 self.graph.flush_pending_edge_deltas();
15161 self.graph.authority_sync();
15163 if self.can_use_static_schedule_cache(to_evaluate) {
15164 let revision = self.schedule_cache_authority_revision();
15166 let current = |e: &CachedScheduleEntry| {
15167 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15168 };
15169 if !self
15170 .cached_static_schedule
15171 .as_ref()
15172 .is_some_and(|c| current(c) && c.candidate_vertices.equals(to_evaluate))
15173 && let Some(i) = self.recent_schedules.iter().position(|e| {
15174 current(e)
15175 && e.candidate_vertices.len() == to_evaluate.len()
15176 && e.candidate_vertices.equals(to_evaluate)
15177 })
15178 {
15179 let hit = self.recent_schedules.remove(i);
15180 if let Some(previous) = self.cached_static_schedule.replace(hit) {
15181 self.retain_recent_schedule(previous);
15182 }
15183 }
15184 if let Some(cached) = self.cached_static_schedule.as_ref()
15185 && cached.topology_epoch == self.topology_epoch
15186 && cached.authority_revision == self.schedule_cache_authority_revision()
15187 && cached.candidate_vertices.equals(to_evaluate)
15188 {
15189 let meta = ScheduleBuildMeta {
15190 candidate_vertices: to_evaluate.len(),
15191 vdeps_vertices: 0,
15192 vdeps_edges: 0,
15193 builder_elapsed_ms: 0,
15194 used_virtual_schedule: false,
15195 schedule_cache_hit: true,
15196 schedule_cache_eligible: true,
15197 };
15198 #[cfg(any(test, feature = "benchmark_internal"))]
15199 {
15200 let mut probe = self.recalc_reuse_probe.lock().unwrap();
15201 probe.schedule_cache_hits += 1;
15202 probe.schedule_shared_handles += 1;
15203 }
15204 return Ok((
15205 EvaluationSchedule::Shared(Arc::clone(&cached.schedule)),
15206 FxHashMap::default(),
15207 meta,
15208 ));
15209 }
15210
15211 let (schedule, vdeps, mut meta) = match self.schedule_from_base(to_evaluate)? {
15212 Some(schedule) => (
15213 schedule,
15214 FxHashMap::default(),
15215 ScheduleBuildMeta {
15216 candidate_vertices: to_evaluate.len(),
15217 vdeps_vertices: 0,
15218 vdeps_edges: 0,
15219 builder_elapsed_ms: 0,
15220 used_virtual_schedule: false,
15221 schedule_cache_hit: false,
15222 schedule_cache_eligible: true,
15223 },
15224 ),
15225 None => self.create_evaluation_schedule_active(to_evaluate)?,
15226 };
15227 meta.schedule_cache_hit = false;
15228 meta.schedule_cache_eligible = true;
15229 #[cfg(any(test, feature = "benchmark_internal"))]
15230 {
15231 self.recalc_reuse_probe
15232 .get_mut()
15233 .unwrap()
15234 .schedule_cache_misses += 1;
15235 }
15236 let schedule = if vdeps.is_empty() {
15237 let mut schedule = schedule;
15240 schedule.units.shrink_to_fit();
15241 for layer in &mut schedule.layers {
15242 layer.vertices.shrink_to_fit();
15243 }
15244 schedule.layers.shrink_to_fit();
15245 for cycle in &mut schedule.cycles {
15246 cycle.shrink_to_fit();
15247 }
15248 schedule.cycles.shrink_to_fit();
15249 let schedule = Arc::new(schedule);
15250 #[cfg(any(test, feature = "benchmark_internal"))]
15251 {
15252 self.recalc_reuse_probe
15253 .get_mut()
15254 .unwrap()
15255 .schedule_shared_handles += 1;
15256 }
15257 let entry = CachedScheduleEntry {
15258 topology_epoch: self.topology_epoch,
15259 authority_revision: self.schedule_cache_authority_revision(),
15260 candidate_vertices: VertexIdRuns::from_slice(to_evaluate),
15261 schedule: Arc::clone(&schedule),
15262 };
15263 if let Some(previous) = self.cached_static_schedule.replace(entry) {
15264 self.retain_recent_schedule(previous);
15265 }
15266 EvaluationSchedule::Shared(schedule)
15267 } else {
15268 EvaluationSchedule::Owned(schedule)
15269 };
15270 return Ok((schedule, vdeps, meta));
15271 }
15272
15273 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_active(to_evaluate)?;
15274 meta.schedule_cache_hit = false;
15275 meta.schedule_cache_eligible = false;
15276 #[cfg(any(test, feature = "benchmark_internal"))]
15277 {
15278 self.recalc_reuse_probe
15279 .get_mut()
15280 .unwrap()
15281 .schedule_cache_ineligible += 1;
15282 }
15283 Ok((EvaluationSchedule::Owned(schedule), vdeps, meta))
15284 }
15285
15286 fn schedule_from_base(
15292 &mut self,
15293 to_evaluate: &[VertexId],
15294 ) -> Result<Option<crate::engine::scheduler::Schedule>, ExcelError> {
15295 let revision = self.schedule_cache_authority_revision();
15296 let current = |e: &&CachedScheduleEntry| {
15297 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15298 };
15299 let Some(base) = [
15302 self.base_schedule.as_ref(),
15303 self.cached_static_schedule.as_ref(),
15304 ]
15305 .into_iter()
15306 .flatten()
15307 .filter(current)
15308 .max_by_key(|e| e.candidate_vertices.len()) else {
15309 return Ok(None);
15310 };
15311 let base_len = base.candidate_vertices.len();
15312 if base_len <= BASE_SCHEDULE_MIN_VERTICES
15313 || to_evaluate.len() < BASE_SCHEDULE_MIN_REQUEST
15314 || to_evaluate.len() > base_len
15315 || base_len > to_evaluate.len().saturating_mul(BASE_SCHEDULE_RATIO)
15316 {
15317 return Ok(None);
15318 }
15319 let mut keep = crate::engine::idset::DenseIdSet::default();
15320 keep.extend(to_evaluate.iter().copied());
15321 let Some((schedule, kept)) = base.schedule.restrict(&keep) else {
15322 return Ok(None);
15323 };
15324 if kept != keep.len() {
15326 return Ok(None);
15327 }
15328 if let Some(ledger) = self.active_resource_ledger.as_mut() {
15329 let layers: usize = schedule
15330 .layers
15331 .iter()
15332 .map(|l| {
15333 l.vertices.capacity() * std::mem::size_of::<VertexId>()
15334 + l.runs.capacity()
15335 * std::mem::size_of::<crate::engine::scheduler::LayerRun>()
15336 })
15337 .sum();
15338 let bytes = (keep.heap_bytes()
15339 + layers
15340 + schedule.layers.capacity()
15341 * std::mem::size_of::<crate::engine::scheduler::Layer>()
15342 + schedule.units.capacity()
15343 * std::mem::size_of::<crate::engine::scheduler::ScheduleUnit>()
15344 + schedule
15345 .cycles
15346 .iter()
15347 .map(|c| c.capacity() * 4)
15348 .sum::<usize>()) as u64;
15349 ledger
15350 .reserve_schedule_discovery(bytes)
15351 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15352 ledger
15353 .release_scratch(bytes)
15354 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15355 }
15356 #[cfg(debug_assertions)]
15357 self.debug_check_restricted_schedule(to_evaluate, &schedule);
15358 #[cfg(any(test, feature = "benchmark_internal"))]
15359 {
15360 self.recalc_reuse_probe
15361 .get_mut()
15362 .unwrap()
15363 .schedule_base_restrictions += 1;
15364 }
15365 Ok(Some(schedule))
15366 }
15367
15368 #[cfg(debug_assertions)]
15372 fn debug_check_restricted_schedule(
15373 &self,
15374 to_evaluate: &[VertexId],
15375 schedule: &crate::engine::scheduler::Schedule,
15376 ) {
15377 let mut position: FxHashMap<VertexId, usize> = FxHashMap::default();
15378 for (u, unit) in schedule.units.iter().enumerate() {
15379 let vertices: &[VertexId] = match *unit {
15380 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
15381 &schedule.unit_layer(i).vertices
15382 }
15383 crate::engine::scheduler::ScheduleUnit::Cycle(i) => schedule.unit_cycle(i),
15384 };
15385 for &v in vertices {
15386 assert!(
15387 position.insert(v, u).is_none(),
15388 "vertex twice in a restricted schedule"
15389 );
15390 }
15391 }
15392 assert_eq!(position.len(), to_evaluate.len());
15393 if to_evaluate.len() > 512 {
15394 return;
15395 }
15396 let Ok(store) = self.graph.authority_plan_store() else {
15399 return;
15400 };
15401 let cells: Vec<(VertexId, (u16, u32, u32))> = to_evaluate
15402 .iter()
15403 .filter_map(|&v| self.graph.authority_cell_of_vertex(v).map(|c| (v, c)))
15404 .collect();
15405 for &(v, (sheet, row, col)) in &cells {
15406 let Some(owner) = store.owner_at((sheet, row, col)) else {
15407 continue;
15408 };
15409 let Ok(refined) = store.refine_owner_column(owner, col, row, row, None) else {
15410 continue;
15411 };
15412 for piece in &refined.pieces {
15413 for edge in &refined.edges[piece.edge_start..piece.edge_end] {
15414 let Some(image) = edge.proj.forward(&piece.domain) else {
15415 continue;
15416 };
15417 for &(d, (s2, r2, c2)) in &cells {
15418 if d == v
15419 || edge.proj.sheet != s2
15420 || !(image.r0 <= r2
15421 && r2 <= image.r1
15422 && image.c0 <= c2
15423 && c2 <= image.c1)
15424 {
15425 continue;
15426 }
15427 let (pd, pv) = (position[&d], position[&v]);
15428 let same_ok = pd == pv
15429 && match schedule.units[pv] {
15430 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
15431 schedule.unit_layer(i).sequential
15432 }
15433 crate::engine::scheduler::ScheduleUnit::Cycle(_) => true,
15434 };
15435 assert!(
15436 pd < pv || same_ok,
15437 "restricted schedule orders {v:?} before its precedent {d:?}"
15438 );
15439 }
15440 }
15441 }
15442 }
15443 }
15444
15445 fn maybe_compress_formulas(&mut self) {
15449 if !self.config.formula_compression {
15450 return;
15451 }
15452 let builds = self.graph.authority_host().builds;
15453 if self.compressed_at_build == Some(builds) {
15454 return;
15455 }
15456 self.compressed_at_build = Some(builds);
15457 if self.has_staged_formulas() {
15458 self.graph.virtualize_family_members();
15461 return;
15462 }
15463 let pool = self.thread_pool.clone();
15464 let (_, garbage) = self.graph.compress_family_formulas(pool.as_deref());
15465 self.graph.virtualize_family_members();
15466 match pool {
15470 Some(pool) if garbage.len() >= 1024 => pool.spawn(move || drop(garbage)),
15471 _ => drop(garbage),
15472 }
15473 }
15474
15475 fn create_evaluation_schedule_active(
15476 &mut self,
15477 to_evaluate: &[VertexId],
15478 ) -> Result<ScheduleBuildOutput, ExcelError> {
15479 self.graph.authority_sync();
15480 self.maybe_compress_formulas();
15481 let mut ledger = self.active_resource_ledger.take();
15482 let result = self.create_evaluation_schedule_uncached(to_evaluate, ledger.as_mut());
15483 self.active_resource_ledger = ledger;
15484 result
15485 }
15486
15487 fn create_evaluation_schedule_uncached(
15488 &self,
15489 to_evaluate: &[VertexId],
15490 #[allow(unused_variables)] ledger: Option<&mut ResourceLedger>,
15491 ) -> Result<ScheduleBuildOutput, ExcelError> {
15492 #[cfg(any(test, feature = "benchmark_internal"))]
15493 {
15494 self.recalc_reuse_probe.lock().unwrap().schedule_builds += 1;
15495 }
15496 let builder = VirtualDepBuilder::new(self);
15497 #[allow(unused_mut)]
15498 let (mut vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
15499 if self.config.enable_virtual_dep_telemetry {
15500 let build_started = crate::instant::FzInstant::now();
15501 let (vdeps, augmented) = builder.build(to_evaluate);
15502 let builder_elapsed_ms = build_started.elapsed().as_millis();
15503 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
15504 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
15505 } else {
15506 let (vdeps, augmented) = builder.build(to_evaluate);
15507 (vdeps, augmented, 0, 0)
15508 };
15509
15510 {
15512 self.freshness_merge_hints(to_evaluate, &mut vdeps);
15513 self.freshness_extent_hints(to_evaluate, &mut vdeps);
15514 }
15515 let mut final_evaluate = to_evaluate.to_vec();
15516 if !augmented.is_empty() {
15517 final_evaluate.extend(augmented);
15518 final_evaluate.sort_unstable();
15519 final_evaluate.dedup();
15520 }
15521
15522 let use_virtual = !vdeps.is_empty();
15523
15524 let schedule = self.create_authority_schedule(&final_evaluate, &vdeps, ledger)?;
15525
15526 let meta = ScheduleBuildMeta {
15527 candidate_vertices: to_evaluate.len(),
15528 vdeps_vertices: vdeps.len(),
15529 vdeps_edges,
15530 builder_elapsed_ms,
15531 used_virtual_schedule: use_virtual,
15532 schedule_cache_hit: false,
15533 schedule_cache_eligible: false,
15534 };
15535
15536 Ok((schedule, vdeps, meta))
15537 }
15538
15539 fn create_authority_schedule(
15540 &self,
15541 candidates: &[VertexId],
15542 vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
15543 mut ledger: Option<&mut ResourceLedger>,
15544 ) -> Result<crate::engine::scheduler::Schedule, ExcelError> {
15545 use crate::engine::authority::{
15546 geom::{Cover, Rect},
15547 plan_schedule, planner,
15548 proj::{AxisMap, RefProj},
15549 store::{EdgeKey, Tag},
15550 };
15551 let failure = |message: String| {
15552 ExcelError::new(ExcelErrorKind::Error)
15553 .with_message(format!("unified_authority planner: {message}"))
15554 };
15555 self.cancellation_checkpoint("Evaluation cancelled before authority planning")?;
15556 let store = self
15557 .graph
15558 .authority_plan_store()
15559 .map_err(Self::authority_excel_error)?;
15560 if let [only] = candidates
15563 && vdeps.is_empty()
15564 && self.graph.authority_host().observed(*only).is_none()
15565 && let Some(cell) = self.graph.authority_cell_of_vertex(*only)
15566 && cell.0 != crate::engine::authority::geom::SYMBOL_SHEET
15567 && let Some(single) = planner::plan_single(store, cell)
15568 {
15569 #[cfg(debug_assertions)]
15570 {
15571 let mut cover = Cover::new();
15572 cover.insert_rect(cell.0, &Rect::new(cell.1, cell.2, cell.1, cell.2));
15573 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
15574 .expect("general plan of one cell");
15575 assert_eq!(
15576 general.cells.as_slice(),
15577 &[single],
15578 "single-cell plan differs from the planner at {cell:?}"
15579 );
15580 }
15581 let adapted = plan_schedule::schedule(
15582 &[single],
15583 0,
15584 None,
15585 |cell| {
15586 self.graph
15587 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15588 .ok_or_else(|| failure("missing executor identity".to_owned()))
15589 },
15590 |_work| Ok(()),
15591 )
15592 .map_err(|error| match error {
15593 plan_schedule::ScheduleError::Runtime(error) => error,
15594 other => failure(format!("{other:?}")),
15595 })?;
15596 if let Some(ledger) = ledger {
15597 ledger
15598 .reserve_schedule_discovery(adapted.peak_heap_bytes)
15599 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15600 ledger
15601 .release_scratch(adapted.peak_heap_bytes)
15602 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15603 }
15604 return Ok(adapted.schedule);
15605 }
15606 if (2..=planner::SMALL_PLAN_MAX).contains(&candidates.len())
15609 && vdeps.is_empty()
15610 && candidates
15611 .iter()
15612 .all(|&v| self.graph.authority_host().observed(v).is_none())
15613 {
15614 let cells: Option<Vec<(u16, u32, u32)>> = candidates
15615 .iter()
15616 .map(|&v| self.graph.authority_cell_of_vertex(v))
15617 .collect();
15618 if let Some(small) = cells.as_deref().and_then(|c| planner::plan_small(store, c)) {
15619 #[cfg(debug_assertions)]
15620 {
15621 let mut cover = Cover::new();
15622 for c in cells.as_deref().unwrap_or_default() {
15623 cover.insert_rect(c.0, &Rect::new(c.1, c.2, c.1, c.2));
15624 }
15625 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
15626 .expect("general plan of a small request");
15627 let key = |c: &planner::OrderedCell| (c.sheet, c.row, c.col, c.id, c.owner);
15628 let mut a: Vec<_> = general.cells.iter().map(key).collect();
15629 let mut b: Vec<_> = small.iter().map(key).collect();
15630 a.sort_unstable();
15631 b.sort_unstable();
15632 assert_eq!(a, b, "small plan cells differ from the planner");
15633 assert!(
15634 general.cells.iter().all(|c| c.cycle.is_none()),
15635 "small plan of a cyclic request"
15636 );
15637 }
15638 let adapted = plan_schedule::schedule(
15639 &small,
15640 0,
15641 None,
15642 |cell| {
15643 self.graph
15644 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15645 .ok_or_else(|| failure("missing executor identity".to_owned()))
15646 },
15647 |_work| Ok(()),
15648 )
15649 .map_err(|error| match error {
15650 plan_schedule::ScheduleError::Runtime(error) => error,
15651 other => failure(format!("{other:?}")),
15652 })?;
15653 if let Some(ledger) = ledger {
15654 ledger
15655 .reserve_schedule_discovery(adapted.peak_heap_bytes)
15656 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15657 ledger
15658 .release_scratch(adapted.peak_heap_bytes)
15659 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15660 }
15661 return Ok(adapted.schedule);
15662 }
15663 }
15664 let mut cover = Cover::new();
15669 {
15670 let cell_of = |&id: &VertexId| {
15671 self.graph
15672 .authority_cell_of_vertex(id)
15673 .map(|(sheet, row, col)| (sheet, col, row))
15674 };
15675 let cells: Vec<(u16, u32, u32)> = match self.thread_pool.as_deref() {
15678 Some(pool) if candidates.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
15679 use rayon::prelude::*;
15680 pool.install(|| {
15681 let mut cells: Vec<_> = candidates.par_iter().filter_map(cell_of).collect();
15682 cells.par_sort_unstable();
15683 cells
15684 })
15685 }
15686 _ => {
15687 let mut cells: Vec<_> = candidates.iter().filter_map(cell_of).collect();
15688 cells.sort_unstable();
15689 cells
15690 }
15691 };
15692 let mut i = 0;
15693 while i < cells.len() {
15694 let (sheet, col, r0) = cells[i];
15695 let mut r1 = r0;
15696 let mut j = i + 1;
15697 while j < cells.len() && cells[j].0 == sheet && cells[j].1 == col {
15698 if cells[j].2 > r1 + 1 {
15699 break;
15700 }
15701 r1 = r1.max(cells[j].2);
15702 j += 1;
15703 }
15704 cover.insert_rect(sheet, &Rect::new(r0, col, r1, col));
15705 i = j;
15706 }
15707 }
15708 let mut hints = Vec::new();
15709 for (&reader, deps) in vdeps {
15710 let Some(reader) = self.graph.authority_cell_of_vertex(reader) else {
15711 continue;
15712 };
15713 for &dependency in deps {
15714 let Some(dep) = self.graph.authority_cell_of_vertex(dependency) else {
15715 continue;
15716 };
15717 hints.push(planner::PlanHint {
15718 reader: (reader.0, reader.2, reader.1),
15719 edge: EdgeKey {
15720 dep_sheet: reader.0,
15721 tag: Tag::X,
15722 lk: u32::MAX,
15723 proj: RefProj {
15724 sheet: dep.0,
15725 rows: AxisMap::fixed(dep.1, dep.1),
15726 cols: AxisMap::fixed(dep.2, dep.2),
15727 },
15728 },
15729 });
15730 }
15731 }
15732 let host = self.graph.authority_host();
15734 for &id in candidates.iter().filter(|_| host.has_observed()) {
15735 let Some(reads) = host.observed(id) else {
15736 continue;
15737 };
15738 let Some(reader) = self.graph.authority_cell_of_vertex(id) else {
15739 continue;
15740 };
15741 for &(sheet, r0, c0, r1, c1) in reads {
15742 hints.push(planner::PlanHint {
15743 reader: (reader.0, reader.2, reader.1),
15744 edge: EdgeKey {
15745 dep_sheet: reader.0,
15746 tag: Tag::X,
15747 lk: u32::MAX,
15748 proj: RefProj {
15749 sheet,
15750 rows: AxisMap::fixed(r0, r1),
15751 cols: AxisMap::fixed(c0, c1),
15752 },
15753 },
15754 });
15755 }
15756 }
15757 hints.sort_unstable_by_key(|hint| hint.reader);
15758 let checkpoint = ledger
15759 .as_ref()
15760 .map_or(0, |ledger| ledger.scratch_checkpoint());
15761 let scratch_limit = ledger
15762 .as_ref()
15763 .and_then(|ledger| ledger.schedule_discovery_limit())
15764 .map(|limit| limit.saturating_sub(checkpoint));
15765 let ordered = planner::plan_with_hints(store, &cover, &hints, scratch_limit, None, None)
15766 .map_err(|error| failure(format!("{error:?}")))?;
15767 let adapted = plan_schedule::schedule(
15771 &ordered.cells,
15772 ordered.heap_bytes(),
15773 scratch_limit,
15774 |cell| {
15775 self.graph
15776 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15777 .ok_or_else(|| failure("missing executor identity".to_owned()))
15778 },
15779 |_work| {
15780 self.cancellation_checkpoint("Evaluation cancelled during authority planning")?;
15781 if let Some(ledger) = ledger.as_deref_mut() {
15782 ledger
15783 .checkpoint_deadline()
15784 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15785 }
15786 Ok(())
15787 },
15788 )
15789 .map_err(|error| match error {
15790 plan_schedule::ScheduleError::Runtime(error) => error,
15791 other => failure(format!("{other:?}")),
15792 })?;
15793 if let Some(ledger) = ledger {
15794 let peak = ordered.peak_heap_bytes.max(adapted.peak_heap_bytes);
15795 ledger
15796 .reserve_schedule_discovery(peak)
15797 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15798 ledger
15799 .release_scratch(peak)
15800 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15801 }
15802 match self.thread_pool.as_deref() {
15804 Some(pool) if ordered.cells.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
15805 pool.spawn(move || drop(ordered))
15806 }
15807 _ => drop(ordered),
15808 }
15809 Ok(adapted.schedule)
15810 }
15811
15812 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
15818 {
15819 let host = self.graph.authority_host();
15823 !to_evaluate.is_empty()
15824 && !self.freshness_has_hints()
15825 && to_evaluate
15826 .iter()
15827 .all(|&v| !self.graph.is_dynamic(v) || host.observed(v).is_some())
15828 }
15829 }
15830
15831 fn schedule_cache_authority_revision(&self) -> (u64, u64) {
15832 {
15833 let host = self.graph.authority_host();
15836 (host.revision(), host.rev_dyn())
15837 }
15838 }
15839
15840 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
15841 VirtualDepTelemetry {
15842 fallback_mode_activations: self.virtual_dep_fallback_activations,
15843 ..VirtualDepTelemetry::default()
15844 }
15845 }
15846
15847 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
15848 telemetry.candidate_vertices_total += meta.candidate_vertices;
15849 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
15850 telemetry.vdeps_edges_total += meta.vdeps_edges;
15851 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
15852 if meta.schedule_cache_eligible {
15853 if meta.schedule_cache_hit {
15854 telemetry.schedule_cache_hits += 1;
15855 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
15856 } else {
15857 telemetry.schedule_cache_misses += 1;
15858 }
15859 }
15860 if meta.used_virtual_schedule {
15861 telemetry.schedule_virtual_passes += 1;
15862 } else {
15863 telemetry.schedule_static_passes += 1;
15864 }
15865 }
15866
15867 fn finish_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
15872 self.finish_pass_dirty_scoped(to_evaluate, changed, true)
15873 }
15874
15875 fn finish_target_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
15878 self.finish_pass_dirty_scoped(to_evaluate, changed, false)
15879 }
15880
15881 fn finish_pass_dirty_scoped(
15882 &mut self,
15883 to_evaluate: &[VertexId],
15884 changed: &[VertexId],
15885 #[allow(unused_variables)] whole_workbook: bool,
15886 ) -> bool {
15887 self.freshness_finish_pass(to_evaluate, changed, whole_workbook)
15888 }
15889
15890 fn begin_pass(
15892 &mut self,
15893 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
15894 ) {
15895 self.freshness_begin_pass(schedule);
15896 }
15897
15898 fn stop_after_unit(
15900 &mut self,
15901 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
15902 #[allow(unused_variables)] index: usize,
15903 ) -> bool {
15904 self.freshness_stop_after_unit(schedule, index)
15905 }
15906
15907 fn changed_virtual_dep_vertices(
15908 &mut self,
15909 to_evaluate: &[VertexId],
15910 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
15911 ) -> Vec<VertexId> {
15912 #[cfg(test)]
15913 if self.force_virtual_dep_changes_remaining_for_test > 0
15914 && let Some(vertex) = to_evaluate.first().copied()
15915 {
15916 self.force_virtual_dep_changes_remaining_for_test -= 1;
15917 return vec![vertex];
15918 }
15919 if self.freshness_armed() {
15922 return Vec::new();
15923 }
15924 if !to_evaluate
15925 .iter()
15926 .copied()
15927 .any(|v| self.graph.is_dynamic(v))
15928 {
15929 return Vec::new();
15930 }
15931
15932 let builder = VirtualDepBuilder::new(self);
15933 let (new_vdeps, _) = builder.build(to_evaluate);
15934
15935 let mut candidates = FxHashSet::default();
15936 candidates.extend(old_vdeps.keys().copied());
15937 candidates.extend(new_vdeps.keys().copied());
15938
15939 let mut changed = Vec::new();
15940 for v in candidates {
15941 if old_vdeps.get(&v) != new_vdeps.get(&v) {
15942 changed.push(v);
15943 }
15944 }
15945 changed
15946 }
15947
15948 #[allow(clippy::type_complexity)]
15954 fn demand_subgraph(
15955 &self,
15956 targets: &[VertexId],
15957 ) -> Result<
15958 (
15959 Vec<VertexId>,
15960 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
15961 ),
15962 ExcelError,
15963 > {
15964 self.authority_demand_subgraph(targets)
15965 }
15966
15967 #[allow(clippy::type_complexity)]
15973 fn authority_demand_subgraph(
15974 &self,
15975 targets: &[VertexId],
15976 ) -> Result<
15977 (
15978 Vec<VertexId>,
15979 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
15980 ),
15981 ExcelError,
15982 > {
15983 use crate::engine::authority::geom::{Cell, Rect, SYMBOL_SHEET};
15984 use crate::engine::authority::store::TagFilter;
15985 use rustc_hash::{FxHashMap, FxHashSet};
15986 let store = self
15987 .graph
15988 .authority_plan_store()
15989 .map_err(Self::authority_excel_error)?;
15990 let ids = store.ids();
15991 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
15992 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default();
15993 let mut visited: FxHashSet<Cell> = FxHashSet::default();
15994 let mut stack: Vec<(Cell, u32)> = Vec::new();
15997 let push_formulas = |stack: &mut Vec<(Cell, u32)>,
15999 visited: &FxHashSet<Cell>,
16000 sheet: u16,
16001 rect: Rect| {
16002 for col in rect.c0..=rect.c1 {
16003 ids.visit_runs_in(sheet, col, rect.r0, rect.r1, &mut |h| {
16004 let run = ids.run(h);
16005 let r0 = run.row_start.max(rect.r0);
16006 let r1 = (run.row_start + run.len - 1).min(rect.r1);
16007 for row in r0..=r1 {
16008 if !visited.contains(&(sheet, row, col)) {
16009 stack.push(((sheet, row, col), run.first_id + (row - run.row_start)));
16010 }
16011 }
16012 });
16013 }
16014 };
16015 for &v in targets {
16016 if let Some(table) = self.graph.table_by_vertex(v) {
16017 let (s, e) = (table.range.start, table.range.end);
16021 push_formulas(
16022 &mut stack,
16023 &visited,
16024 s.sheet_id,
16025 Rect::new(s.coord.row(), s.coord.col(), e.coord.row(), e.coord.col()),
16026 );
16027 } else if let Some(cell) = self.graph.authority_cell_of_vertex(v) {
16028 stack.push((cell, crate::engine::authority::identity::NO_VID));
16029 }
16030 }
16031 let mut hits = Vec::new();
16032 #[cfg(any(test, feature = "benchmark_internal"))]
16033 let (mut probe_vertices, mut probe_clean_formulas, mut probe_edges, mut probe_dynamic) =
16034 (0, 0, 0, 0);
16035 while let Some((cell, id)) = stack.pop() {
16036 if !visited.insert(cell) {
16037 continue;
16038 }
16039 let Some(v) = self.graph.authority_vertex_of_formula(id, cell) else {
16040 continue;
16041 };
16042 if !self.graph.vertex_exists(v) {
16043 continue;
16044 }
16045 #[cfg(any(test, feature = "benchmark_internal"))]
16046 {
16047 probe_vertices += 1;
16048 }
16049 match self.graph.get_vertex_kind(v) {
16050 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
16051 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
16052 to_evaluate.insert(v);
16053 } else {
16054 #[cfg(any(test, feature = "benchmark_internal"))]
16055 {
16056 probe_clean_formulas += 1;
16057 }
16058 }
16059 }
16060 VertexKind::NamedScalar | VertexKind::NamedArray => {
16061 to_evaluate.insert(v);
16062 }
16063 _ => {}
16064 }
16065 hits.clear();
16066 store.direct_precedents(cell, TagFilter::All, &mut hits);
16067 #[cfg(any(test, feature = "benchmark_internal"))]
16068 {
16069 probe_edges += hits.len();
16070 }
16071 for &(_, sheet, rect) in &hits {
16072 if sheet != SYMBOL_SHEET {
16075 for anchor in self
16076 .graph
16077 .spill_anchors_in_region(sheet, rect.r0, rect.c0, rect.r1, rect.c1)
16078 {
16079 if anchor != v && self.graph.is_dirty(anchor) {
16080 vdeps.entry(v).or_default().push(anchor);
16081 if let Some(c) = self.graph.authority_cell_of_vertex(anchor) {
16082 stack.push((c, crate::engine::authority::identity::NO_VID));
16083 }
16084 }
16085 }
16086 }
16087 if sheet == SYMBOL_SHEET {
16088 stack.extend((rect.r0..=rect.r1).map(|slot| {
16089 (
16090 (SYMBOL_SHEET, slot, 0),
16091 crate::engine::authority::identity::NO_VID,
16092 )
16093 }));
16094 continue;
16095 }
16096 push_formulas(&mut stack, &visited, sheet, rect);
16097 }
16098 if self.graph.is_dynamic(v) {
16099 #[cfg(any(test, feature = "benchmark_internal"))]
16100 {
16101 probe_dynamic += 1;
16102 }
16103 if let Some(reads) = self.graph.authority_host().observed(v) {
16105 for &(sheet, r0, c0, r1, c1) in reads {
16106 push_formulas(&mut stack, &visited, sheet, Rect::new(r0, c0, r1, c1));
16107 }
16108 }
16109 let (vdeps_map, _) = VirtualDepBuilder::new(self).build(&[v]);
16110 let hinted = self.freshness_hints(v).unwrap_or(&[]);
16113 if let Some(deps) = vdeps_map
16114 .get(&v)
16115 .map(|deps| deps.iter().chain(hinted))
16116 .or(Some([].iter().chain(hinted)))
16117 {
16118 for &u in deps {
16119 vdeps.entry(v).or_default().push(u);
16120 if let Some(c) = self.graph.authority_cell_of_vertex(u) {
16121 stack.push((c, crate::engine::authority::identity::NO_VID));
16122 }
16123 }
16124 }
16125 }
16126 }
16127 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
16128 result.sort_unstable();
16129 for deps in vdeps.values_mut() {
16130 deps.sort_unstable();
16131 deps.dedup();
16132 }
16133 #[cfg(any(test, feature = "benchmark_internal"))]
16134 {
16135 let mut probe = self.recalc_reuse_probe.lock().unwrap();
16136 probe.demand_builds += 1;
16137 probe.demand_vertices += probe_vertices;
16138 probe.demand_clean_formulas += probe_clean_formulas;
16139 probe.demand_explicit_edges += probe_edges;
16140 probe.demand_virtual_builder_calls += probe_dynamic;
16141 }
16142 Ok((result, vdeps))
16143 }
16144
16145 fn col_to_letters(col: u32) -> String {
16147 col_letters_from_1based(col).expect("column index must be >= 1")
16148 }
16149
16150 pub fn evaluate_all_cancellable(
16152 &mut self,
16153 cancel: crate::engine::CancelToken,
16154 ) -> Result<EvalResult, ExcelError> {
16155 self.observe_evaluation_resource_request(EvaluationRequestKind::FullCancellable, |engine| {
16156 engine.observe_function_semantic_epoch()?;
16157 engine.active_cancel_flag = Some(cancel.clone());
16158 let res = engine.evaluate_all_cancellable_impl(cancel.as_flag());
16159 engine.active_cancel_flag = None;
16160 res
16161 })
16162 }
16163
16164 fn evaluate_all_cancellable_impl(
16165 &mut self,
16166 cancel_flag: &AtomicBool,
16167 ) -> Result<EvalResult, ExcelError> {
16168 let _source_cache = self.source_cache_session();
16169 self.validate_deterministic_mode()?;
16170 if self.config.defer_graph_building {
16171 self.build_graph_all()?;
16172 }
16173 if cancel_flag.load(Ordering::Relaxed) {
16174 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16175 .with_message("Evaluation cancelled before scheduling".to_string()));
16176 }
16177 self.require_unified_authority()?;
16178 self.begin_evaluation_request();
16179 self.reset_virtual_dep_telemetry_if_disabled();
16180 let start = crate::instant::FzInstant::now();
16181 let mut computed_vertices = 0;
16182 let mut cycle_errors = 0;
16183
16184 let mut replan_iterations = 0;
16185 const MAX_REPLAN: usize = 5;
16186 let mut telemetry = self
16187 .config
16188 .enable_virtual_dep_telemetry
16189 .then(|| self.start_virtual_dep_telemetry());
16190
16191 loop {
16192 if cancel_flag.load(Ordering::Relaxed) {
16193 if let Some(mut t) = telemetry {
16194 t.bailout_reason = Some("cancelled");
16195 t.replan_iterations = replan_iterations;
16196 self.last_virtual_dep_telemetry = t;
16197 }
16198 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16199 .with_message("Evaluation cancelled before scheduling".to_string()));
16200 }
16201
16202 let to_evaluate = self.graph.get_evaluation_vertices();
16203 if to_evaluate.is_empty() {
16204 if let Some(t) = telemetry.as_mut()
16205 && t.bailout_reason.is_none()
16206 {
16207 t.bailout_reason = Some("no_work");
16208 }
16209 break;
16210 }
16211
16212 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
16213 if let Some(t) = telemetry.as_mut() {
16214 Self::accumulate_schedule_meta(t, &meta);
16215 }
16216
16217 self.begin_pass(&schedule);
16220 for (unit_index, &unit) in schedule.units.iter().enumerate() {
16221 match unit {
16222 ScheduleUnit::Cycle(i) => {
16223 if cancel_flag.load(Ordering::Relaxed) {
16225 if let Some(mut t) = telemetry {
16226 t.bailout_reason = Some("cancelled");
16227 t.replan_iterations = replan_iterations;
16228 self.last_virtual_dep_telemetry = t;
16229 }
16230 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
16231 "Evaluation cancelled during cycle handling".to_string(),
16232 ));
16233 }
16234
16235 if self.handle_cycle_unit(
16236 schedule.unit_cycle(i),
16237 None,
16238 None,
16239 Some(cancel_flag),
16240 )? > 0
16241 {
16242 cycle_errors += 1;
16243 }
16244 }
16245 ScheduleUnit::Layer(i) => {
16246 let layer = schedule.unit_layer(i);
16247 if cancel_flag.load(Ordering::Relaxed) {
16249 if let Some(mut t) = telemetry {
16250 t.bailout_reason = Some("cancelled");
16251 t.replan_iterations = replan_iterations;
16252 self.last_virtual_dep_telemetry = t;
16253 }
16254 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16255 .with_message("Evaluation cancelled between layers".to_string()));
16256 }
16257
16258 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
16260 computed_vertices +=
16261 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
16262 } else {
16263 computed_vertices +=
16264 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
16265 }
16266 }
16267 }
16268 if self.stop_after_unit(&schedule, unit_index) {
16269 break;
16270 }
16271 }
16272
16273 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
16274 if let Some(t) = telemetry.as_mut() {
16275 t.changed_vdeps_total += changed_vertices.len();
16276 }
16277 self.resource_checkpoint(0)?;
16278 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
16279 if let Some(t) = telemetry.as_mut() {
16280 t.bailout_reason = Some("converged");
16281 }
16282 break;
16283 }
16284 if replan_iterations >= MAX_REPLAN {
16285 if let Some(mut t) = telemetry.take() {
16286 t.bailout_reason = Some("max_replan");
16287 t.replan_iterations = replan_iterations;
16288 self.last_virtual_dep_telemetry = t;
16289 }
16290 return Err(
16291 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
16292 );
16293 }
16294 replan_iterations += 1;
16295 }
16296
16297 if let Some(mut t) = telemetry {
16298 t.replan_iterations = replan_iterations;
16299 self.last_virtual_dep_telemetry = t;
16300 }
16301
16302 self.redirty_for_next_recalc();
16304 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
16305
16306 Ok(EvalResult {
16307 computed_vertices,
16308 cycle_errors,
16309 elapsed: start.elapsed(),
16310 })
16311 }
16312
16313 pub fn evaluate_until_cancellable(
16315 &mut self,
16316 targets: &[&str],
16317 cancel: crate::engine::CancelToken,
16318 ) -> Result<EvalResult, ExcelError> {
16319 self.observe_evaluation_resource_request(
16320 EvaluationRequestKind::TargetedCancellable,
16321 |engine| {
16322 engine.observe_function_semantic_epoch()?;
16323 engine.active_cancel_flag = Some(cancel.clone());
16324 let res = engine.evaluate_until_cancellable_impl(targets, cancel.as_flag());
16325 engine.active_cancel_flag = None;
16326 res
16327 },
16328 )
16329 }
16330
16331 fn evaluate_until_cancellable_impl(
16332 &mut self,
16333 targets: &[&str],
16334 cancel_flag: &AtomicBool,
16335 ) -> Result<EvalResult, ExcelError> {
16336 if cancel_flag.load(Ordering::Relaxed) {
16337 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16338 .with_message("Evaluation cancelled before target preparation"));
16339 }
16340 let mut typed_targets = Vec::with_capacity(targets.len());
16341 for target in targets {
16342 let (sheet, row, col) = self.parse_a1_notation(target)?;
16343 self.graph.sheet_id_mut(&sheet);
16344 typed_targets.push(crate::engine::EvaluationTarget::Cell { sheet, row, col });
16345 }
16346 self.evaluate_mixed_targets(&typed_targets, None)
16347 }
16348
16349 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
16350 let mut quoted = false;
16351 let mut separator = None;
16352 let bytes = address.as_bytes();
16353 let mut index = 0usize;
16354 while index < bytes.len() {
16355 match bytes[index] {
16356 b'\'' => {
16357 if quoted && bytes.get(index + 1) == Some(&b'\'') {
16358 index = index.saturating_add(1);
16359 } else {
16360 quoted = !quoted;
16361 }
16362 }
16363 b'!' if !quoted => separator = Some(index),
16364 _ => {}
16365 }
16366 index = index.saturating_add(1);
16367 }
16368 if quoted {
16369 return Err(ExcelError::new(ExcelErrorKind::Ref)
16370 .with_message(format!("Invalid quoted sheet reference `{address}`")));
16371 }
16372 let (sheet, cell_part) = match separator {
16373 Some(separator) => {
16374 let raw_sheet = &address[..separator];
16375 let sheet = if raw_sheet.starts_with('\'') && raw_sheet.ends_with('\'') {
16376 raw_sheet[1..raw_sheet.len().saturating_sub(1)].replace("''", "'")
16377 } else {
16378 raw_sheet.to_string()
16379 };
16380 (sheet, &address[separator + 1..])
16381 }
16382 None => (self.default_sheet_name().to_string(), address),
16383 };
16384
16385 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
16386 ExcelError::new(ExcelErrorKind::Ref)
16387 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
16388 })?;
16389
16390 Ok((sheet, row, col))
16391 }
16392
16393 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
16395 use formualizer_parse::parser::ASTNodeType;
16396 match &ast.node_type {
16397 ASTNodeType::Function { name, args, .. } => {
16398 if let Some(func) = self
16399 .get_function("", name)
16400 .or_else(|| crate::function_registry::get("", name))
16401 && func.caps().contains(crate::function::FnCaps::VOLATILE)
16402 {
16403 return true;
16404 }
16405 args.iter()
16406 .any(|arg| self.is_ast_volatile_with_provider(arg))
16407 }
16408 ASTNodeType::BinaryOp { left, right, .. } => {
16409 self.is_ast_volatile_with_provider(left)
16410 || self.is_ast_volatile_with_provider(right)
16411 }
16412 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
16413 ASTNodeType::Array(rows) => rows.iter().any(|row| {
16414 row.iter()
16415 .any(|cell| self.is_ast_volatile_with_provider(cell))
16416 }),
16417 _ => false,
16418 }
16419 }
16420
16421 fn evaluate_layer_sequential(
16423 &mut self,
16424 layer: &super::scheduler::Layer,
16425 ) -> Result<usize, ExcelError> {
16426 self.resource_checkpoint(layer.vertices.len() as u64)?;
16427 self.evaluate_layer_sequential_effects(layer)
16428 }
16429
16430 fn update_vertex_value_with_delta(
16431 &mut self,
16432 vertex_id: VertexId,
16433 new_value: LiteralValue,
16434 delta: &mut DeltaCollector,
16435 ) {
16436 if delta.mode != DeltaMode::Off
16437 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
16438 {
16439 let sheet_name = self.graph.sheet_name(cell.sheet_id);
16440 let old = self
16441 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
16442 .unwrap_or(LiteralValue::Empty);
16443 if old != new_value {
16444 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
16445 }
16446 }
16447 self.graph.update_vertex_value_ref(vertex_id, &new_value);
16448 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
16449 }
16450
16451 fn evaluate_layer_sequential_with_delta(
16452 &mut self,
16453 layer: &super::scheduler::Layer,
16454 delta: &mut DeltaCollector,
16455 ) -> Result<usize, ExcelError> {
16456 self.resource_checkpoint(layer.vertices.len() as u64)?;
16457 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
16458 }
16459
16460 fn evaluate_layer_sequential_cancellable(
16462 &mut self,
16463 layer: &super::scheduler::Layer,
16464 cancel_flag: &AtomicBool,
16465 ) -> Result<usize, ExcelError> {
16466 self.resource_checkpoint(layer.vertices.len() as u64)?;
16467 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
16468 }
16469
16470 fn evaluate_layer_sequential_cancellable_demand_driven(
16472 &mut self,
16473 layer: &super::scheduler::Layer,
16474 cancel_flag: &AtomicBool,
16475 ) -> Result<usize, ExcelError> {
16476 self.resource_checkpoint(layer.vertices.len() as u64)?;
16477 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
16478 }
16479
16480 fn evaluate_layer_parallel(
16492 &mut self,
16493 layer: &super::scheduler::Layer,
16494 ) -> Result<usize, ExcelError> {
16495 if layer.sequential {
16496 return self.evaluate_layer_sequential(layer);
16497 }
16498 self.resource_checkpoint(layer.vertices.len() as u64)?;
16499 let len = layer.vertices.len();
16500 let buffered = buffer_layer_writes(layer);
16501 let (probe, worth) = (PARALLEL_LAYER_PROBE, PARALLEL_LAYER_WORTH);
16502 let start = crate::instant::FzInstant::now();
16503 let mut pos = 0usize;
16504 let mut step = 1usize;
16505 while pos < len {
16506 if pos > 0 {
16507 let elapsed = start.elapsed();
16508 let per_vertex = elapsed.as_nanos() / pos as u128 + 1;
16510 let rest_estimate = per_vertex * (len - pos) as u128;
16511 if len - pos >= 2 && (elapsed >= probe || rest_estimate >= worth.as_nanos()) {
16512 let rest = layer.sub_layer(pos, len);
16513 let min_chunk = if per_vertex >= EXPENSIVE_VERTEX_NS {
16516 1
16517 } else {
16518 8
16519 };
16520 return Ok(pos + self.evaluate_layer_parallel_effects(&rest, min_chunk)?);
16521 }
16522 let fit = (probe.saturating_sub(elapsed).as_nanos() / per_vertex) as usize + 1;
16525 step = step.saturating_mul(2).min(fit);
16526 }
16527 let end = (pos + step).min(len);
16528 let slice = layer.sub_layer(pos, end);
16529 pos += self.evaluate_layer_units_until(
16532 &slice,
16533 None,
16534 None,
16535 None,
16536 buffered,
16537 Some(start + probe),
16538 )?;
16539 }
16540 Ok(len)
16541 }
16542
16543 fn evaluate_layer_parallel_with_delta(
16544 &mut self,
16545 layer: &super::scheduler::Layer,
16546 delta: &mut DeltaCollector,
16547 ) -> Result<usize, ExcelError> {
16548 if layer.sequential {
16549 return self.evaluate_layer_sequential_with_delta(layer, delta);
16550 }
16551 self.resource_checkpoint(layer.vertices.len() as u64)?;
16552 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
16553 }
16554
16555 fn evaluate_layer_parallel_cancellable(
16557 &mut self,
16558 layer: &super::scheduler::Layer,
16559 cancel_flag: &AtomicBool,
16560 ) -> Result<usize, ExcelError> {
16561 if layer.sequential {
16562 return self.evaluate_layer_sequential_cancellable(layer, cancel_flag);
16563 }
16564 self.resource_checkpoint(layer.vertices.len() as u64)?;
16565 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
16566 }
16567
16568 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
16570 if !self.graph.vertex_exists(vertex_id) {
16572 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
16573 .with_message(format!("Vertex not found: {vertex_id:?}")));
16574 }
16575
16576 let kind = self.graph.get_vertex_kind(vertex_id);
16578 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
16579
16580 let view = match kind {
16581 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
16582 if let Some(view) = self.graph.formula_view(vertex_id) {
16583 view
16584 } else {
16585 return Ok(LiteralValue::Number(0.0));
16586 }
16587 }
16588 VertexKind::Empty | VertexKind::Cell => {
16589 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
16590 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16591 let row = cell_ref.coord.row() + 1;
16592 let col = cell_ref.coord.col() + 1;
16593 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
16594 return Ok(v);
16595 }
16596 }
16597 return Ok(LiteralValue::Number(0.0));
16598 }
16599 VertexKind::NamedScalar => {
16600 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
16601 ExcelError::new(ExcelErrorKind::Name)
16602 .with_message("Named range metadata missing".to_string())
16603 })?;
16604
16605 return match &named_range.definition {
16606 NamedDefinition::Cell(cell_ref) => {
16607 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16608 Ok(self
16609 .get_cell_value(
16610 sheet_name,
16611 cell_ref.coord.row() + 1,
16612 cell_ref.coord.col() + 1,
16613 )
16614 .unwrap_or(LiteralValue::Empty))
16615 }
16616 NamedDefinition::Literal(v) => Ok(v.clone()),
16617 NamedDefinition::Formula { ast, .. } => {
16618 let context_sheet = match named_range.scope {
16619 NameScope::Sheet(id) => id,
16620 NameScope::Workbook => sheet_id,
16621 };
16622 let sheet_name = self.graph.sheet_name(context_sheet);
16623 let cell_ref = self
16624 .graph
16625 .get_cell_ref(vertex_id)
16626 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
16627 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16628 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
16629 }
16630 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
16631 .with_message("Range-valued name evaluated as scalar".to_string())),
16632 };
16633 }
16634 VertexKind::NamedArray => {
16635 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
16636 ExcelError::new(ExcelErrorKind::Name)
16637 .with_message("Named range metadata missing".to_string())
16638 })?;
16639
16640 return match &named_range.definition {
16641 NamedDefinition::Range(range_ref) => {
16642 if range_ref.start.sheet_id != range_ref.end.sheet_id {
16643 return Err(ExcelError::new(ExcelErrorKind::Ref)
16644 .with_message("Named range cannot span sheets".to_string()));
16645 }
16646 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
16647 let sr0 = range_ref.start.coord.row();
16648 let sc0 = range_ref.start.coord.col();
16649 let er0 = range_ref.end.coord.row();
16650 let ec0 = range_ref.end.coord.col();
16651 if sr0 > er0 || sc0 > ec0 {
16652 return Err(ExcelError::new(ExcelErrorKind::Ref)
16653 .with_message("Invalid named range bounds".to_string()));
16654 }
16655
16656 let h = (er0 - sr0 + 1) as usize;
16657 let w = (ec0 - sc0 + 1) as usize;
16658 let cell_count = (h as u64).saturating_mul(w as u64);
16659 if cell_count > self.config.spill.max_spill_cells as u64 {
16660 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
16661 "Named range too large to materialize as an array".to_string(),
16662 ));
16663 }
16664
16665 let sheet_id = range_ref.start.sheet_id;
16668 let asheet = self.arrow_sheets.sheet(sheet_name);
16669 let mut rows = Vec::with_capacity(h);
16670 for r0 in sr0..=er0 {
16671 let mut row = Vec::with_capacity(w);
16672 for c0 in sc0..=ec0 {
16673 row.push(
16674 self.read_cell_formatted_in(sheet_id, asheet, r0 + 1, c0 + 1)
16675 .0,
16676 );
16677 }
16678 rows.push(row);
16679 }
16680 Ok(LiteralValue::Array(rows))
16681 }
16682 NamedDefinition::Cell(cell_ref) => {
16683 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16684 let row = cell_ref.coord.row() + 1;
16685 let col = cell_ref.coord.col() + 1;
16686 let v = self
16687 .get_cell_value(sheet_name, row, col)
16688 .unwrap_or(LiteralValue::Empty);
16689 Ok(LiteralValue::Array(vec![vec![v]]))
16690 }
16691 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
16692 NamedDefinition::Formula { ast, .. } => {
16693 let context_sheet = match named_range.scope {
16694 NameScope::Sheet(id) => id,
16695 NameScope::Workbook => sheet_id,
16696 };
16697 let sheet_name = self.graph.sheet_name(context_sheet);
16698 let cell_ref = self
16699 .graph
16700 .get_cell_ref(vertex_id)
16701 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
16702 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16703 match interpreter.evaluate_ast(ast) {
16704 Ok(cv) => {
16705 let v = cv.into_literal();
16706 match v {
16707 LiteralValue::Array(_) => Ok(v),
16708 other => Ok(LiteralValue::Array(vec![vec![other]])),
16709 }
16710 }
16711 Err(err) => Ok(LiteralValue::Error(err)),
16712 }
16713 }
16714 };
16715 }
16716 VertexKind::InfiniteRange
16717 | VertexKind::Range
16718 | VertexKind::External
16719 | VertexKind::Table => {
16720 return Ok(LiteralValue::Number(0.0));
16722 }
16723 };
16724
16725 let sheet_name = self.graph.sheet_name(sheet_id);
16727 let cell_ref = self
16728 .graph
16729 .get_cell_ref(vertex_id)
16730 .expect("cell ref for vertex");
16731 if let Some(result) =
16732 self.freshness_evaluate_recorded(vertex_id, sheet_name, cell_ref, view)
16733 {
16734 return result;
16735 }
16736 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16737
16738 interpreter
16739 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
16740 .map(|cv| {
16741 let format = cv.format_id();
16742 self.record_derived_format(vertex_id, format);
16743 crate::engine::result_finalization::finalize_formula_result(cv.into_literal())
16744 })
16745 }
16746
16747 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
16749 self.thread_pool.as_ref()
16750 }
16751}
16752
16753#[derive(Default)]
16754struct RowBoundsCache {
16755 snapshot: u64,
16756 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
16758}
16759
16760impl RowBoundsCache {
16761 fn new(snapshot: u64) -> Self {
16762 Self {
16763 snapshot,
16764 map: Default::default(),
16765 }
16766 }
16767 fn get_row_bounds(
16768 &self,
16769 sheet_id: SheetId,
16770 col_idx: usize,
16771 snapshot: u64,
16772 ) -> Option<(Option<u32>, Option<u32>)> {
16773 if self.snapshot != snapshot {
16774 return None;
16775 }
16776 self.map.get(&(sheet_id as u32, col_idx)).copied()
16777 }
16778 fn put_row_bounds(
16779 &mut self,
16780 sheet_id: SheetId,
16781 col_idx: usize,
16782 snapshot: u64,
16783 bounds: (Option<u32>, Option<u32>),
16784 ) {
16785 if self.snapshot != snapshot {
16786 self.snapshot = snapshot;
16787 self.map.clear();
16788 }
16789 self.map.insert((sheet_id as u32, col_idx), bounds);
16790 }
16791}
16792
16793struct UsedAxisBoundsCache {
16794 snapshot: u64,
16795 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
16796 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
16797 #[cfg(test)]
16798 row_hits: std::sync::atomic::AtomicUsize,
16799 #[cfg(test)]
16800 row_misses: std::sync::atomic::AtomicUsize,
16801 #[cfg(test)]
16802 col_hits: std::sync::atomic::AtomicUsize,
16803 #[cfg(test)]
16804 col_misses: std::sync::atomic::AtomicUsize,
16805}
16806
16807impl UsedAxisBoundsCache {
16808 fn new(snapshot: u64) -> Self {
16809 Self {
16810 snapshot,
16811 row_bounds_by_col_span: Default::default(),
16812 col_bounds_by_row_span: Default::default(),
16813 #[cfg(test)]
16814 row_hits: std::sync::atomic::AtomicUsize::new(0),
16815 #[cfg(test)]
16816 row_misses: std::sync::atomic::AtomicUsize::new(0),
16817 #[cfg(test)]
16818 col_hits: std::sync::atomic::AtomicUsize::new(0),
16819 #[cfg(test)]
16820 col_misses: std::sync::atomic::AtomicUsize::new(0),
16821 }
16822 }
16823
16824 fn reset_for_snapshot(&mut self, snapshot: u64) {
16825 if self.snapshot != snapshot {
16826 self.snapshot = snapshot;
16827 self.row_bounds_by_col_span.clear();
16828 self.col_bounds_by_row_span.clear();
16829 }
16830 }
16831
16832 fn get_row_bounds(
16833 &self,
16834 sheet_id: SheetId,
16835 start_col: u32,
16836 end_col: u32,
16837 snapshot: u64,
16838 ) -> Option<Option<(u32, u32)>> {
16839 if self.snapshot != snapshot {
16840 return None;
16841 }
16842 let cached = self
16843 .row_bounds_by_col_span
16844 .get(&(sheet_id, start_col, end_col))
16845 .copied();
16846 #[cfg(test)]
16847 if cached.is_some() {
16848 self.row_hits.fetch_add(1, Ordering::Relaxed);
16849 }
16850 cached
16851 }
16852
16853 fn put_row_bounds(
16854 &mut self,
16855 sheet_id: SheetId,
16856 start_col: u32,
16857 end_col: u32,
16858 snapshot: u64,
16859 bounds: Option<(u32, u32)>,
16860 ) {
16861 self.reset_for_snapshot(snapshot);
16862 self.row_bounds_by_col_span
16863 .insert((sheet_id, start_col, end_col), bounds);
16864 #[cfg(test)]
16865 self.row_misses.fetch_add(1, Ordering::Relaxed);
16866 }
16867
16868 fn get_col_bounds(
16869 &self,
16870 sheet_id: SheetId,
16871 start_row: u32,
16872 end_row: u32,
16873 snapshot: u64,
16874 ) -> Option<Option<(u32, u32)>> {
16875 if self.snapshot != snapshot {
16876 return None;
16877 }
16878 let cached = self
16879 .col_bounds_by_row_span
16880 .get(&(sheet_id, start_row, end_row))
16881 .copied();
16882 #[cfg(test)]
16883 if cached.is_some() {
16884 self.col_hits.fetch_add(1, Ordering::Relaxed);
16885 }
16886 cached
16887 }
16888
16889 fn put_col_bounds(
16890 &mut self,
16891 sheet_id: SheetId,
16892 start_row: u32,
16893 end_row: u32,
16894 snapshot: u64,
16895 bounds: Option<(u32, u32)>,
16896 ) {
16897 self.reset_for_snapshot(snapshot);
16898 self.col_bounds_by_row_span
16899 .insert((sheet_id, start_row, end_row), bounds);
16900 #[cfg(test)]
16901 self.col_misses.fetch_add(1, Ordering::Relaxed);
16902 }
16903}
16904
16905#[derive(Default)]
16907pub struct ShimSpillManager {
16908 region_locks: RegionLockManager,
16909 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
16910}
16911
16912impl ShimSpillManager {
16913 pub(crate) fn reserve(
16914 &mut self,
16915 owner: VertexId,
16916 anchor_cell: CellRef,
16917 shape: SpillShape,
16918 _meta: SpillMeta,
16919 ) -> Result<(), ExcelError> {
16920 let region = crate::engine::spill::Region {
16922 sheet_id: anchor_cell.sheet_id as u32,
16923 row_start: anchor_cell.coord.row(),
16924 row_end: anchor_cell
16925 .coord
16926 .row()
16927 .saturating_add(shape.rows)
16928 .saturating_sub(1),
16929 col_start: anchor_cell.coord.col(),
16930 col_end: anchor_cell
16931 .coord
16932 .col()
16933 .saturating_add(shape.cols)
16934 .saturating_sub(1),
16935 };
16936 match self.region_locks.reserve(region, owner) {
16937 Ok(id) => {
16938 if id != 0 {
16939 self.active_locks.insert(owner, id);
16940 }
16941 Ok(())
16942 }
16943 Err(e) => Err(e),
16944 }
16945 }
16946
16947 pub(crate) fn release_owner(&mut self, owner: VertexId) {
16953 if let Some(id) = self.active_locks.remove(&owner) {
16954 self.region_locks.release(id);
16955 }
16956 }
16957
16958 pub(crate) fn commit_array_with_value_probe<F>(
16959 &mut self,
16960 graph: &mut DependencyGraph,
16961 anchor_vertex: VertexId,
16962 targets: &[CellRef],
16963 rows: Vec<Vec<LiteralValue>>,
16964 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
16965 mut value_probe: F,
16966 ) -> Result<(), ExcelError>
16967 where
16968 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
16969 {
16970 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
16971
16972 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
16976 anchor_vertex,
16977 targets,
16978 overwritable_formulas,
16979 );
16980 if let Err(e) = plan_res {
16981 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
16982 self.region_locks.release(id);
16983 }
16984 return Err(e);
16985 }
16986
16987 if !graph.value_cache_enabled() {
16988 let (expected_rows, expected_cols) = if targets.is_empty() {
16990 (0u32, 0u32)
16991 } else {
16992 let mut min_r = u32::MAX;
16993 let mut max_r = 0u32;
16994 let mut min_c = u32::MAX;
16995 let mut max_c = 0u32;
16996 for cell in targets {
16997 let r = cell.coord.row();
16998 let c = cell.coord.col();
16999 min_r = min_r.min(r);
17000 max_r = max_r.max(r);
17001 min_c = min_c.min(c);
17002 max_c = max_c.max(c);
17003 }
17004 (
17005 max_r.saturating_sub(min_r).saturating_add(1),
17006 max_c.saturating_sub(min_c).saturating_add(1),
17007 )
17008 };
17009
17010 let anchor_cell = graph
17011 .get_cell_ref(anchor_vertex)
17012 .expect("anchor cell ref for spill commit");
17013
17014 for cell in targets {
17015 if *cell == anchor_cell {
17017 continue;
17018 }
17019 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
17021 continue;
17022 }
17023 if let Some(vid) = graph.get_vertex_id_for_address(cell)
17025 && vid != anchor_vertex
17026 {
17027 match graph.get_vertex_kind(vid) {
17028 crate::engine::vertex::VertexKind::FormulaScalar
17029 | crate::engine::vertex::VertexKind::FormulaArray => {
17030 continue;
17032 }
17033 _ => {}
17034 }
17035 }
17036
17037 if let Some(v) = value_probe(graph, cell)
17038 && !matches!(v, LiteralValue::Empty)
17039 {
17040 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17041 self.region_locks.release(id);
17042 }
17043 return Err(ExcelError::new(ExcelErrorKind::Spill)
17044 .with_message("BlockedByValue")
17045 .with_extra(ExcelErrorExtra::Spill {
17046 expected_rows,
17047 expected_cols,
17048 }));
17049 }
17050 }
17051 }
17052
17053 let commit_res = graph.commit_spill_region_atomic_with_fault(
17054 anchor_vertex,
17055 targets.to_vec(),
17056 rows,
17057 None,
17058 );
17059 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17060 self.region_locks.release(id);
17061 }
17062 commit_res.map(|_| ())
17063 }
17064
17065 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
17067 &mut self,
17068 engine: &mut Engine<R>,
17069 anchor_vertex: VertexId,
17070 targets: &[CellRef],
17071 rows: Vec<Vec<LiteralValue>>,
17072 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
17073 ) -> Result<(), ExcelError> {
17074 if let Err(error) = engine.guard_pending_spill_commit(anchor_vertex, targets) {
17075 self.release_owner(anchor_vertex);
17076 return Err(error);
17077 }
17078 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
17080 anchor_vertex,
17081 targets,
17082 overwritable_formulas,
17083 );
17084 if let Err(e) = plan_res {
17085 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17086 self.region_locks.release(id);
17087 }
17088 return Err(e);
17089 }
17090
17091 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
17092 anchor_vertex,
17093 targets.to_vec(),
17094 rows.clone(),
17095 None,
17096 );
17097 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17098 self.region_locks.release(id);
17099 }
17100 commit_res.map(|_| ())?;
17101 engine
17102 .blocked_pending_spills
17103 .retain(|entry| entry.0 != anchor_vertex);
17104
17105 if engine.config.arrow_storage_enabled
17107 && engine.config.delta_overlay_enabled
17108 && engine.config.write_formula_overlay_enabled
17109 {
17110 for (idx, cell) in targets.iter().enumerate() {
17112 let (r_off, c_off) = {
17113 if rows.is_empty() || rows[0].is_empty() {
17114 (0usize, 0usize)
17115 } else {
17116 let width = rows[0].len();
17117 (idx / width, idx % width)
17118 }
17119 };
17120 let v = rows
17121 .get(r_off)
17122 .and_then(|r| r.get(c_off))
17123 .cloned()
17124 .unwrap_or(LiteralValue::Empty);
17125 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
17126 engine.mirror_value_to_computed_overlay(
17127 &sheet_name,
17128 cell.coord.row() + 1,
17129 cell.coord.col() + 1,
17130 &v,
17131 );
17132 }
17133 }
17134 Ok(())
17135 }
17136}
17137
17138impl<R> Engine<R>
17139where
17140 R: EvaluationContext,
17141{
17142 fn resolve_shared_ref(
17143 &self,
17144 reference: &ReferenceType,
17145 current_sheet: &str,
17146 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
17147 use formualizer_common::{
17148 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
17149 SheetRef as SharedRef,
17150 };
17151
17152 let sr = match reference {
17154 ReferenceType::Cell {
17155 sheet,
17156 row,
17157 col,
17158 row_abs,
17159 col_abs,
17160 } => {
17161 let row0 = row
17162 .checked_sub(1)
17163 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17164 let col0 = col
17165 .checked_sub(1)
17166 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17167 let sheet_loc = match sheet.as_deref() {
17168 Some(name) => SheetLocator::from_name(name),
17169 None => SheetLocator::Current,
17170 };
17171 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
17172 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
17173 }
17174 ReferenceType::Range {
17175 sheet,
17176 start_row,
17177 start_col,
17178 end_row,
17179 end_col,
17180 start_row_abs,
17181 start_col_abs,
17182 end_row_abs,
17183 end_col_abs,
17184 } => {
17185 let sheet_loc = match sheet.as_deref() {
17186 Some(name) => SheetLocator::from_name(name),
17187 None => SheetLocator::Current,
17188 };
17189 let sr = start_row
17190 .map(|r| {
17191 r.checked_sub(1)
17192 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17193 })
17194 .transpose()?;
17195 let sc = start_col
17196 .map(|c| {
17197 c.checked_sub(1)
17198 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17199 })
17200 .transpose()?;
17201 let er = end_row
17202 .map(|r| {
17203 r.checked_sub(1)
17204 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17205 })
17206 .transpose()?;
17207 let ec = end_col
17208 .map(|c| {
17209 c.checked_sub(1)
17210 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17211 })
17212 .transpose()?;
17213 let range = SharedRangeRef::from_parts(
17214 sheet_loc,
17215 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
17216 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
17217 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
17218 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
17219 )
17220 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
17221 SharedRef::Range(range)
17222 }
17223 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
17224 };
17225
17226 let current_id = self
17227 .graph
17228 .sheet_id(current_sheet)
17229 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17230
17231 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
17232 match loc {
17233 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
17234 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
17235 SheetLocator::Name(name) => {
17236 let n = name.as_ref();
17237 self.graph
17238 .sheet_id(n)
17239 .map(SheetLocator::Id)
17240 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17241 }
17242 }
17243 };
17244
17245 match sr {
17246 SharedRef::Cell(cell) => {
17247 let owned = cell.into_owned();
17248 let sheet = resolve_loc(owned.sheet)?;
17249 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
17250 }
17251 SharedRef::Range(range) => {
17252 let owned = range.into_owned();
17253 let sheet = resolve_loc(owned.sheet)?;
17254 Ok(SharedRef::Range(SharedRangeRef {
17255 sheet,
17256 start_row: owned.start_row,
17257 start_col: owned.start_col,
17258 end_row: owned.end_row,
17259 end_col: owned.end_col,
17260 }))
17261 }
17262 }
17263 }
17264}
17265
17266impl<R> crate::traits::ReferenceResolver for Engine<R>
17269where
17270 R: EvaluationContext,
17271{
17272 fn resolve_cell_reference(
17273 &self,
17274 sheet: Option<&str>,
17275 row: u32,
17276 col: u32,
17277 ) -> Result<LiteralValue, ExcelError> {
17278 let Some(sheet_name) = sheet else {
17289 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
17290 "Unqualified cell reference resolved without sheet context".to_string(),
17291 ));
17292 };
17293 if let Some(v) = self.get_cell_value(sheet_name, row, col) {
17296 Ok(v)
17297 } else {
17298 Ok(LiteralValue::Number(0.0))
17300 }
17301 }
17302}
17303
17304impl<R> crate::traits::RangeResolver for Engine<R>
17305where
17306 R: EvaluationContext,
17307{
17308 fn resolve_range_reference(
17309 &self,
17310 sheet: Option<&str>,
17311 sr: Option<u32>,
17312 sc: Option<u32>,
17313 er: Option<u32>,
17314 ec: Option<u32>,
17315 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
17316 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
17319 }
17320}
17321
17322impl<R> crate::traits::NamedRangeResolver for Engine<R>
17323where
17324 R: EvaluationContext,
17325{
17326 fn resolve_named_range_reference(
17327 &self,
17328 name: &str,
17329 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
17330 self.resolver.resolve_named_range_reference(name)
17331 }
17332}
17333
17334impl<R> crate::traits::TableResolver for Engine<R>
17335where
17336 R: EvaluationContext,
17337{
17338 fn resolve_table_reference(
17339 &self,
17340 tref: &formualizer_parse::parser::TableReference,
17341 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17342 self.resolver.resolve_table_reference(tref)
17343 }
17344}
17345
17346impl<R> crate::traits::SourceResolver for Engine<R>
17347where
17348 R: EvaluationContext,
17349{
17350 fn source_scalar_version(&self, name: &str) -> Option<u64> {
17351 self.resolver.source_scalar_version(name)
17352 }
17353
17354 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
17355 self.resolver.resolve_source_scalar(name)
17356 }
17357
17358 fn source_table_version(&self, name: &str) -> Option<u64> {
17359 self.resolver.source_table_version(name)
17360 }
17361
17362 fn resolve_source_table(
17363 &self,
17364 name: &str,
17365 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17366 self.resolver.resolve_source_table(name)
17367 }
17368}
17369
17370impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
17372
17373impl<R> crate::traits::FunctionProvider for Engine<R>
17375where
17376 R: EvaluationContext,
17377{
17378 fn planning_semantic_revision(&self) -> Option<u64> {
17379 self.resolver.planning_semantic_revision()
17380 }
17381
17382 fn get_function(
17383 &self,
17384 prefix: &str,
17385 name: &str,
17386 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17387 self.resolver.get_function(prefix, name)
17388 }
17389
17390 fn get_function_for_planning(
17391 &self,
17392 prefix: &str,
17393 name: &str,
17394 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17395 self.resolver.get_function_for_planning(prefix, name)
17396 }
17397}
17398
17399impl<R> Engine<R>
17400where
17401 R: EvaluationContext,
17402{
17403 pub(crate) fn semantic_used_rows_for_columns(
17411 &self,
17412 sheet: &str,
17413 start_col: u32,
17414 end_col: u32,
17415 ) -> Option<(u32, u32)> {
17416 let arrow_bounds = self
17417 .sheet_store()
17418 .sheet(sheet)
17419 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
17420 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
17421 Self::union_used_bounds(arrow_bounds, formula_bounds)
17422 }
17423
17424 pub(crate) fn semantic_used_cols_for_rows(
17425 &self,
17426 sheet: &str,
17427 start_row: u32,
17428 end_row: u32,
17429 ) -> Option<(u32, u32)> {
17430 let arrow_bounds = self
17431 .sheet_store()
17432 .sheet(sheet)
17433 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
17434 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
17435 Self::union_used_bounds(arrow_bounds, formula_bounds)
17436 }
17437}
17438
17439impl<R> crate::traits::EvaluationContext for Engine<R>
17441where
17442 R: EvaluationContext,
17443{
17444 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
17445 &self.clock
17446 }
17447
17448 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
17449 self.thread_pool.as_ref()
17450 }
17451
17452 fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
17453 self.active_cancel_flag.clone()
17454 }
17455
17456 fn chunk_hint(&self) -> Option<usize> {
17457 let hint =
17459 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
17460 Some(hint.clamp(1024, 1 << 20)) }
17462
17463 fn volatile_level(&self) -> crate::traits::VolatileLevel {
17464 self.config.volatile_level
17465 }
17466
17467 fn workbook_seed(&self) -> u64 {
17468 self.config.workbook_seed
17469 }
17470
17471 fn recalc_epoch(&self) -> u64 {
17472 self.recalc_epoch
17473 }
17474
17475 fn workbook_sheet_count(&self) -> Option<usize> {
17476 Some(self.graph.sheet_reg().active_len())
17477 }
17478
17479 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
17480 self.graph.sheet_reg().active_position(sheet)
17481 }
17482
17483 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
17484 self.sheet_index_by_name(current_sheet)
17485 }
17486
17487 fn inspect_reference(
17488 &self,
17489 reference: &ReferenceType,
17490 current_sheet: &str,
17491 ) -> Result<Option<ReferenceInfo>, ExcelError> {
17492 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
17493 let sheet_id = self
17494 .graph
17495 .sheet_id(sheet_name)
17496 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17497 let sheet_index = self
17498 .graph
17499 .sheet_reg()
17500 .active_position_by_id(sheet_id)
17501 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17502 Ok((sheet_id, sheet_index))
17503 };
17504
17505 let cell_info =
17506 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
17507 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
17508 let row0 = row
17509 .checked_sub(1)
17510 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17511 let col0 = col
17512 .checked_sub(1)
17513 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17514 Ok(ReferenceInfo {
17515 first_sheet_index: Some(sheet_index),
17516 sheet_count: Some(1),
17517 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
17518 })
17519 };
17520
17521 let range_info = |sheet_name: &str,
17522 start_row: Option<u32>,
17523 start_col: Option<u32>|
17524 -> Result<ReferenceInfo, ExcelError> {
17525 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
17526 let row = start_row.unwrap_or(1);
17527 let col = start_col.unwrap_or(1);
17528 let row0 = row
17529 .checked_sub(1)
17530 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17531 let col0 = col
17532 .checked_sub(1)
17533 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17534 Ok(ReferenceInfo {
17535 first_sheet_index: Some(sheet_index),
17536 sheet_count: Some(1),
17537 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
17538 })
17539 };
17540
17541 let info = match reference {
17542 ReferenceType::Cell {
17543 sheet, row, col, ..
17544 } => {
17545 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
17546 cell_info(sheet_name, *row, *col)?
17547 }
17548 ReferenceType::Range {
17549 sheet,
17550 start_row,
17551 start_col,
17552 ..
17553 } => {
17554 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
17555 range_info(sheet_name, *start_row, *start_col)?
17556 }
17557 ReferenceType::Cell3D {
17558 sheet_first,
17559 sheet_last,
17560 row,
17561 col,
17562 ..
17563 } => {
17564 let first = cell_info(sheet_first, *row, *col)?;
17565 ReferenceInfo {
17566 first_sheet_index: first.first_sheet_index,
17567 sheet_count: self
17568 .graph
17569 .sheet_reg()
17570 .active_span_len(sheet_first, sheet_last),
17571 first_cell: first.first_cell,
17572 }
17573 }
17574 ReferenceType::Range3D {
17575 sheet_first,
17576 sheet_last,
17577 start_row,
17578 start_col,
17579 ..
17580 } => {
17581 let first = range_info(sheet_first, *start_row, *start_col)?;
17582 ReferenceInfo {
17583 first_sheet_index: first.first_sheet_index,
17584 sheet_count: self
17585 .graph
17586 .sheet_reg()
17587 .active_span_len(sheet_first, sheet_last),
17588 first_cell: first.first_cell,
17589 }
17590 }
17591 ReferenceType::NamedRange(name) => {
17592 let current_id = self
17593 .graph
17594 .sheet_id(current_sheet)
17595 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17596 let named = self
17597 .graph
17598 .resolve_name_entry(name, current_id)
17599 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17600 match &named.definition {
17601 NamedDefinition::Cell(cell) => ReferenceInfo {
17602 first_sheet_index: self
17603 .graph
17604 .sheet_reg()
17605 .active_position_by_id(cell.sheet_id),
17606 sheet_count: Some(1),
17607 first_cell: Some(*cell),
17608 },
17609 NamedDefinition::Range(range) => ReferenceInfo {
17610 first_sheet_index: self
17611 .graph
17612 .sheet_reg()
17613 .active_position_by_id(range.start.sheet_id),
17614 sheet_count: Some(1),
17615 first_cell: Some(range.start),
17616 },
17617 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
17618 ReferenceInfo {
17619 first_sheet_index: None,
17620 sheet_count: None,
17621 first_cell: None,
17622 }
17623 }
17624 }
17625 }
17626 ReferenceType::Table(tref) => {
17627 let table = self
17628 .graph
17629 .resolve_table_entry(&tref.name)
17630 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17631 ReferenceInfo {
17632 first_sheet_index: self
17633 .graph
17634 .sheet_reg()
17635 .active_position_by_id(table.range.start.sheet_id),
17636 sheet_count: Some(1),
17637 first_cell: Some(table.range.start),
17638 }
17639 }
17640 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
17641 };
17642
17643 Ok(Some(info))
17644 }
17645
17646 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
17647 let sheet_name = self.graph.sheet_name(cell.sheet_id);
17648 if sheet_name.is_empty() {
17649 return Err(ExcelError::new(ExcelErrorKind::Ref));
17650 }
17651 let row = cell.coord.row() + 1;
17652 let col = cell.coord.col() + 1;
17653
17654 if let Some(entries) = self.staged_formulas.get(sheet_name)
17655 && let Some(text) = entries.get(row, col)
17656 {
17657 return Ok(Some(if text.starts_with('=') {
17658 text.to_owned()
17659 } else {
17660 format!("={text}")
17661 }));
17662 }
17663
17664 let Some((Some(ast), _)) = self.get_cell(sheet_name, row, col) else {
17665 return Ok(None);
17666 };
17667 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
17668 }
17669
17670 fn used_rows_for_columns(
17671 &self,
17672 sheet: &str,
17673 start_col: u32,
17674 end_col: u32,
17675 ) -> Option<(u32, u32)> {
17676 let sheet_id = self.graph.sheet_id(sheet)?;
17678 let snap = self.data_snapshot_id();
17679 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
17680 guard
17681 .as_ref()
17682 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
17683 }) {
17684 return cached;
17685 }
17686
17687 let arrow_bounds = self
17688 .sheet_store()
17689 .sheet(sheet)
17690 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
17691 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
17692 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
17693 Some(bounds)
17694 } else {
17695 let sc0 = start_col.saturating_sub(1);
17696 let ec0 = end_col.saturating_sub(1);
17697 self.graph
17698 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
17699 .map(|(a0, b0)| (a0 + 1, b0 + 1))
17700 };
17701
17702 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
17703 guard
17704 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
17705 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
17706 }
17707
17708 computed
17709 }
17710
17711 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
17712 let sheet_id = self.graph.sheet_id(sheet)?;
17714 let snap = self.data_snapshot_id();
17715 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
17716 guard
17717 .as_ref()
17718 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
17719 }) {
17720 return cached;
17721 }
17722
17723 let arrow_bounds = self
17724 .sheet_store()
17725 .sheet(sheet)
17726 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
17727 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
17728 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
17729 Some(bounds)
17730 } else {
17731 let sr0 = start_row.saturating_sub(1);
17732 let er0 = end_row.saturating_sub(1);
17733 self.graph
17734 .used_col_bounds_for_rows(sheet_id, sr0, er0)
17735 .map(|(a0, b0)| (a0 + 1, b0 + 1))
17736 };
17737
17738 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
17739 guard
17740 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
17741 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
17742 }
17743
17744 computed
17745 }
17746
17747 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
17748 let _ = self.graph.sheet_id(sheet)?;
17749 Some((1_048_576, 16_384)) }
17753
17754 fn data_snapshot_id(&self) -> u64 {
17755 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
17756 }
17757
17758 fn backend_caps(&self) -> crate::traits::BackendCaps {
17759 crate::traits::BackendCaps {
17760 streaming: true,
17761 used_region: true,
17762 write: false,
17763 tables: false,
17764 async_stream: false,
17765 }
17766 }
17767
17768 fn build_lookup_index(
17769 &self,
17770 view: &RangeView<'_>,
17771 axis: LookupAxis,
17772 ) -> Option<Arc<LookupIndex>> {
17773 self.build_lookup_index_impl(view, axis)
17774 }
17775
17776 fn date_system(&self) -> crate::engine::DateSystem {
17779 self.config.date_system
17780 }
17781 fn resolve_range_view<'c>(
17783 &'c self,
17784 reference: &ReferenceType,
17785 current_sheet: &str,
17786 ) -> Result<RangeView<'c>, ExcelError> {
17787 match reference {
17788 ReferenceType::External(ext) => {
17789 let name = ext.raw.as_str();
17790 match ext.kind {
17791 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
17792 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
17793 return Err(ExcelError::new(ExcelErrorKind::Name)
17794 .with_message(format!("Undefined name: {name}")));
17795 };
17796 let version = source
17797 .version
17798 .or_else(|| self.resolver.source_scalar_version(name));
17799 let v = self.resolve_source_scalar_cached(name, version)?;
17800 Ok(RangeView::from_owned_rows(
17801 vec![vec![v]],
17802 self.config.date_system,
17803 ))
17804 }
17805 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
17806 let Some(source) = self.graph.resolve_source_table_entry(name) else {
17807 let kind =
17811 if crate::engine::refs::unbound_external_range_defers(&ext.kind) {
17812 ExcelErrorKind::Ref
17813 } else {
17814 ExcelErrorKind::Name
17815 };
17816 return Err(ExcelError::new(kind)
17817 .with_message(format!("Undefined table: {name}")));
17818 };
17819 let version = source
17820 .version
17821 .or_else(|| self.resolver.source_table_version(name));
17822 let table = self.resolve_source_table_cached(name, version)?;
17823 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
17824 self.source_table_to_range_view(table.as_ref(), &spec)
17825 }
17826 }
17827 }
17828 ReferenceType::Range { .. } => {
17829 let shared = self.resolve_shared_ref(reference, current_sheet)?;
17830 let formualizer_common::SheetRef::Range(range) = shared else {
17831 return Err(ExcelError::new(ExcelErrorKind::Ref));
17832 };
17833 let sheet_id = match range.sheet {
17836 formualizer_common::SheetLocator::Id(id) => id,
17837 formualizer_common::SheetLocator::Current
17838 | formualizer_common::SheetLocator::Name(_) => {
17839 return Err(ExcelError::new(ExcelErrorKind::Ref));
17840 }
17841 };
17842 let sheet_name = self.graph.sheet_name(sheet_id);
17843
17844 let bounded_range = if range.start_row.is_some()
17845 && range.start_col.is_some()
17846 && range.end_row.is_some()
17847 && range.end_col.is_some()
17848 {
17849 Some(RangeRef::try_from_shared(range.as_ref())?)
17850 } else {
17851 None
17852 };
17853
17854 let sr = bounded_range
17855 .as_ref()
17856 .map(|r| r.start.coord.row() + 1)
17857 .or_else(|| range.start_row.map(|b| b.index + 1));
17858 let sc = bounded_range
17859 .as_ref()
17860 .map(|r| r.start.coord.col() + 1)
17861 .or_else(|| range.start_col.map(|b| b.index + 1));
17862 let er = bounded_range
17863 .as_ref()
17864 .map(|r| r.end.coord.row() + 1)
17865 .or_else(|| range.end_row.map(|b| b.index + 1));
17866 let ec = bounded_range
17867 .as_ref()
17868 .map(|r| r.end.coord.col() + 1)
17869 .or_else(|| range.end_col.map(|b| b.index + 1));
17870
17871 let extent = resolve_used_extent_with_fallback(
17872 OpenRangeBounds {
17873 start_row: sr,
17874 start_column: sc,
17875 end_row: er,
17876 end_column: ec,
17877 },
17878 ExtentPolicy::EvaluationCompat {
17879 fallback_row: None,
17880 fallback_column: None,
17881 },
17882 || {
17883 self.sheet_bounds(sheet_name)
17884 .map(|_| self.config.max_open_ended_rows)
17885 },
17886 || {
17887 self.sheet_bounds(sheet_name)
17888 .map(|_| self.config.max_open_ended_cols)
17889 },
17890 |first, last| self.used_rows_for_columns(sheet_name, first, last),
17891 |first, last| self.used_cols_for_rows(sheet_name, first, last),
17892 );
17893 let (sr, sc, er, ec) = extent
17894 .map(|extent| {
17895 (
17896 extent.start_row,
17897 extent.start_column,
17898 extent.end_row,
17899 extent.end_column,
17900 )
17901 })
17902 .unwrap_or((1, 1, 0, 0));
17903
17904 if self.force_materialize_range_views {
17905 if er < sr || ec < sc {
17906 return Ok(RangeView::from_owned_rows(
17907 Vec::new(),
17908 self.config.date_system,
17909 ));
17910 }
17911 let h = (er - sr + 1) as u64;
17912 let w = (ec - sc + 1) as u64;
17913 let cell_count = h.saturating_mul(w);
17914 if cell_count <= self.config.spill.max_spill_cells as u64 {
17915 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
17916 for r in sr..=er {
17917 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
17918 for c in sc..=ec {
17919 rowv.push(
17920 self.get_cell_value(sheet_name, r, c)
17921 .unwrap_or(LiteralValue::Empty),
17922 );
17923 }
17924 rows.push(rowv);
17925 }
17926 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
17927 }
17928 }
17929
17930 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
17931 return Ok(RangeView::from_owned_rows(
17932 Vec::new(),
17933 self.config.date_system,
17934 ));
17935 };
17936
17937 let rv = if er < sr || ec < sc {
17938 asheet.range_view(1, 1, 0, 0)
17939 } else {
17940 let sr0 = sr.saturating_sub(1) as usize;
17941 let sc0 = sc.saturating_sub(1) as usize;
17942 let er0 = er.saturating_sub(1) as usize;
17943 let ec0 = ec.saturating_sub(1) as usize;
17944 asheet.range_view(sr0, sc0, er0, ec0)
17945 };
17946
17947 Ok(rv)
17948 }
17949 ReferenceType::Cell { .. } => {
17950 let shared = self.resolve_shared_ref(reference, current_sheet)?;
17951 let formualizer_common::SheetRef::Cell(cell) = shared else {
17952 return Err(ExcelError::new(ExcelErrorKind::Ref));
17953 };
17954 let addr = CellRef::try_from_shared(cell)?;
17955 let sheet_id = addr.sheet_id;
17956 let sheet_name = self.graph.sheet_name(sheet_id);
17957 let row = addr.coord.row() + 1;
17958 let col = addr.coord.col() + 1;
17959
17960 if self.force_materialize_range_views {
17961 let v = self
17962 .get_cell_value(sheet_name, row, col)
17963 .unwrap_or(LiteralValue::Empty);
17964 return Ok(RangeView::from_owned_rows(
17965 vec![vec![v]],
17966 self.config.date_system,
17967 ));
17968 }
17969
17970 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
17971 let r0 = row.saturating_sub(1) as usize;
17972 let c0 = col.saturating_sub(1) as usize;
17973 let rv = asheet.range_view(r0, c0, r0, c0);
17974 Ok(rv)
17975 } else {
17976 let v = self
17977 .get_cell_value(sheet_name, row, col)
17978 .unwrap_or(LiteralValue::Empty);
17979 Ok(RangeView::from_owned_rows(
17980 vec![vec![v]],
17981 self.config.date_system,
17982 ))
17983 }
17984 }
17985 ReferenceType::NamedRange(name) => {
17986 if let Some(current_id) = self.graph.sheet_id(current_sheet)
17987 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
17988 {
17989 match &named.definition {
17990 NamedDefinition::Cell(cell_ref) => {
17991 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
17992 if self.force_materialize_range_views {
17993 let v = self
17994 .get_cell_value(
17995 sheet_name,
17996 cell_ref.coord.row() + 1,
17997 cell_ref.coord.col() + 1,
17998 )
17999 .unwrap_or(LiteralValue::Empty);
18000 return Ok(RangeView::from_owned_rows(
18001 vec![vec![v]],
18002 self.config.date_system,
18003 ));
18004 } else {
18005 let asheet = self
18006 .sheet_store()
18007 .sheet(sheet_name)
18008 .expect("Arrow sheet missing for named cell");
18009 let r0 = cell_ref.coord.row() as usize;
18010 let c0 = cell_ref.coord.col() as usize;
18011 let rv = asheet.range_view(r0, c0, r0, c0);
18012 return Ok(rv);
18013 }
18014 }
18015 NamedDefinition::Range(range_ref) => {
18016 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
18017 let sr = range_ref.start.coord.row() + 1;
18018 let sc = range_ref.start.coord.col() + 1;
18019 let er = range_ref.end.coord.row() + 1;
18020 let ec = range_ref.end.coord.col() + 1;
18021 if self.force_materialize_range_views {
18022 let h = (er.saturating_sub(sr) + 1) as u64;
18023 let w = (ec.saturating_sub(sc) + 1) as u64;
18024 let cell_count = h.saturating_mul(w);
18025 if cell_count <= self.config.spill.max_spill_cells as u64 {
18026 let mut rows: Vec<Vec<LiteralValue>> =
18027 Vec::with_capacity(h as usize);
18028 for r in sr..=er {
18029 let mut rowv: Vec<LiteralValue> =
18030 Vec::with_capacity(w as usize);
18031 for c in sc..=ec {
18032 rowv.push(
18033 self.get_cell_value(sheet_name, r, c)
18034 .unwrap_or(LiteralValue::Empty),
18035 );
18036 }
18037 rows.push(rowv);
18038 }
18039 return Ok(RangeView::from_owned_rows(
18040 rows,
18041 self.config.date_system,
18042 ));
18043 }
18044 }
18045 let asheet = self
18046 .sheet_store()
18047 .sheet(sheet_name)
18048 .expect("Arrow sheet missing for named range");
18049 let sr0 = range_ref.start.coord.row() as usize;
18050 let sc0 = range_ref.start.coord.col() as usize;
18051 let er0 = range_ref.end.coord.row() as usize;
18052 let ec0 = range_ref.end.coord.col() as usize;
18053 let rv = asheet.range_view(sr0, sc0, er0, ec0);
18054 return Ok(rv);
18055 }
18056 NamedDefinition::Literal(v) => {
18057 return Ok(RangeView::from_owned_rows(
18058 vec![vec![v.clone()]],
18059 self.config.date_system,
18060 ));
18061 }
18062 NamedDefinition::Formula { .. } => {
18063 if let Some(value) = self.graph.get_value(named.vertex) {
18064 return Ok(RangeView::from_owned_rows(
18065 vec![vec![value]],
18066 self.config.date_system,
18067 ));
18068 }
18069 }
18070 }
18071 }
18072
18073 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
18074 let version = source
18075 .version
18076 .or_else(|| self.resolver.source_scalar_version(name));
18077 let v = self.resolve_source_scalar_cached(name, version)?;
18078 return Ok(RangeView::from_owned_rows(
18079 vec![vec![v]],
18080 self.config.date_system,
18081 ));
18082 }
18083
18084 let data = self.resolver.resolve_named_range_reference(name)?;
18085 Ok(RangeView::from_owned_rows(data, self.config.date_system))
18086 }
18087 ReferenceType::Table(tref) => {
18088 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
18089 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
18090 let asheet = self
18091 .sheet_store()
18092 .sheet(sheet_name)
18093 .expect("Arrow sheet missing for table reference");
18094
18095 let sr0 = table.range.start.coord.row() as usize;
18096 let sc0 = table.range.start.coord.col() as usize;
18097 let er0 = table.range.end.coord.row() as usize;
18098 let ec0 = table.range.end.coord.col() as usize;
18099
18100 let has_totals = table.totals_row;
18101 let has_headers = table.header_row;
18102 let data_sr = if has_headers {
18103 sr0.saturating_add(1)
18104 } else {
18105 sr0
18106 };
18107 let data_er = if has_totals {
18108 er0.saturating_sub(1)
18109 } else {
18110 er0
18111 };
18112
18113 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
18114 if sr > er || sc > ec {
18115 asheet.range_view(1, 1, 0, 0)
18116 } else {
18117 asheet.range_view(sr, sc, er, ec)
18118 }
18119 };
18120
18121 let av = match &tref.specifier {
18122 None => {
18123 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18124 "Table reference without specifier is unsupported".to_string(),
18125 ));
18126 }
18127 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
18128 let Some(idx) = table.col_index(col) else {
18129 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18130 "Column refers to unknown table column".to_string(),
18131 ));
18132 };
18133 let c0 = sc0 + idx;
18134 select(data_sr, c0, data_er, c0)
18135 }
18136 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
18137 start,
18138 end,
18139 )) => {
18140 let Some(si) = table.col_index(start) else {
18141 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18142 "Column range refers to unknown column(s)".to_string(),
18143 ));
18144 };
18145 let Some(ei) = table.col_index(end) else {
18146 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18147 "Column range refers to unknown column(s)".to_string(),
18148 ));
18149 };
18150 let (mut a, mut b) = (si, ei);
18151 if a > b {
18152 std::mem::swap(&mut a, &mut b);
18153 }
18154 let c_start = sc0 + a;
18155 let c_end = sc0 + b;
18156 select(data_sr, c_start, data_er, c_end)
18157 }
18158 Some(formualizer_parse::parser::TableSpecifier::All)
18159 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18160 formualizer_parse::parser::SpecialItem::All,
18161 )) => select(sr0, sc0, er0, ec0),
18162 Some(formualizer_parse::parser::TableSpecifier::Data)
18163 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18164 formualizer_parse::parser::SpecialItem::Data,
18165 )) => select(data_sr, sc0, data_er, ec0),
18166 Some(formualizer_parse::parser::TableSpecifier::Headers)
18167 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18168 formualizer_parse::parser::SpecialItem::Headers,
18169 )) => {
18170 if !has_headers {
18171 asheet.range_view(1, 1, 0, 0)
18172 } else {
18173 select(sr0, sc0, sr0, ec0)
18174 }
18175 }
18176 Some(formualizer_parse::parser::TableSpecifier::Totals)
18177 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18178 formualizer_parse::parser::SpecialItem::Totals,
18179 )) => {
18180 if !has_totals {
18181 asheet.range_view(1, 1, 0, 0)
18182 } else {
18183 select(er0, sc0, er0, ec0)
18184 }
18185 }
18186 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18187 formualizer_parse::parser::SpecialItem::ThisRow,
18188 )) => {
18189 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18190 "@ (This Row) requires table-aware context; not yet supported"
18191 .to_string(),
18192 ));
18193 }
18194 Some(formualizer_parse::parser::TableSpecifier::Row(_))
18195 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
18196 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18197 "Complex structured references not yet supported".to_string(),
18198 ));
18199 }
18200 };
18201
18202 return Ok(av);
18203 }
18204
18205 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
18206 let version = source
18207 .version
18208 .or_else(|| self.resolver.source_table_version(&tref.name));
18209 let table = self.resolve_source_table_cached(&tref.name, version)?;
18210 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
18211 }
18212
18213 let boxed = self
18218 .resolve_range_like(&ReferenceType::Table(tref.clone()))
18219 .map_err(|e| {
18220 if e.kind == ExcelErrorKind::NImpl {
18221 ExcelError::new(ExcelErrorKind::Name)
18222 .with_message(format!("Unknown table: {}", tref.name))
18223 } else {
18224 e
18225 }
18226 })?;
18227 let owned = boxed.materialise().into_owned();
18228 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
18229 }
18230 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
18231 Err(ExcelError::new(ExcelErrorKind::NImpl)
18232 .with_message("3D references are not yet supported".to_string()))
18233 }
18234 }
18235 }
18236
18237 fn resolve_cell_format(
18238 &self,
18239 sheet: Option<&str>,
18240 row: u32,
18241 col: u32,
18242 current_sheet: &str,
18243 ) -> Option<crate::format::FormatId> {
18244 self.effective_format_id(sheet.unwrap_or(current_sheet), row, col)
18245 }
18246
18247 fn format_class(
18248 &self,
18249 format: crate::format::FormatId,
18250 ) -> Option<formualizer_common::numfmt::FormatClass> {
18251 self.format_registry.class(format).cloned()
18252 }
18253
18254 fn record_cell_derived_format(
18255 &self,
18256 sheet: &str,
18257 row: u32,
18258 col: u32,
18259 format: Option<crate::format::FormatId>,
18260 ) {
18261 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
18262 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
18263 self.record_derived_format_at(cell, format);
18264 }
18265 }
18266
18267 fn resolve_cell_reference_value(
18268 &self,
18269 sheet: Option<&str>,
18270 row: u32,
18271 col: u32,
18272 current_sheet: &str,
18273 ) -> Result<LiteralValue, ExcelError> {
18274 let sheet_name = sheet.unwrap_or(current_sheet);
18275 if self.graph.sheet_id(sheet_name).is_none() {
18276 return Err(ExcelError::new(ExcelErrorKind::Ref));
18277 }
18278 Ok(self
18279 .get_cell_value(sheet_name, row, col)
18280 .unwrap_or(LiteralValue::Empty))
18281 }
18282
18283 fn resolve_cell_reference_value_formatted(
18284 &self,
18285 sheet: Option<&str>,
18286 row: u32,
18287 col: u32,
18288 current_sheet: &str,
18289 ) -> Result<(LiteralValue, Option<crate::format::FormatId>), ExcelError> {
18290 let sheet_name = sheet.unwrap_or(current_sheet);
18293 let Some(sheet_id) = self.graph.sheet_id(sheet_name) else {
18294 return Err(ExcelError::new(ExcelErrorKind::Ref));
18295 };
18296 let asheet = self.arrow_sheets.sheet(sheet_name);
18297 Ok(self.read_cell_formatted_in(sheet_id, asheet, row, col))
18298 }
18299
18300 fn build_criteria_mask(
18301 &self,
18302 view: &RangeView<'_>,
18303 col_in_view: usize,
18304 pred: &crate::args::CriteriaPredicate,
18305 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18306 #[cfg(any(test, feature = "test-support"))]
18307 criteria_mask_test_hooks::note_mask(view.dims().0);
18308 if view.dims().1 == 0 {
18309 return None;
18310 }
18311 let sheet_rows = view.sheet().nrows as usize;
18314 if sheet_rows == 0 || view.start_row() >= sheet_rows {
18315 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
18316 }
18317 compute_criteria_mask(view, col_in_view, pred)
18318 }
18319
18320 fn build_row_visibility_mask(
18321 &self,
18322 view: &RangeView<'_>,
18323 mode: VisibilityMaskMode,
18324 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18325 self.build_row_visibility_mask_for_view(view, mode)
18326 }
18327}
18328
18329impl<R> Engine<R>
18330where
18331 R: EvaluationContext,
18332{
18333 fn clear_spill_projection_and_mirror(
18334 &mut self,
18335 anchor_vertex: VertexId,
18336 delta: Option<&mut DeltaCollector>,
18337 ) {
18338 let spill_cells = self
18339 .graph
18340 .spill_cells_for_anchor(anchor_vertex)
18341 .map(|cells| cells.to_vec())
18342 .unwrap_or_default();
18343 if spill_cells.is_empty() {
18344 return;
18345 }
18346
18347 if let Some(delta) = delta
18348 && delta.mode != DeltaMode::Off
18349 {
18350 let empty = LiteralValue::Empty;
18351 for cell in spill_cells.iter() {
18352 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18353 let old = self
18354 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18355 .unwrap_or(LiteralValue::Empty);
18356 if old != empty {
18357 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
18358 }
18359 }
18360 }
18361
18362 self.graph.clear_spill_region(anchor_vertex);
18363 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
18364 self.record_structural_change(scope);
18365 }
18366
18367 if self.config.arrow_storage_enabled
18368 && self.config.delta_overlay_enabled
18369 && self.config.write_formula_overlay_enabled
18370 {
18371 let empty = LiteralValue::Empty;
18372 for cell in spill_cells.iter() {
18373 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
18374 self.mirror_value_to_computed_overlay(
18375 &sheet_name,
18376 cell.coord.row() + 1,
18377 cell.coord.col() + 1,
18378 &empty,
18379 );
18380 }
18381 }
18382 }
18383
18384 fn apply_cycle_outcome(
18398 &mut self,
18399 cycle: &[VertexId],
18400 mut delta: Option<&mut DeltaCollector>,
18401 dirty_filter: Option<&FxHashSet<VertexId>>,
18402 ) -> usize {
18403 let circ_error = LiteralValue::Error(
18404 ExcelError::new(ExcelErrorKind::Circ)
18405 .with_message("Circular dependency detected".to_string()),
18406 );
18407 let mut stamped = 0usize;
18408 for &vertex_id in cycle {
18409 if let Some(filter) = dirty_filter
18410 && !filter.contains(&vertex_id)
18411 {
18412 continue;
18413 }
18414 self.stamp_cycle_error(vertex_id, &circ_error, delta.as_deref_mut());
18415 stamped += 1;
18416 }
18417 stamped
18418 }
18419
18420 fn stamp_cycle_error(
18435 &mut self,
18436 vertex_id: VertexId,
18437 circ_error: &LiteralValue,
18438 mut delta: Option<&mut DeltaCollector>,
18439 ) {
18440 if self.graph.spill_registry_has_anchor(vertex_id) {
18442 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
18443 }
18444 self.spill_mgr.release_owner(vertex_id);
18447
18448 if let Some(d) = delta
18450 && d.mode != DeltaMode::Off
18451 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
18452 {
18453 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18454 let old = self
18455 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18456 .unwrap_or(LiteralValue::Empty);
18457 if old != *circ_error {
18458 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
18459 }
18460 }
18461
18462 self.graph.update_vertex_value_ref(vertex_id, circ_error);
18463 self.mirror_vertex_value_to_overlay(vertex_id, circ_error);
18464 }
18465
18466 fn handle_cycle_unit(
18479 &mut self,
18480 cycle: &[VertexId],
18481 mut delta: Option<&mut DeltaCollector>,
18482 dirty_filter: Option<&FxHashSet<VertexId>>,
18483 cancel_flag: Option<&AtomicBool>,
18484 ) -> Result<usize, ExcelError> {
18485 self.resource_checkpoint(cycle.len() as u64)?;
18486 match self.config.cycle.detection {
18487 CycleDetection::Static => {
18488 Ok(self.apply_cycle_outcome(cycle, delta.as_deref_mut(), dirty_filter))
18489 }
18490 CycleDetection::Runtime => {
18491 if let Some(filter) = dirty_filter
18492 && !cycle.iter().any(|v| filter.contains(v))
18493 {
18494 return Ok(0);
18495 }
18496 self.evaluate_scc_unit(cycle, delta, cancel_flag)
18500 }
18501 }
18502 }
18503
18504 pub(crate) fn evaluate_scc_unit(
18524 &mut self,
18525 cycle: &[VertexId],
18526 mut delta: Option<&mut DeltaCollector>,
18527 cancel_flag: Option<&AtomicBool>,
18528 ) -> Result<usize, ExcelError> {
18529 struct SccMember {
18530 vertex: VertexId,
18531 cell: Option<CellRef>,
18532 }
18533
18534 let task_start = crate::instant::FzInstant::now();
18535
18536 let mut cell_members: Vec<(VertexId, CellRef)> = Vec::new();
18541 let mut name_members: Vec<(VertexId, String)> = Vec::new();
18542 let mut other_members: Vec<VertexId> = Vec::new();
18543 for &v in cycle {
18544 match self.graph.get_vertex_kind(v) {
18545 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
18546 match self.graph.get_cell_ref(v) {
18547 Some(cell) => cell_members.push((v, cell)),
18548 None => other_members.push(v),
18549 }
18550 }
18551 VertexKind::NamedScalar | VertexKind::NamedArray => {
18552 match self.graph.name_key_for_vertex(v) {
18553 Some(key) => name_members.push((v, key)),
18554 None => other_members.push(v),
18555 }
18556 }
18557 _ => other_members.push(v),
18558 }
18559 }
18560 cell_members.sort_unstable_by_key(|(_, c)| (c.sheet_id, c.coord.row(), c.coord.col()));
18561 name_members.sort_unstable_by(|(av, ak), (bv, bk)| ak.cmp(bk).then(av.cmp(bv)));
18562 other_members.sort_unstable();
18563
18564 let cell_refs: Vec<CellRef> = cell_members.iter().map(|(_, c)| *c).collect();
18565 let name_keys: Vec<String> = name_members.iter().map(|(_, k)| k.clone()).collect();
18566 let mut members: Vec<SccMember> = Vec::with_capacity(cycle.len());
18567 for (v, c) in &cell_members {
18568 members.push(SccMember {
18569 vertex: *v,
18570 cell: Some(*c),
18571 });
18572 }
18573 for (v, _) in &name_members {
18574 members.push(SccMember {
18575 vertex: *v,
18576 cell: None,
18577 });
18578 }
18579 for v in &other_members {
18580 members.push(SccMember {
18581 vertex: *v,
18582 cell: None,
18583 });
18584 }
18585 let n = members.len();
18586 let recordable = cell_refs.len() + name_keys.len();
18589
18590 let circ_error = LiteralValue::Error(
18591 ExcelError::new(ExcelErrorKind::Circ)
18592 .with_message("Circular dependency detected".to_string()),
18593 );
18594
18595 if !self.iterative_state_values.is_empty() {
18606 let restore: Vec<(VertexId, LiteralValue)> = members
18607 .iter()
18608 .filter_map(|m| {
18609 let cell = m.cell?;
18610 let persisted = self.iterative_state_values.get(&m.vertex)?;
18611 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18612 let overlay = self
18613 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18614 .unwrap_or(LiteralValue::Empty);
18615 if matches!(overlay, LiteralValue::Empty) {
18616 Some((m.vertex, persisted.clone()))
18617 } else {
18618 None
18619 }
18620 })
18621 .collect();
18622 for (vertex, value) in restore {
18623 self.mirror_vertex_value_to_overlay(vertex, &value);
18624 }
18625 }
18626
18627 let snapshot: Vec<LiteralValue> = members
18630 .iter()
18631 .map(|m| match m.cell {
18632 Some(cell) => {
18633 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18634 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18635 .unwrap_or(LiteralValue::Empty)
18636 }
18637 None => self
18638 .graph
18639 .get_value(m.vertex)
18640 .unwrap_or(LiteralValue::Empty),
18641 })
18642 .collect();
18643
18644 let mut excluded = vec![false; n];
18649 let mut last_value = snapshot.clone();
18650 let mut stamped = 0usize;
18651 for (i, m) in members.iter().enumerate() {
18652 match self.graph.get_vertex_kind(m.vertex) {
18653 VertexKind::FormulaArray => {
18654 self.stamp_cycle_error(m.vertex, &circ_error, delta.as_deref_mut());
18658 excluded[i] = true;
18659 last_value[i] = circ_error.clone();
18660 stamped += 1;
18661 }
18662 VertexKind::FormulaScalar | VertexKind::NamedScalar | VertexKind::NamedArray => {}
18663 _ => excluded[i] = true,
18664 }
18665 }
18666
18667 let collector = LiveEdgeCollector::new_with_names(&cell_refs, &name_keys);
18668
18669 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
18671 let mut pos: Vec<i64> = vec![-1; n];
18673 let mut changed = vec![false; n];
18675
18676 macro_rules! run_member {
18679 ($i:expr) => {{
18680 let i: usize = $i;
18681 let m = &members[i];
18682 if i < recordable {
18683 collector.set_current(i as u32);
18684 }
18685 let value = {
18686 let ctx = RecordingContext::new(&*self, &collector);
18687 match self.evaluate_vertex_recorded(m.vertex, &ctx, &collector) {
18688 Ok(v) => v,
18689 Err(e) => LiteralValue::Error(e),
18690 }
18691 };
18692 let is_cell_formula = m.cell.is_some();
18693 if is_cell_formula && matches!(value, LiteralValue::Array(_)) {
18694 self.stamp_cycle_error(m.vertex, &circ_error, None);
18699 excluded[i] = true;
18700 stamped += 1;
18701 changed[i] = last_value[i] != circ_error;
18702 last_value[i] = circ_error.clone();
18703 } else {
18704 self.graph.update_vertex_value_ref(m.vertex, &value);
18705 self.mirror_vertex_value_to_overlay(m.vertex, &value);
18706 #[cfg(debug_assertions)]
18710 if let Some(cell) = m.cell {
18711 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
18712 debug_assert!(
18713 self.read_delta_overlay_cell(
18714 &sheet_name,
18715 cell.coord.row() + 1,
18716 cell.coord.col() + 1
18717 )
18718 .is_none(),
18719 "user overlay must never shadow a formula SCC member ({sheet_name}!r{}c{})",
18720 cell.coord.row() + 1,
18721 cell.coord.col() + 1
18722 );
18723 }
18724 changed[i] = last_value[i] != value;
18725 last_value[i] = value;
18726 }
18727 }};
18728 }
18729
18730 let check_cancel = |flag: Option<&AtomicBool>| -> Result<(), ExcelError> {
18731 if let Some(flag) = flag
18732 && flag.load(Ordering::Relaxed)
18733 {
18734 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
18735 .with_message("Evaluation cancelled during SCC evaluation".to_string()));
18736 }
18737 Ok(())
18738 };
18739
18740 check_cancel(cancel_flag)?;
18742 let mut passes = 1usize;
18743 {
18744 let mut p = 0i64;
18745 for i in 0..n {
18746 if excluded[i] {
18747 continue;
18748 }
18749 run_member!(i);
18750 pos[i] = p;
18751 p += 1;
18752 }
18753 }
18754
18755 let policy = self.config.cycle.policy;
18771 let cap = n + 2;
18772 let mut witnessed_cycles = 0usize;
18773 let mut capped = false;
18774 let mut iterating = false;
18776 let mut converged = false;
18777 let mut exact_fixed_point = false;
18778 let mut prev_pass: Option<Vec<LiteralValue>> = None;
18782 let mut iter_max_delta = 0f64;
18785 let mut iter_nan_converged = 0usize;
18786 let mut settle_passes = 0usize;
18790 loop {
18791 let drained = collector.take_edges();
18794 for i in 0..n {
18795 if pos[i] >= 0 {
18796 out_edges[i].clear();
18797 }
18798 }
18799 for (from, to) in drained {
18800 debug_assert!(
18801 pos[from as usize] >= 0,
18802 "edge from a member that did not run"
18803 );
18804 out_edges[from as usize].push(to);
18805 }
18806 let mut edges: Vec<(u32, u32)> = Vec::new();
18807 for (i, outs) in out_edges.iter().enumerate() {
18808 if excluded[i] {
18809 continue;
18810 }
18811 for &t in outs {
18812 edges.push((i as u32, t));
18813 }
18814 }
18815 edges.sort_unstable();
18816 edges.dedup();
18817
18818 let analysis = analyze_live_graph(n, &edges);
18819
18820 if analysis.cycle_count > 0 {
18821 witnessed_cycles = witnessed_cycles.max(analysis.cycle_count);
18825 match policy {
18826 CyclePolicy::Error => {
18827 for i in 0..n {
18833 if analysis.in_cycle[i] && !excluded[i] {
18834 self.stamp_cycle_error(members[i].vertex, &circ_error, None);
18835 excluded[i] = true;
18836 last_value[i] = circ_error.clone();
18837 stamped += 1;
18838 }
18839 }
18840 check_cancel(cancel_flag)?;
18841 let order: Vec<usize> = analysis
18842 .topo
18843 .iter()
18844 .map(|&i| i as usize)
18845 .filter(|&i| !excluded[i])
18846 .collect();
18847 if !order.is_empty() {
18848 passes += 1;
18849 for i in order {
18850 run_member!(i);
18851 }
18852 }
18853 break;
18854 }
18855 CyclePolicy::Iterate {
18856 max_iterations,
18857 max_change,
18858 } => {
18859 iterating = true;
18861
18862 if let Some(prev) = &prev_pass {
18868 let mut round_max_delta = 0f64;
18869 let mut round_nan = 0usize;
18870 let mut all_converged = true;
18871 let mut round_exact = true;
18872 for i in 0..n {
18873 if excluded[i] {
18874 continue;
18878 }
18879 let out = crate::engine::convergence::values_converged(
18880 &prev[i],
18881 &last_value[i],
18882 max_change,
18883 self.config.date_system,
18884 );
18885 if out.nan_converged {
18886 round_nan += 1;
18887 }
18888 if let Some(d) = out.abs_delta {
18889 round_max_delta = round_max_delta.max(d);
18890 if d != 0.0 {
18891 round_exact = false;
18892 }
18893 }
18894 if !out.converged {
18895 all_converged = false;
18896 }
18897 }
18898 iter_max_delta = round_max_delta;
18901 iter_nan_converged = round_nan;
18902 if all_converged {
18903 exact_fixed_point = round_exact;
18904 converged = true;
18905 break;
18906 }
18907 }
18908
18909 if passes >= max_iterations as usize {
18921 capped = true;
18922 break;
18923 }
18924
18925 check_cancel(cancel_flag)?;
18926 prev_pass = Some(last_value.clone());
18934 for x in pos.iter_mut() {
18935 *x = -1;
18936 }
18937 changed.fill(false);
18938 passes += 1;
18939 let mut p = 0i64;
18940 for i in 0..n {
18941 if excluded[i] {
18942 continue;
18943 }
18944 run_member!(i);
18945 pos[i] = p;
18946 p += 1;
18947 }
18948 continue;
18949 }
18950 }
18951 }
18952
18953 let mut stale: Vec<usize> = Vec::new();
18956 for i in 0..n {
18957 if excluded[i] {
18958 continue;
18959 }
18960 let is_stale = out_edges[i].iter().any(|&t| {
18961 let t = t as usize;
18962 changed[t] && (pos[i] < 0 || (pos[t] >= 0 && pos[i] < pos[t]))
18963 });
18964 if is_stale {
18965 stale.push(i);
18966 }
18967 }
18968 if stale.is_empty() {
18969 break; }
18971 if 1 + settle_passes >= cap {
18972 capped = true;
18974 for (i, m) in members.iter().enumerate() {
18975 if !excluded[i] {
18976 self.stamp_cycle_error(m.vertex, &circ_error, None);
18977 excluded[i] = true;
18978 last_value[i] = circ_error.clone();
18979 stamped += 1;
18980 }
18981 }
18982 break;
18983 }
18984
18985 check_cancel(cancel_flag)?;
18986 prev_pass = None;
18993 let topo_pos = analysis.topo_positions();
18994 stale.sort_unstable_by_key(|&i| topo_pos[i]);
18995 for x in pos.iter_mut() {
18996 *x = -1;
18997 }
18998 changed.fill(false);
18999 passes += 1;
19000 settle_passes += 1;
19001 for (p, i) in stale.into_iter().enumerate() {
19002 run_member!(i);
19003 pos[i] = p as i64;
19004 }
19005 }
19006
19007 if iterating && !converged && !capped {
19012 converged = true;
19013 }
19014
19015 collector.clear_current();
19018 if let Some(d) = delta
19019 && d.mode != DeltaMode::Off
19020 {
19021 for (i, m) in members.iter().enumerate() {
19022 if let Some(cell) = m.cell
19023 && last_value[i] != snapshot[i]
19024 {
19025 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19026 }
19027 }
19028 }
19029
19030 if !self.retained_scc_members.is_empty() {
19048 for m in members.iter() {
19049 self.retained_scc_members.remove(&m.vertex);
19050 }
19051 }
19052 if iterating {
19053 let retain = converged
19054 && !capped
19055 && exact_fixed_point
19056 && iter_nan_converged == 0
19057 && members
19058 .iter()
19059 .all(|m| !self.graph.is_volatile(m.vertex) && !self.graph.is_dynamic(m.vertex));
19060 if retain {
19061 if self.retained_scc_members.is_empty() {
19062 self.retained_scc_config_fingerprint = self.retained_scc_config_fingerprint();
19063 self.retained_scc_function_epoch_seen =
19064 crate::function_registry::semantic_epoch();
19065 self.retained_scc_provider_revision_seen =
19066 self.resolver.planning_semantic_revision();
19067 }
19068 let scc_id = self.next_retained_scc_id;
19069 self.next_retained_scc_id = self.next_retained_scc_id.wrapping_add(1);
19070 for (i, m) in members.iter().enumerate() {
19071 self.retained_scc_members.insert(m.vertex, scc_id);
19072 if matches!(last_value[i], LiteralValue::Empty) {
19075 self.iterative_state_values.remove(&m.vertex);
19076 } else {
19077 self.iterative_state_values
19078 .insert(m.vertex, last_value[i].clone());
19079 }
19080 }
19081 } else {
19082 self.pending_iterative_redirty
19083 .extend(members.iter().map(|m| m.vertex));
19084 }
19085 } else if !self.iterative_state_values.is_empty() {
19086 for m in members.iter() {
19090 self.iterative_state_values.remove(&m.vertex);
19091 }
19092 }
19093
19094 {
19095 let t = &mut self.last_cycle_telemetry;
19096 t.static_sccs += 1;
19097 if witnessed_cycles == 0 && stamped == 0 && !capped {
19098 t.phantom_sccs += 1;
19099 }
19100 t.live_cycles_witnessed += witnessed_cycles;
19101 t.circ_cells_stamped += stamped;
19102 t.settle_passes_total += passes;
19103 t.max_passes_single_scc = t.max_passes_single_scc.max(passes);
19104 if iterating {
19105 t.iterated_sccs += 1;
19106 if converged {
19107 t.converged_sccs += 1;
19108 }
19109 t.max_abs_delta_at_stop = t.max_abs_delta_at_stop.max(iter_max_delta);
19110 t.nan_converged += iter_nan_converged;
19111 }
19112 if capped {
19113 t.capped_sccs += 1;
19114 }
19115 t.elapsed_ms += task_start.elapsed().as_millis();
19116 }
19117
19118 Ok(stamped)
19119 }
19120
19121 fn evaluate_vertex_recorded(
19129 &self,
19130 vertex_id: VertexId,
19131 ctx: &RecordingContext<'_, R>,
19132 collector: &LiveEdgeCollector,
19133 ) -> Result<LiteralValue, ExcelError> {
19134 if !self.graph.vertex_exists(vertex_id) {
19135 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
19136 .with_message(format!("Vertex not found: {vertex_id:?}")));
19137 }
19138
19139 let kind = self.graph.get_vertex_kind(vertex_id);
19140 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
19141
19142 match kind {
19143 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19144 let Some(view) = self.graph.formula_view(vertex_id) else {
19145 return Ok(LiteralValue::Number(0.0)); };
19147 let sheet_name = self.graph.sheet_name(sheet_id);
19148 let cell_ref = self
19149 .graph
19150 .get_cell_ref(vertex_id)
19151 .expect("cell ref for vertex");
19152 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19153 interpreter
19154 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
19155 .map(|cv| {
19156 let format = cv.format_id();
19157 self.record_derived_format(vertex_id, format);
19158 crate::engine::result_finalization::finalize_formula_result(
19159 cv.into_literal(),
19160 )
19161 })
19162 }
19163 VertexKind::NamedScalar | VertexKind::NamedArray => {
19164 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
19165 ExcelError::new(ExcelErrorKind::Name)
19166 .with_message("Named range metadata missing".to_string())
19167 })?;
19168
19169 match &named_range.definition {
19170 NamedDefinition::Formula { ast, .. } => {
19171 let context_sheet = match named_range.scope {
19172 NameScope::Sheet(id) => id,
19173 NameScope::Workbook => sheet_id,
19174 };
19175 let sheet_name = self.graph.sheet_name(context_sheet);
19176 let cell_ref = self
19177 .graph
19178 .get_cell_ref(vertex_id)
19179 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
19180 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19181 if kind == VertexKind::NamedScalar {
19182 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
19183 } else {
19184 match interpreter.evaluate_ast(ast) {
19185 Ok(cv) => match cv.into_literal() {
19186 v @ LiteralValue::Array(_) => Ok(v),
19187 other => Ok(LiteralValue::Array(vec![vec![other]])),
19188 },
19189 Err(err) => Ok(LiteralValue::Error(err)),
19190 }
19191 }
19192 }
19193 NamedDefinition::Cell(cell_ref) => {
19194 collector.record_scalar(
19198 cell_ref.sheet_id,
19199 cell_ref.coord.row(),
19200 cell_ref.coord.col(),
19201 );
19202 self.evaluate_vertex_immutable(vertex_id)
19203 }
19204 NamedDefinition::Range(range_ref) => {
19205 if range_ref.start.sheet_id == range_ref.end.sheet_id {
19206 collector.record_rect(
19207 range_ref.start.sheet_id,
19208 range_ref.start.coord.row(),
19209 range_ref.start.coord.col(),
19210 range_ref.end.coord.row(),
19211 range_ref.end.coord.col(),
19212 );
19213 }
19214 self.evaluate_vertex_immutable(vertex_id)
19215 }
19216 NamedDefinition::Literal(_) => self.evaluate_vertex_immutable(vertex_id),
19217 }
19218 }
19219 _ => self.evaluate_vertex_immutable(vertex_id),
19220 }
19221 }
19222
19223 fn pending_spill_occupied(&self, anchor: CellRef, end_row: u32, end_col: u32) -> bool {
19225 let sheet = self.graph.sheet_name(anchor.sheet_id);
19226 let package = self
19227 .staged_formulas
19228 .get(sheet)
19229 .and_then(|staged| staged.deferred_package.as_ref());
19230 self.staged_formula_index.occupies_spill(
19231 sheet,
19232 (anchor.coord.row() + 1, anchor.coord.col() + 1),
19233 (end_row + 1, end_col + 1),
19234 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
19235 )
19236 }
19237
19238 fn remember_pending_spill(
19239 &mut self,
19240 vertex: VertexId,
19241 anchor: CellRef,
19242 region: Region,
19243 ) -> Result<(), ExcelError> {
19244 self.cancellation_checkpoint("pending spill occupancy")?;
19245 self.resource_checkpoint(1)?;
19246 if let Some(entry) = self
19247 .blocked_pending_spills
19248 .iter_mut()
19249 .find(|entry| entry.0 == vertex)
19250 {
19251 *entry = (vertex, anchor, region);
19252 return Ok(());
19253 }
19254 if self.blocked_pending_spills.len() == self.blocked_pending_spills.capacity() {
19255 let additional = self.blocked_pending_spills.capacity().max(1);
19258 let bytes =
19259 (additional as u64)
19260 .saturating_mul(std::mem::size_of::<(VertexId, CellRef, Region)>() as u64);
19261 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19262 ledger
19263 .reserve_retained(bytes)
19264 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19265 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
19266 }
19267 if self
19268 .blocked_pending_spills
19269 .try_reserve_exact(additional)
19270 .is_err()
19271 {
19272 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19273 ledger
19274 .release_retained(bytes)
19275 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19276 self.source_cache_accounted -= bytes;
19277 }
19278 return Err(crate::engine::ResourceLedgerError::Exhausted(
19279 formualizer_common::ResourceExhaustionDetail {
19280 reason: formualizer_common::ResourceExhaustionReason::RetainedMemory,
19281 limit: u64::MAX,
19282 observed: bytes,
19283 request_id: None,
19284 },
19285 )
19286 .into_excel_error());
19287 }
19288 }
19289 self.blocked_pending_spills.push((vertex, anchor, region));
19290 Ok(())
19291 }
19292
19293 fn invalidate_pending_spills(&mut self, scope: StructuralScope) {
19297 if let StructuralScope::RemovedSheet(sheet) = scope {
19298 self.blocked_pending_spills
19299 .retain(|entry| entry.1.sheet_id != sheet);
19300 return;
19301 }
19302 for &(vertex, anchor, region) in &self.blocked_pending_spills {
19303 let affected = match scope {
19304 StructuralScope::Cell { sheet, row, col } => {
19305 region.intersects(&Region::point(sheet, row, col))
19306 }
19307 StructuralScope::Region(changed) => region.intersects(&changed),
19308 StructuralScope::Sheet(sheet) | StructuralScope::RemovedSheet(sheet) => {
19309 region.sheet_id() == sheet
19310 }
19311 StructuralScope::OpaqueGlobal | StructuralScope::AllSheets => true,
19312 };
19313 if affected
19314 && self.graph.vertex_exists(vertex)
19315 && self.graph.get_cell_ref(vertex) == Some(anchor)
19316 && matches!(
19317 self.graph.get_vertex_kind(vertex),
19318 VertexKind::FormulaScalar | VertexKind::FormulaArray
19319 )
19320 {
19321 self.graph.mark_vertex_dirty(vertex);
19322 }
19323 }
19324 }
19325
19326 fn guard_pending_spill_commit(
19327 &mut self,
19328 anchor_vertex: VertexId,
19329 targets: &[CellRef],
19330 ) -> Result<(), ExcelError> {
19331 let Some(anchor) = self.graph.get_cell_ref(anchor_vertex) else {
19332 return Ok(());
19333 };
19334 let Some(last) = targets.last() else {
19335 return Ok(());
19336 };
19337 let occupied = self.pending_spill_occupied(anchor, last.coord.row(), last.coord.col());
19338 if (occupied
19339 || self
19340 .blocked_pending_spills
19341 .iter()
19342 .any(|entry| entry.0 == anchor_vertex))
19343 && let Err(error) = self.remember_pending_spill(
19344 anchor_vertex,
19345 anchor,
19346 Region::rect(
19347 anchor.sheet_id,
19348 anchor.coord.row(),
19349 last.coord.row(),
19350 anchor.coord.col(),
19351 last.coord.col(),
19352 ),
19353 )
19354 {
19355 self.spill_mgr.release_owner(anchor_vertex);
19356 return Err(error);
19357 }
19358 if occupied {
19359 self.spill_mgr.release_owner(anchor_vertex);
19360 return Err(ExcelError::new(ExcelErrorKind::Spill)
19361 .with_message("Spill blocked")
19362 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
19363 expected_rows: last.coord.row() - anchor.coord.row() + 1,
19364 expected_cols: last.coord.col() - anchor.coord.col() + 1,
19365 }));
19366 }
19367 Ok(())
19368 }
19369
19370 fn commit_spill_and_mirror(
19372 &mut self,
19373 anchor_vertex: VertexId,
19374 targets: &[CellRef],
19375 rows: Vec<Vec<LiteralValue>>,
19376 delta: Option<&mut DeltaCollector>,
19377 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19378 ) -> Result<(), ExcelError> {
19379 self.guard_pending_spill_commit(anchor_vertex, targets)?;
19380 let prev_spill_cells = self
19381 .graph
19382 .spill_cells_for_anchor(anchor_vertex)
19383 .map(|cells| cells.to_vec())
19384 .unwrap_or_default();
19385
19386 if let Some(delta) = delta
19387 && delta.mode != DeltaMode::Off
19388 {
19389 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
19390 targets.iter().copied().collect();
19391 let empty = LiteralValue::Empty;
19392
19393 for cell in prev_spill_cells.iter() {
19395 if target_set.contains(cell) {
19396 continue;
19397 }
19398 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19399 let old = self
19400 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19401 .unwrap_or(LiteralValue::Empty);
19402 if old != empty {
19403 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19404 }
19405 }
19406
19407 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
19409 let width = rows[0].len();
19410 for (idx, cell) in targets.iter().enumerate() {
19411 let r_off = idx / width;
19412 let c_off = idx % width;
19413 let new = rows
19414 .get(r_off)
19415 .and_then(|r| r.get(c_off))
19416 .cloned()
19417 .unwrap_or(LiteralValue::Empty);
19418 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19419 let old = self
19420 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19421 .unwrap_or(LiteralValue::Empty);
19422 if old != new {
19423 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19424 }
19425 }
19426 } else {
19427 for cell in targets.iter() {
19429 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19430 let old = self
19431 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19432 .unwrap_or(LiteralValue::Empty);
19433 if !matches!(old, LiteralValue::Empty) {
19434 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19435 }
19436 }
19437 }
19438 }
19439
19440 let arrow_sheets = &self.arrow_sheets;
19443 self.spill_mgr.commit_array_with_value_probe(
19444 &mut self.graph,
19445 anchor_vertex,
19446 targets,
19447 rows.clone(),
19448 overwritable_formulas,
19449 |g, cell| {
19450 let sheet_name = g.sheet_name(cell.sheet_id);
19451 let asheet = arrow_sheets.sheet(sheet_name)?;
19452 let r0 = cell.coord.row() as usize;
19453 let c0 = cell.coord.col() as usize;
19454 let v = asheet.get_cell_value(r0, c0);
19455 if matches!(v, LiteralValue::Empty) {
19456 None
19457 } else {
19458 Some(v)
19459 }
19460 },
19461 )?;
19462
19463 self.blocked_pending_spills
19464 .retain(|entry| entry.0 != anchor_vertex);
19465 if let Some(scope) = Self::structural_scope_from_cells(&prev_spill_cells) {
19466 self.record_structural_change(scope);
19467 }
19468 if let Some(scope) = Self::structural_scope_from_cells(targets) {
19469 self.record_structural_change(scope);
19470 }
19471
19472 if self.config.arrow_storage_enabled
19473 && self.config.delta_overlay_enabled
19474 && self.config.write_formula_overlay_enabled
19475 {
19476 if !prev_spill_cells.is_empty() {
19477 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
19478 targets.iter().copied().collect();
19479 let empty = LiteralValue::Empty;
19480 for cell in prev_spill_cells.iter() {
19481 if !target_set.contains(cell) {
19482 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19483 self.mirror_value_to_computed_overlay(
19484 &sheet_name,
19485 cell.coord.row() + 1,
19486 cell.coord.col() + 1,
19487 &empty,
19488 );
19489 }
19490 }
19491 }
19492
19493 for (idx, cell) in targets.iter().enumerate() {
19494 if rows.is_empty() || rows[0].is_empty() {
19495 break;
19496 }
19497 let width = rows[0].len();
19498 let r_off = idx / width;
19499 let c_off = idx % width;
19500 let v = rows[r_off][c_off].clone();
19501 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19502 self.mirror_value_to_computed_overlay(
19503 &sheet_name,
19504 cell.coord.row() + 1,
19505 cell.coord.col() + 1,
19506 &v,
19507 );
19508 }
19509 }
19510 Ok(())
19511 }
19512}
19513
19514#[cfg(test)]
19515#[path = "tests/authority_schedule_execution.rs"]
19516mod authority_schedule_execution;
19517
19518#[cfg(test)]
19519#[path = "tests/pending_spill.rs"]
19520mod pending_spill_tests;
19521
19522use crate::engine::effects::Effect;
19527use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
19528
19529impl<R> Engine<R>
19530where
19531 R: EvaluationContext,
19532{
19533 pub(crate) fn plan_vertex_effects(
19540 &mut self,
19541 vertex_id: VertexId,
19542 computed_value: LiteralValue,
19543 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19544 ) -> Result<Vec<Effect>, ExcelError> {
19545 if self.freshness_armed() {
19548 if self.freshness_drop_stale(vertex_id) {
19549 return Ok(Vec::new());
19550 }
19551 let effects = self.plan_vertex_effects_unrecorded(
19552 vertex_id,
19553 computed_value,
19554 overwritable_formulas,
19555 )?;
19556 self.freshness_mark_committed(vertex_id);
19557 return Ok(effects);
19558 }
19559 self.plan_vertex_effects_unrecorded(vertex_id, computed_value, overwritable_formulas)
19560 }
19561
19562 fn plan_vertex_effects_unrecorded(
19563 &mut self,
19564 vertex_id: VertexId,
19565 computed_value: LiteralValue,
19566 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19567 ) -> Result<Vec<Effect>, ExcelError> {
19568 let kind = self.graph.get_vertex_kind(vertex_id);
19569 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
19570
19571 if !is_formula {
19575 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
19576 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
19577 && owner != vertex_id
19578 {
19579 return Ok(Vec::new());
19580 }
19581 return Ok(vec![Effect::WriteCell {
19583 vertex_id,
19584 value: computed_value,
19585 }]);
19586 }
19587
19588 match computed_value {
19589 LiteralValue::Array(rows) => {
19590 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
19591 }
19592 other => self.plan_scalar_effects(vertex_id, other),
19593 }
19594 }
19595
19596 fn plan_scalar_effects(
19598 &mut self,
19599 vertex_id: VertexId,
19600 value: LiteralValue,
19601 ) -> Result<Vec<Effect>, ExcelError> {
19602 if !matches!(&value, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Spill) {
19603 self.blocked_pending_spills
19604 .retain(|entry| entry.0 != vertex_id);
19605 }
19606 let has_spill = self
19607 .graph
19608 .spill_cells_for_anchor(vertex_id)
19609 .is_some_and(|c| !c.is_empty());
19610
19611 let mut effects = Vec::new();
19612 if has_spill {
19613 effects.push(Effect::SpillClear {
19614 anchor_vertex: vertex_id,
19615 });
19616 }
19617 effects.push(Effect::WriteCell { vertex_id, value });
19618 Ok(effects)
19619 }
19620
19621 fn plan_array_effects(
19623 &mut self,
19624 vertex_id: VertexId,
19625 rows: Vec<Vec<LiteralValue>>,
19626 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19627 ) -> Result<Vec<Effect>, ExcelError> {
19628 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
19630
19631 let anchor = self
19632 .graph
19633 .get_cell_ref(vertex_id)
19634 .expect("cell ref for vertex");
19635 let sheet_id = anchor.sheet_id;
19636 let h = rows.len() as u32;
19637 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
19638
19639 let spill_cells = (h as u64).saturating_mul(w as u64);
19641 if spill_cells > self.config.spill.max_spill_cells as u64 {
19642 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
19643 }
19644
19645 const PACKED_MAX_ROW: u32 = 1_048_575;
19647 const PACKED_MAX_COL: u32 = 16_383;
19648 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
19649 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
19650 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
19651 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
19652 }
19653
19654 let mut targets = Vec::new();
19655 for r in 0..h {
19656 for c in 0..w {
19657 targets.push(self.graph.make_cell_ref_internal(
19658 sheet_id,
19659 anchor.coord.row() + r,
19660 anchor.coord.col() + c,
19661 ));
19662 }
19663 }
19664
19665 if h != 0 && w != 0 {
19666 let occupied = self.pending_spill_occupied(anchor, end_row, end_col);
19667 if occupied
19668 || self
19669 .blocked_pending_spills
19670 .iter()
19671 .any(|entry| entry.0 == vertex_id)
19672 {
19673 self.spill_mgr.release_owner(vertex_id);
19674 self.remember_pending_spill(
19675 vertex_id,
19676 anchor,
19677 Region::rect(
19678 sheet_id,
19679 anchor.coord.row(),
19680 end_row,
19681 anchor.coord.col(),
19682 end_col,
19683 ),
19684 )?;
19685 }
19686 if occupied {
19687 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
19688 }
19689 }
19690
19691 match self.spill_mgr.reserve(
19693 vertex_id,
19694 anchor,
19695 SpillShape { rows: h, cols: w },
19696 SpillMeta {
19697 epoch: self.recalc_epoch,
19698 config: self.config.spill,
19699 },
19700 ) {
19701 Ok(()) => {
19702 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
19704 vertex_id,
19705 &targets,
19706 overwritable_formulas,
19707 ) {
19708 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
19709 }
19710
19711 if !self.graph.value_cache_enabled() {
19715 let sheet_name = self.graph.sheet_name(sheet_id);
19716 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
19717 for cell in targets.iter() {
19718 if *cell == anchor {
19720 continue;
19721 }
19722 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
19724 continue;
19725 }
19726 if let Some(vid) = self.graph.get_vertex_id_for_address(cell)
19728 && vid != vertex_id
19729 {
19730 match self.graph.get_vertex_kind(vid) {
19731 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19732 continue;
19733 }
19734 _ => {}
19735 }
19736 }
19737
19738 let v = asheet.get_cell_value(
19739 cell.coord.row() as usize,
19740 cell.coord.col() as usize,
19741 );
19742 if !matches!(v, LiteralValue::Empty) {
19743 return self.plan_spill_error_effects(
19744 vertex_id,
19745 "BlockedByValue",
19746 h,
19747 w,
19748 );
19749 }
19750 }
19751 }
19752 }
19753
19754 let top_left = rows
19755 .first()
19756 .and_then(|r| r.first())
19757 .cloned()
19758 .unwrap_or(LiteralValue::Empty);
19759
19760 let mut effects = Vec::new();
19761 let has_prev = self
19763 .graph
19764 .spill_cells_for_anchor(vertex_id)
19765 .is_some_and(|c| !c.is_empty());
19766 if has_prev {
19767 effects.push(Effect::SpillClear {
19768 anchor_vertex: vertex_id,
19769 });
19770 }
19771 effects.push(Effect::SpillCommit {
19772 anchor_vertex: vertex_id,
19773 anchor_cell: anchor,
19774 target_cells: targets,
19775 values: rows,
19776 });
19777 effects.push(Effect::WriteCell {
19778 vertex_id,
19779 value: top_left,
19780 });
19781 Ok(effects)
19782 }
19783 Err(e) => {
19784 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
19785 self.plan_spill_error_effects(vertex_id, &msg, h, w)
19786 }
19787 }
19788 }
19789
19790 fn plan_spill_error_effects(
19792 &mut self,
19793 vertex_id: VertexId,
19794 message: &str,
19795 expected_rows: u32,
19796 expected_cols: u32,
19797 ) -> Result<Vec<Effect>, ExcelError> {
19798 self.spill_mgr.release_owner(vertex_id);
19799 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
19800 .with_message(message)
19801 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
19802 expected_rows,
19803 expected_cols,
19804 });
19805 let spill_val = LiteralValue::Error(spill_err);
19806
19807 let effects = vec![
19808 Effect::SpillClear {
19809 anchor_vertex: vertex_id,
19810 },
19811 Effect::WriteCell {
19812 vertex_id,
19813 value: spill_val,
19814 },
19815 ];
19816 Ok(effects)
19817 }
19818
19819 pub(crate) fn apply_effect(
19821 &mut self,
19822 effect: &Effect,
19823 delta: Option<&mut DeltaCollector>,
19824 log: Option<&mut ChangeLog>,
19825 ) -> Result<(), ExcelError> {
19826 self.apply_effect_with_computed_writes(effect, delta, log, None)
19827 }
19828
19829 fn apply_effect_with_computed_writes(
19830 &mut self,
19831 effect: &Effect,
19832 delta: Option<&mut DeltaCollector>,
19833 log: Option<&mut ChangeLog>,
19834 computed_writes: Option<&mut ComputedWriteBuffer>,
19835 ) -> Result<(), ExcelError> {
19836 match effect {
19837 Effect::WriteCell { vertex_id, value } => {
19838 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
19839 }
19840 Effect::SpillClear { anchor_vertex } => {
19841 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
19842 }
19843 Effect::SpillCommit {
19844 anchor_vertex,
19845 anchor_cell: _,
19846 target_cells,
19847 values,
19848 } => {
19849 self.apply_spill_commit(
19850 *anchor_vertex,
19851 target_cells,
19852 values.clone(),
19853 delta,
19854 log,
19855 computed_writes,
19856 )?;
19857 }
19858 }
19859 Ok(())
19860 }
19861
19862 fn apply_write_cell(
19864 &mut self,
19865 vertex_id: VertexId,
19866 value: &LiteralValue,
19867 delta: Option<&mut DeltaCollector>,
19868 mut computed_writes: Option<&mut ComputedWriteBuffer>,
19869 ) -> Result<(), ExcelError> {
19870 if let Some(d) = delta
19871 && d.mode != DeltaMode::Off
19872 {
19873 if let Some(buffer) = computed_writes.as_deref_mut() {
19874 self.flush_computed_write_buffer(buffer)?;
19875 }
19876 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
19877 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19878 let old = self
19879 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19880 .unwrap_or(LiteralValue::Empty);
19881 if old != *value {
19882 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19883 }
19884 }
19885 }
19886 self.graph.update_vertex_value_ref(vertex_id, value);
19887 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
19888 Ok(())
19889 }
19890
19891 fn apply_spill_clear(
19893 &mut self,
19894 anchor_vertex: VertexId,
19895 delta: Option<&mut DeltaCollector>,
19896 log: Option<&mut ChangeLog>,
19897 computed_writes: Option<&mut ComputedWriteBuffer>,
19898 ) -> Result<(), ExcelError> {
19899 if let Some(buffer) = computed_writes {
19900 self.flush_computed_write_buffer(buffer)?;
19901 }
19902
19903 let spill_cells = self
19904 .graph
19905 .spill_cells_for_anchor(anchor_vertex)
19906 .map(|cells| cells.to_vec())
19907 .unwrap_or_default();
19908 if spill_cells.is_empty() {
19909 return Ok(());
19910 }
19911
19912 let snapshot = if log.is_some() {
19914 self.snapshot_spill_for_anchor(anchor_vertex)
19915 } else {
19916 None
19917 };
19918
19919 if let Some(d) = delta
19921 && d.mode != DeltaMode::Off
19922 {
19923 let empty = LiteralValue::Empty;
19924 for cell in spill_cells.iter() {
19925 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19926 let old = self
19927 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19928 .unwrap_or(LiteralValue::Empty);
19929 if old != empty {
19930 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19931 }
19932 }
19933 }
19934
19935 self.graph.clear_spill_region(anchor_vertex);
19936 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
19937 self.record_structural_change(scope);
19938 }
19939
19940 if self.config.arrow_storage_enabled
19942 && self.config.delta_overlay_enabled
19943 && self.config.write_formula_overlay_enabled
19944 {
19945 let empty = LiteralValue::Empty;
19946 for cell in spill_cells.iter() {
19947 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19948 self.mirror_value_to_computed_overlay(
19949 &sheet_name,
19950 cell.coord.row() + 1,
19951 cell.coord.col() + 1,
19952 &empty,
19953 );
19954 }
19955 }
19956
19957 if let Some(log) = log
19959 && let Some(old) = snapshot
19960 {
19961 log.record(ChangeEvent::SpillCleared {
19962 anchor: anchor_vertex,
19963 old,
19964 });
19965 }
19966 Ok(())
19967 }
19968
19969 fn apply_spill_commit(
19971 &mut self,
19972 anchor_vertex: VertexId,
19973 target_cells: &[CellRef],
19974 values: Vec<Vec<LiteralValue>>,
19975 delta: Option<&mut DeltaCollector>,
19976 log: Option<&mut ChangeLog>,
19977 computed_writes: Option<&mut ComputedWriteBuffer>,
19978 ) -> Result<(), ExcelError> {
19979 self.guard_pending_spill_commit(anchor_vertex, target_cells)?;
19980 if let Some(buffer) = computed_writes {
19981 self.flush_computed_write_buffer(buffer)?;
19982 }
19983
19984 let old_snapshot = if log.is_some() {
19986 self.snapshot_spill_for_anchor(anchor_vertex)
19987 } else {
19988 None
19989 };
19990
19991 self.commit_spill_and_mirror(
19993 anchor_vertex,
19994 target_cells,
19995 values.clone(),
19996 delta,
19997 None, )?;
19999
20000 if let Some(log) = log {
20002 log.record(ChangeEvent::SpillCommitted {
20003 anchor: anchor_vertex,
20004 old: old_snapshot,
20005 new: SpillSnapshot {
20006 target_cells: target_cells.to_vec(),
20007 values,
20008 },
20009 });
20010 }
20011 Ok(())
20012 }
20013
20014 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
20019 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
20020 if cells.is_empty() {
20021 return None;
20022 }
20023
20024 let max = self.config.spill.max_spill_cells as usize;
20025 let mut cells = cells;
20026 if cells.len() > max {
20027 cells.truncate(max);
20028 }
20029
20030 let first = *cells.first().expect("non-empty spill cells");
20031 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
20032 let row0 = first.coord.row();
20033 let col0 = first.coord.col();
20034
20035 let mut max_row = row0;
20036 let mut max_col = col0;
20037 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
20038 for cell in &cells {
20039 max_row = max_row.max(cell.coord.row());
20040 max_col = max_col.max(cell.coord.col());
20041 let v = self
20042 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20043 .unwrap_or(LiteralValue::Empty);
20044 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
20045 }
20046
20047 let rows = (max_row - row0 + 1) as usize;
20048 let cols = (max_col - col0 + 1) as usize;
20049 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
20050 for r in 0..rows {
20051 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
20052 for c in 0..cols {
20053 row.push(
20054 by_coord
20055 .get(&(row0 + r as u32, col0 + c as u32))
20056 .cloned()
20057 .unwrap_or(LiteralValue::Empty),
20058 );
20059 }
20060 values.push(row);
20061 }
20062
20063 Some(SpillSnapshot {
20064 target_cells: cells,
20065 values,
20066 })
20067 }
20068
20069 fn flush_before_range_dependent_vertex(
20070 &mut self,
20071 vertex_id: VertexId,
20072 computed_writes: &mut ComputedWriteBuffer,
20073 ) -> Result<(), ExcelError> {
20074 if self.graph.reads_compressed_range(vertex_id) {
20075 self.flush_computed_write_buffer(computed_writes)?;
20076 }
20077 Ok(())
20078 }
20079
20080 fn plan_vertex_effects_with_computed_flush(
20081 &mut self,
20082 vertex_id: VertexId,
20083 computed_value: LiteralValue,
20084 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
20085 computed_writes: &mut ComputedWriteBuffer,
20086 ) -> Result<Vec<Effect>, ExcelError> {
20087 if matches!(&computed_value, LiteralValue::Array(_)) {
20088 self.flush_computed_write_buffer(computed_writes)?;
20089 }
20090 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
20091 }
20092
20093 fn evaluate_small_layer_direct_effects(
20096 &mut self,
20097 layer: &super::scheduler::Layer,
20098 delta: Option<&mut DeltaCollector>,
20099 log: Option<&mut ChangeLog>,
20100 cancel_flag: Option<&AtomicBool>,
20101 cancel_check_every: usize,
20102 cancel_message: &'static str,
20103 ) -> Result<usize, ExcelError> {
20104 let cancel = cancel_flag.map(|flag| (flag, cancel_check_every, cancel_message));
20105 self.evaluate_layer_units(layer, delta, log, cancel, false)
20106 }
20107
20108 fn evaluate_layer_units(
20115 &mut self,
20116 layer: &super::scheduler::Layer,
20117 delta: Option<&mut DeltaCollector>,
20118 log: Option<&mut ChangeLog>,
20119 cancel: Option<(&AtomicBool, usize, &'static str)>,
20120 buffered: bool,
20121 ) -> Result<usize, ExcelError> {
20122 self.evaluate_layer_units_until(layer, delta, log, cancel, buffered, None)
20123 }
20124
20125 fn evaluate_layer_units_until(
20129 &mut self,
20130 layer: &super::scheduler::Layer,
20131 mut delta: Option<&mut DeltaCollector>,
20132 mut log: Option<&mut ChangeLog>,
20133 cancel: Option<(&AtomicBool, usize, &'static str)>,
20134 buffered: bool,
20135 stop_at: Option<crate::instant::FzInstant>,
20136 ) -> Result<usize, ExcelError> {
20137 let mut chain_values = None;
20140 if layer.sequential && !layer.runs.is_empty() {
20141 chain_values = match layer.runs.as_slice() {
20142 [run] if run.start == 0 && run.len as usize == layer.vertices.len() => {
20143 self.try_chain_lift(*run, &layer.vertices)
20144 }
20145 _ => None,
20146 };
20147 if chain_values.is_none() {
20148 let cells = super::scheduler::Layer {
20149 vertices: layer.vertices.clone(),
20150 runs: Vec::new(),
20151 sequential: true,
20152 };
20153 return self.evaluate_layer_units_until(&cells, delta, log, cancel, false, stop_at);
20154 }
20155 }
20156 let chained = chain_values.is_some();
20157 let buffered = buffered || chained;
20159 let track_unflushed = buffered
20166 && self.freshness_armed()
20167 && layer.vertices.iter().any(|&v| self.graph.is_dynamic(v));
20168 let mut committed_unit: &[VertexId] = &[];
20169 let mut computed_writes = ComputedWriteBuffer::default();
20170 let mut next_check = 0usize;
20171 let mut done = 0usize;
20172 for unit in layer_units(layer) {
20173 if done > 0
20174 && let Some(stop_at) = stop_at
20175 && crate::instant::FzInstant::now() >= stop_at
20176 {
20177 self.flush_computed_write_buffer(&mut computed_writes)?;
20178 if track_unflushed {
20179 self.freshness_flushed();
20180 }
20181 return Ok(done);
20182 }
20183 if let Some((flag, every, message)) = cancel
20184 && every > 0
20185 && done >= next_check
20186 {
20187 next_check = (done / every + 1) * every;
20188 if flag.load(Ordering::Relaxed) {
20189 if buffered {
20190 self.flush_computed_write_buffer(&mut computed_writes)?;
20191 }
20192 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
20193 .with_message(message.to_string()));
20194 }
20195 }
20196 if buffered && self.unit_reads_compressed_range(layer, unit) {
20197 self.flush_computed_write_buffer(&mut computed_writes)?;
20198 }
20199 if track_unflushed {
20203 if computed_writes.is_empty() {
20204 self.freshness_flushed();
20205 } else {
20206 self.freshness_note_unflushed(committed_unit);
20207 }
20208 committed_unit = unit_members(layer, unit);
20209 }
20210 let values = match (chain_values.take(), unit) {
20211 (Some(chain), LayerUnit::Run(run)) => {
20212 let members =
20213 &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20214 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20215 match self.commit_run_numbers(
20216 run,
20217 members,
20218 &chain,
20219 delta_active,
20220 Some(&mut computed_writes),
20221 ) {
20222 Ok(true) => {
20223 done += chain.len();
20224 continue;
20225 }
20226 Ok(false) => {}
20227 Err(e) => {
20228 self.flush_computed_write_buffer(&mut computed_writes)?;
20229 return Err(e);
20230 }
20231 }
20232 members
20233 .iter()
20234 .copied()
20235 .zip(chain.into_iter().map(LiteralValue::Number))
20236 .collect()
20237 }
20238 (_, unit) => self.evaluate_unit_immutable(layer, unit),
20239 };
20240 done += values.len();
20241 if let LayerUnit::Run(run) = unit {
20242 let members = &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20243 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20244 let committed = self.commit_run_scalars(
20245 run,
20246 members,
20247 &values,
20248 delta_active,
20249 buffered.then_some(&mut computed_writes),
20250 );
20251 match committed {
20252 Ok(true) => continue,
20253 Ok(false) => {}
20254 Err(e) => {
20255 self.flush_computed_write_buffer(&mut computed_writes)?;
20256 return Err(e);
20257 }
20258 }
20259 }
20260 for (vertex_id, value) in values {
20261 let effects = if buffered {
20262 self.plan_vertex_effects_with_computed_flush(
20263 vertex_id,
20264 value,
20265 None,
20266 &mut computed_writes,
20267 )
20268 } else {
20269 self.plan_vertex_effects(vertex_id, value, None)
20270 };
20271 let effects = match effects {
20272 Ok(effects) => effects,
20273 Err(e) => {
20274 self.flush_computed_write_buffer(&mut computed_writes)?;
20275 return Err(e);
20276 }
20277 };
20278 for effect in &effects {
20279 if let Err(e) = self.apply_effect_with_computed_writes(
20280 effect,
20281 delta.as_deref_mut(),
20282 log.as_deref_mut(),
20283 buffered.then_some(&mut computed_writes),
20284 ) {
20285 self.flush_computed_write_buffer(&mut computed_writes)?;
20286 return Err(e);
20287 }
20288 }
20289 }
20290 }
20291 self.flush_computed_write_buffer(&mut computed_writes)?;
20292 if track_unflushed {
20293 self.freshness_flushed();
20294 }
20295 #[cfg(debug_assertions)]
20298 if chained {
20299 for &v in &layer.vertices {
20300 let cell = self.graph.get_cell_ref(v);
20301 let (sheet, row, col) = cell
20302 .map(|c| {
20303 (
20304 self.graph.sheet_name(c.sheet_id).to_string(),
20305 c.coord.row() + 1,
20306 c.coord.col() + 1,
20307 )
20308 })
20309 .expect("chain member cell");
20310 let written = self.get_cell_value(&sheet, row, col);
20311 let oracle = self
20312 .evaluate_vertex_immutable(v)
20313 .unwrap_or_else(LiteralValue::Error);
20314 assert!(
20315 written
20316 .as_ref()
20317 .is_some_and(|w| same_value_bits(w, &oracle)),
20318 "chain member {sheet}!R{row}C{col}: {written:?} vs per-cell {oracle:?}"
20319 );
20320 }
20321 }
20322 #[cfg(not(debug_assertions))]
20323 let _ = chained;
20324 Ok(layer.vertices.len())
20325 }
20326
20327 fn evaluate_layer_sequential_effects(
20329 &mut self,
20330 layer: &super::scheduler::Layer,
20331 ) -> Result<usize, ExcelError> {
20332 let buffered = buffer_layer_writes(layer);
20333 self.evaluate_layer_units(layer, None, None, None, buffered)
20334 }
20335
20336 fn evaluate_layer_sequential_with_delta_effects(
20338 &mut self,
20339 layer: &super::scheduler::Layer,
20340 delta: &mut DeltaCollector,
20341 ) -> Result<usize, ExcelError> {
20342 let buffered = buffer_layer_writes(layer);
20343 self.evaluate_layer_units(layer, Some(delta), None, None, buffered)
20344 }
20345
20346 fn evaluate_layer_sequential_cancellable_effects(
20348 &mut self,
20349 layer: &super::scheduler::Layer,
20350 cancel_flag: &AtomicBool,
20351 ) -> Result<usize, ExcelError> {
20352 let buffered = buffer_layer_writes(layer);
20353 let cancel = (cancel_flag, 256, "Evaluation cancelled within layer");
20354 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
20355 }
20356
20357 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
20359 &mut self,
20360 layer: &super::scheduler::Layer,
20361 cancel_flag: &AtomicBool,
20362 ) -> Result<usize, ExcelError> {
20363 let buffered = buffer_layer_writes(layer);
20364 let cancel = (
20365 cancel_flag,
20366 128,
20367 "Demand-driven evaluation cancelled within layer",
20368 );
20369 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
20370 }
20371
20372 fn evaluate_layer_parallel_effects(
20374 &mut self,
20375 layer: &super::scheduler::Layer,
20376 min_chunk: u32,
20377 ) -> Result<usize, ExcelError> {
20378 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20379
20380 let phases = self.parallel_phases(layer);
20381
20382 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20383 let mut applied = 0usize;
20384
20385 for (units, group) in &phases {
20386 let group = &group[..];
20387 if group.is_empty() {
20388 continue;
20389 }
20390 let mut computed_writes = ComputedWriteBuffer::default();
20391
20392 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
20393 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, min_chunk));
20394
20395 self.freshness_gate_group(group);
20398 match results {
20399 Ok(vertex_results) => {
20400 let (vertex_results, committed) = self.commit_parallel_runs(
20401 layer,
20402 units,
20403 vertex_results,
20404 false,
20405 &mut computed_writes,
20406 )?;
20407 applied = applied.saturating_add(committed);
20408 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20411 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20412 for (vertex_id, result) in vertex_results {
20413 if matches!(result, LiteralValue::Array(_)) {
20414 arrays.push((vertex_id, result));
20415 } else {
20416 others.push((vertex_id, result));
20417 }
20418 }
20419 for (vertex_id, result) in arrays {
20420 let effects = match self.plan_vertex_effects_with_computed_flush(
20421 vertex_id,
20422 result,
20423 Some(&inflight),
20424 &mut computed_writes,
20425 ) {
20426 Ok(effects) => effects,
20427 Err(e) => {
20428 self.flush_computed_write_buffer(&mut computed_writes)?;
20429 return Err(e);
20430 }
20431 };
20432 for effect in &effects {
20433 if let Err(e) = self.apply_effect_with_computed_writes(
20434 effect,
20435 None,
20436 None,
20437 Some(&mut computed_writes),
20438 ) {
20439 self.flush_computed_write_buffer(&mut computed_writes)?;
20440 return Err(e);
20441 }
20442 }
20443 applied = applied.saturating_add(1);
20444 }
20445 self.flush_computed_write_buffer(&mut computed_writes)?;
20447 for (vertex_id, result) in others {
20448 let effects = match self.plan_vertex_effects_with_computed_flush(
20449 vertex_id,
20450 result,
20451 Some(&inflight),
20452 &mut computed_writes,
20453 ) {
20454 Ok(effects) => effects,
20455 Err(e) => {
20456 self.flush_computed_write_buffer(&mut computed_writes)?;
20457 return Err(e);
20458 }
20459 };
20460 for effect in &effects {
20461 if let Err(e) = self.apply_effect_with_computed_writes(
20462 effect,
20463 None,
20464 None,
20465 Some(&mut computed_writes),
20466 ) {
20467 self.flush_computed_write_buffer(&mut computed_writes)?;
20468 return Err(e);
20469 }
20470 }
20471 applied = applied.saturating_add(1);
20472 }
20473 self.flush_computed_write_buffer(&mut computed_writes)?;
20475 }
20476 Err(e) => {
20477 self.flush_computed_write_buffer(&mut computed_writes)?;
20478 return Err(e);
20479 }
20480 }
20481 }
20482
20483 Ok(applied)
20484 }
20485
20486 fn evaluate_layer_parallel_with_delta_effects(
20488 &mut self,
20489 layer: &super::scheduler::Layer,
20490 delta: &mut DeltaCollector,
20491 ) -> Result<usize, ExcelError> {
20492 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20493
20494 let phases = self.parallel_phases(layer);
20495
20496 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20497 let mut applied = 0usize;
20498
20499 for (units, group) in &phases {
20500 let group = &group[..];
20501 if group.is_empty() {
20502 continue;
20503 }
20504 let mut computed_writes = ComputedWriteBuffer::default();
20505 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
20506 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, 8));
20507
20508 self.freshness_gate_group(group);
20511 match results {
20512 Ok(vertex_results) => {
20513 let (vertex_results, committed) = self.commit_parallel_runs(
20514 layer,
20515 units,
20516 vertex_results,
20517 delta.mode != DeltaMode::Off,
20518 &mut computed_writes,
20519 )?;
20520 applied = applied.saturating_add(committed);
20521 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20522 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20523 for (vertex_id, result) in vertex_results {
20524 if matches!(result, LiteralValue::Array(_)) {
20525 arrays.push((vertex_id, result));
20526 } else {
20527 others.push((vertex_id, result));
20528 }
20529 }
20530 for (vertex_id, result) in arrays {
20531 let effects = match self.plan_vertex_effects_with_computed_flush(
20532 vertex_id,
20533 result,
20534 Some(&inflight),
20535 &mut computed_writes,
20536 ) {
20537 Ok(effects) => effects,
20538 Err(e) => {
20539 self.flush_computed_write_buffer(&mut computed_writes)?;
20540 return Err(e);
20541 }
20542 };
20543 for effect in &effects {
20544 if let Err(e) = self.apply_effect_with_computed_writes(
20545 effect,
20546 Some(delta),
20547 None,
20548 Some(&mut computed_writes),
20549 ) {
20550 self.flush_computed_write_buffer(&mut computed_writes)?;
20551 return Err(e);
20552 }
20553 }
20554 applied = applied.saturating_add(1);
20555 }
20556 self.flush_computed_write_buffer(&mut computed_writes)?;
20557 for (vertex_id, result) in others {
20558 let effects = match self.plan_vertex_effects_with_computed_flush(
20559 vertex_id,
20560 result,
20561 Some(&inflight),
20562 &mut computed_writes,
20563 ) {
20564 Ok(effects) => effects,
20565 Err(e) => {
20566 self.flush_computed_write_buffer(&mut computed_writes)?;
20567 return Err(e);
20568 }
20569 };
20570 for effect in &effects {
20571 if let Err(e) = self.apply_effect_with_computed_writes(
20572 effect,
20573 Some(delta),
20574 None,
20575 Some(&mut computed_writes),
20576 ) {
20577 self.flush_computed_write_buffer(&mut computed_writes)?;
20578 return Err(e);
20579 }
20580 }
20581 applied = applied.saturating_add(1);
20582 }
20583 self.flush_computed_write_buffer(&mut computed_writes)?;
20584 }
20585 Err(e) => {
20586 self.flush_computed_write_buffer(&mut computed_writes)?;
20587 return Err(e);
20588 }
20589 }
20590 }
20591
20592 Ok(applied)
20593 }
20594
20595 fn evaluate_layer_parallel_cancellable_effects(
20597 &mut self,
20598 layer: &super::scheduler::Layer,
20599 cancel_flag: &AtomicBool,
20600 ) -> Result<usize, ExcelError> {
20601 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20602
20603 if cancel_flag.load(Ordering::Relaxed) {
20604 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
20605 .with_message("Parallel evaluation cancelled before starting".to_string()));
20606 }
20607
20608 let phases = self.parallel_phases(layer);
20609
20610 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20611 let mut applied = 0usize;
20612
20613 for (units, group) in &phases {
20614 let group = &group[..];
20615 if group.is_empty() {
20616 continue;
20617 }
20618 let mut computed_writes = ComputedWriteBuffer::default();
20619
20620 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> = thread_pool
20621 .install(|| self.evaluate_units_parallel(layer, units, Some(cancel_flag), 8));
20622
20623 self.freshness_gate_group(group);
20626 match results {
20627 Ok(vertex_results) => {
20628 let (vertex_results, committed) = self.commit_parallel_runs(
20629 layer,
20630 units,
20631 vertex_results,
20632 false,
20633 &mut computed_writes,
20634 )?;
20635 applied = applied.saturating_add(committed);
20636 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20637 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20638 for (vertex_id, result) in vertex_results {
20639 if matches!(result, LiteralValue::Array(_)) {
20640 arrays.push((vertex_id, result));
20641 } else {
20642 others.push((vertex_id, result));
20643 }
20644 }
20645 for (vertex_id, result) in arrays {
20646 let effects = match self.plan_vertex_effects_with_computed_flush(
20647 vertex_id,
20648 result,
20649 Some(&inflight),
20650 &mut computed_writes,
20651 ) {
20652 Ok(effects) => effects,
20653 Err(e) => {
20654 self.flush_computed_write_buffer(&mut computed_writes)?;
20655 return Err(e);
20656 }
20657 };
20658 for effect in &effects {
20659 if let Err(e) = self.apply_effect_with_computed_writes(
20660 effect,
20661 None,
20662 None,
20663 Some(&mut computed_writes),
20664 ) {
20665 self.flush_computed_write_buffer(&mut computed_writes)?;
20666 return Err(e);
20667 }
20668 }
20669 applied = applied.saturating_add(1);
20670 }
20671 self.flush_computed_write_buffer(&mut computed_writes)?;
20672 for (vertex_id, result) in others {
20673 let effects = match self.plan_vertex_effects_with_computed_flush(
20674 vertex_id,
20675 result,
20676 Some(&inflight),
20677 &mut computed_writes,
20678 ) {
20679 Ok(effects) => effects,
20680 Err(e) => {
20681 self.flush_computed_write_buffer(&mut computed_writes)?;
20682 return Err(e);
20683 }
20684 };
20685 for effect in &effects {
20686 if let Err(e) = self.apply_effect_with_computed_writes(
20687 effect,
20688 None,
20689 None,
20690 Some(&mut computed_writes),
20691 ) {
20692 self.flush_computed_write_buffer(&mut computed_writes)?;
20693 return Err(e);
20694 }
20695 }
20696 applied = applied.saturating_add(1);
20697 }
20698 self.flush_computed_write_buffer(&mut computed_writes)?;
20699 }
20700 Err(e) => {
20701 self.flush_computed_write_buffer(&mut computed_writes)?;
20702 return Err(e);
20703 }
20704 }
20705 }
20706
20707 Ok(applied)
20708 }
20709
20710 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
20717 self.observe_evaluation_resource_request(EvaluationRequestKind::FullLogged, |engine| {
20718 engine.evaluate_all_logged_unobserved(log)
20719 })
20720 }
20721
20722 fn evaluate_all_logged_unobserved(
20723 &mut self,
20724 log: &mut ChangeLog,
20725 ) -> Result<EvalResult, ExcelError> {
20726 self.observe_function_semantic_epoch()?;
20727 let _source_cache = self.source_cache_session();
20728 self.validate_deterministic_mode()?;
20729 if self.config.defer_graph_building {
20730 self.build_graph_all()?;
20731 }
20732 self.require_unified_authority()?;
20733 self.begin_evaluation_request();
20734 self.reset_virtual_dep_telemetry_if_disabled();
20735 let start = crate::instant::FzInstant::now();
20736 let mut computed_vertices = 0;
20737 let mut cycle_errors = 0;
20738
20739 let mut replan_iterations = 0;
20740 const MAX_REPLAN: usize = 5;
20741 let mut telemetry = self
20742 .config
20743 .enable_virtual_dep_telemetry
20744 .then(|| self.start_virtual_dep_telemetry());
20745
20746 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
20747
20748 let result = (|| -> Result<EvalResult, ExcelError> {
20749 loop {
20750 let to_evaluate = self.graph.get_evaluation_vertices();
20751 if to_evaluate.is_empty() {
20752 if let Some(t) = telemetry.as_mut()
20753 && t.bailout_reason.is_none()
20754 {
20755 t.bailout_reason = Some("no_work");
20756 }
20757 break;
20758 }
20759
20760 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
20761 if let Some(t) = telemetry.as_mut() {
20762 Self::accumulate_schedule_meta(t, &meta);
20763 }
20764
20765 self.begin_pass(&schedule);
20768 for (unit_index, &unit) in schedule.units.iter().enumerate() {
20769 match unit {
20770 ScheduleUnit::Cycle(i) => {
20771 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0
20782 {
20783 cycle_errors += 1;
20784 }
20785 }
20786 ScheduleUnit::Layer(i) => {
20787 computed_vertices +=
20788 self.evaluate_layer_logged(schedule.unit_layer(i), log)?;
20789 }
20790 }
20791 if self.stop_after_unit(&schedule, unit_index) {
20792 break;
20793 }
20794 }
20795
20796 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
20797 if let Some(t) = telemetry.as_mut() {
20798 t.changed_vdeps_total += changed_vertices.len();
20799 }
20800 self.resource_checkpoint(0)?;
20801 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
20802 if let Some(t) = telemetry.as_mut() {
20803 t.bailout_reason = Some("converged");
20804 }
20805 break;
20806 }
20807 if replan_iterations >= MAX_REPLAN {
20808 if let Some(mut t) = telemetry.take() {
20809 t.bailout_reason = Some("max_replan");
20810 t.replan_iterations = replan_iterations;
20811 self.last_virtual_dep_telemetry = t;
20812 }
20813 return Err(
20814 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
20815 );
20816 }
20817 replan_iterations += 1;
20818 }
20819
20820 if let Some(mut t) = telemetry {
20821 t.replan_iterations = replan_iterations;
20822 self.last_virtual_dep_telemetry = t;
20823 }
20824
20825 self.redirty_for_next_recalc();
20826 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
20827
20828 Ok(EvalResult {
20829 computed_vertices,
20830 cycle_errors,
20831 elapsed: start.elapsed(),
20832 })
20833 })();
20834 log.end_compound();
20835 result
20836 }
20837
20838 fn evaluate_layer_logged(
20840 &mut self,
20841 layer: &super::scheduler::Layer,
20842 log: &mut ChangeLog,
20843 ) -> Result<usize, ExcelError> {
20844 self.resource_checkpoint(layer.vertices.len() as u64)?;
20845 self.evaluate_layer_units(layer, None, Some(log), None, true)
20846 }
20847}