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;
581const UNIT_CANCELLED: &str = "Evaluation cancelled; the evaluated unit was not committed";
583const GROUP_CANCELLED: &str =
584 "Parallel evaluation cancelled; the evaluated group was not committed";
585const SCC_CANCELLED: &str = "Evaluation cancelled; the evaluated cycle was not completed";
586
587#[derive(Debug, Clone, PartialEq)]
588pub(crate) enum ComputedWrite {
589 Cell {
590 seq: u64,
591 sheet_id: SheetId,
592 row0: u32,
593 col0: u32,
594 value: OverlayValue,
595 format_id: Option<crate::format::FormatId>,
596 },
597 Rect {
598 seq: u64,
599 sheet_id: SheetId,
600 sr0: u32,
601 sc0: u32,
602 values: Vec<Vec<OverlayValue>>,
603 },
604 Run {
607 seq: u64,
608 sheet_id: SheetId,
609 row0: u32,
610 col0: u32,
611 entries: Vec<(OverlayValue, Option<crate::format::FormatId>)>,
612 },
613}
614
615impl ComputedWrite {
616 #[inline]
617 pub(crate) fn seq(&self) -> u64 {
618 match self {
619 ComputedWrite::Cell { seq, .. }
620 | ComputedWrite::Rect { seq, .. }
621 | ComputedWrite::Run { seq, .. } => *seq,
622 }
623 }
624}
625
626#[derive(Debug, Default)]
627pub(crate) struct ComputedWriteBuffer {
628 writes: Vec<ComputedWrite>,
629 next_seq: u64,
630 estimated_bytes: usize,
631 formats_present: bool,
632}
633
634impl ComputedWriteBuffer {
635 const ENTRY_BASE_BYTES: usize = 32;
636
637 #[inline]
638 pub(crate) fn is_empty(&self) -> bool {
639 self.writes.is_empty()
640 }
641
642 #[inline]
643 pub(crate) fn len(&self) -> usize {
644 self.writes.len()
645 }
646
647 #[inline]
648 pub(crate) fn writes(&self) -> &[ComputedWrite] {
649 &self.writes
650 }
651
652 #[inline]
653 pub(crate) fn estimated_bytes(&self) -> usize {
654 self.estimated_bytes
655 }
656
657 #[cfg(feature = "tracing")]
658 fn traced_cell_count(&self) -> usize {
659 self.writes
660 .iter()
661 .map(|write| match write {
662 ComputedWrite::Cell { .. } => 1,
663 ComputedWrite::Rect { values, .. } => values.iter().map(Vec::len).sum(),
664 ComputedWrite::Run { entries, .. } => entries.len(),
665 })
666 .sum()
667 }
668
669 pub(crate) fn push_cell(
670 &mut self,
671 sheet_id: SheetId,
672 row0: u32,
673 col0: u32,
674 value: OverlayValue,
675 ) {
676 self.push_cell_with_format(sheet_id, row0, col0, value, None);
677 }
678
679 pub(crate) fn push_cell_with_format(
680 &mut self,
681 sheet_id: SheetId,
682 row0: u32,
683 col0: u32,
684 value: OverlayValue,
685 format_id: Option<crate::format::FormatId>,
686 ) {
687 let format_id = format_id.filter(|id| *id != crate::format::FormatId::GENERAL);
688 self.formats_present |= format_id.is_some();
689 let seq = self.next_sequence();
690 self.estimated_bytes = self
691 .estimated_bytes
692 .saturating_add(Self::estimate_value_bytes(&value));
693 self.writes.push(ComputedWrite::Cell {
694 seq,
695 sheet_id,
696 row0,
697 col0,
698 value,
699 format_id,
700 });
701 }
702
703 pub(crate) fn push_column_run(
704 &mut self,
705 sheet_id: SheetId,
706 row0: u32,
707 col0: u32,
708 mut entries: Vec<(OverlayValue, Option<crate::format::FormatId>)>,
709 ) {
710 let seq = self.next_sequence();
711 let mut added = 0usize;
712 for (value, format_id) in entries.iter_mut() {
713 *format_id = format_id.filter(|id| *id != crate::format::FormatId::GENERAL);
714 self.formats_present |= format_id.is_some();
715 added = added.saturating_add(Self::estimate_value_bytes(value));
716 }
717 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
718 self.writes.push(ComputedWrite::Run {
719 seq,
720 sheet_id,
721 row0,
722 col0,
723 entries,
724 });
725 }
726
727 pub(crate) fn push_rect(
728 &mut self,
729 sheet_id: SheetId,
730 sr0: u32,
731 sc0: u32,
732 values: Vec<Vec<OverlayValue>>,
733 ) {
734 let seq = self.next_sequence();
735 let added = values
736 .iter()
737 .flat_map(|row| row.iter())
738 .map(Self::estimate_value_bytes)
739 .fold(0usize, usize::saturating_add);
740 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
741 self.writes.push(ComputedWrite::Rect {
742 seq,
743 sheet_id,
744 sr0,
745 sc0,
746 values,
747 });
748 }
749
750 pub(crate) fn clear(&mut self) {
751 self.writes.clear();
752 self.estimated_bytes = 0;
753 self.formats_present = false;
754 }
755
756 fn take_writes(&mut self) -> (Vec<ComputedWrite>, bool) {
757 self.estimated_bytes = 0;
758 let formats_present = std::mem::take(&mut self.formats_present);
759 (std::mem::take(&mut self.writes), formats_present)
760 }
761
762 fn next_sequence(&mut self) -> u64 {
763 let seq = self.next_seq;
764 self.next_seq = self.next_seq.wrapping_add(1);
765 seq
766 }
767
768 #[inline]
769 fn estimate_value_bytes(value: &OverlayValue) -> usize {
770 Self::ENTRY_BASE_BYTES.saturating_add(value.estimated_payload_bytes())
771 }
772}
773
774#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
775struct ComputedWriteChunkKey {
776 sheet_id: SheetId,
777 col0: u32,
778 chunk_idx: usize,
779 chunk_start_row0: u32,
780}
781
782#[derive(Debug, Clone, PartialEq)]
783pub(crate) struct ComputedWriteChunkEntryPlan {
784 pub(crate) row_in_chunk: usize,
785 pub(crate) seq: u64,
786 pub(crate) value: OverlayValue,
787 pub(crate) format_id: Option<crate::format::FormatId>,
788}
789
790#[derive(Debug, Clone, PartialEq, Eq)]
791pub(crate) enum ComputedWriteChunkPlanShape {
792 Point,
793 SparseOffsets {
794 entries: usize,
795 span_len: usize,
796 },
797 DenseRange {
798 start: usize,
799 len: usize,
800 },
801 RunRange {
802 start: usize,
803 len: usize,
804 runs: usize,
805 },
806}
807
808#[derive(Debug, Clone, PartialEq, Eq)]
809pub(crate) enum ComputedWriteFormatClear {
810 Range { start: usize, end: usize },
811 Offsets(Vec<usize>),
812}
813
814#[derive(Debug, Clone, PartialEq)]
815pub(crate) enum ComputedWriteChunkFormatEffect {
816 NoFormatWork,
817 ClearStale(ComputedWriteFormatClear),
818 SetExplicit(Vec<(usize, Option<crate::format::FormatId>)>),
819}
820
821#[derive(Debug, Clone, PartialEq)]
822pub(crate) struct ComputedWriteChunkPlan {
823 pub(crate) sheet_id: SheetId,
824 pub(crate) col0: u32,
825 pub(crate) chunk_idx: usize,
826 pub(crate) chunk_start_row0: u32,
827 pub(crate) entries: Vec<ComputedWriteChunkEntryPlan>,
828 pub(crate) shape: ComputedWriteChunkPlanShape,
829 pub(crate) format_effect: ComputedWriteChunkFormatEffect,
830}
831
832#[derive(Debug, Clone, Default, PartialEq)]
833pub(crate) struct ComputedWriteCoalescingPlan {
834 pub(crate) chunks: Vec<ComputedWriteChunkPlan>,
835 pub(crate) input_cells: usize,
836 pub(crate) coalesced_cells: usize,
837 pub(crate) overwritten_cells: usize,
838}
839
840impl ComputedWriteCoalescingPlan {
841 #[inline]
842 pub(crate) fn is_empty(&self) -> bool {
843 self.chunks.is_empty()
844 }
845}
846
847impl ComputedWriteChunkPlan {
848 fn from_group(
849 key: ComputedWriteChunkKey,
850 mut entries: Vec<ComputedWriteChunkEntryPlan>,
851 formats_present: bool,
852 computed_lane_has_formats: bool,
853 ) -> (Self, usize) {
854 entries.sort_by_key(|entry| (entry.row_in_chunk, entry.seq));
855 let input_len = entries.len();
856 let mut coalesced: Vec<ComputedWriteChunkEntryPlan> = Vec::with_capacity(input_len);
857 for entry in entries {
858 if let Some(prev) = coalesced.last_mut()
859 && prev.row_in_chunk == entry.row_in_chunk
860 {
861 *prev = entry;
862 continue;
863 }
864 coalesced.push(entry);
865 }
866 let overwritten = input_len.saturating_sub(coalesced.len());
867 let shape = Self::classify_shape(&coalesced);
868 let format_effect = Self::classify_format_effect(
869 &coalesced,
870 &shape,
871 formats_present,
872 computed_lane_has_formats,
873 );
874 (
875 Self {
876 sheet_id: key.sheet_id,
877 col0: key.col0,
878 chunk_idx: key.chunk_idx,
879 chunk_start_row0: key.chunk_start_row0,
880 entries: coalesced,
881 shape,
882 format_effect,
883 },
884 overwritten,
885 )
886 }
887
888 fn classify_format_effect(
889 entries: &[ComputedWriteChunkEntryPlan],
890 shape: &ComputedWriteChunkPlanShape,
891 formats_present: bool,
892 computed_lane_has_formats: bool,
893 ) -> ComputedWriteChunkFormatEffect {
894 if !formats_present {
895 return if computed_lane_has_formats {
896 ComputedWriteChunkFormatEffect::ClearStale(Self::format_clear_spec(entries, shape))
897 } else {
898 ComputedWriteChunkFormatEffect::NoFormatWork
899 };
900 }
901
902 if entries.iter().all(|entry| entry.format_id.is_none()) {
903 return if computed_lane_has_formats {
904 ComputedWriteChunkFormatEffect::ClearStale(Self::format_clear_spec(entries, shape))
905 } else {
906 ComputedWriteChunkFormatEffect::NoFormatWork
907 };
908 }
909
910 ComputedWriteChunkFormatEffect::SetExplicit(
911 entries
912 .iter()
913 .map(|entry| (entry.row_in_chunk, entry.format_id))
914 .collect(),
915 )
916 }
917
918 fn format_clear_spec(
919 entries: &[ComputedWriteChunkEntryPlan],
920 shape: &ComputedWriteChunkPlanShape,
921 ) -> ComputedWriteFormatClear {
922 match shape {
923 ComputedWriteChunkPlanShape::DenseRange { start, len }
924 | ComputedWriteChunkPlanShape::RunRange { start, len, .. } => {
925 ComputedWriteFormatClear::Range {
926 start: *start,
927 end: start.saturating_add(*len),
928 }
929 }
930 ComputedWriteChunkPlanShape::Point
931 | ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
932 ComputedWriteFormatClear::Offsets(
933 entries.iter().map(|entry| entry.row_in_chunk).collect(),
934 )
935 }
936 }
937 }
938
939 fn classify_shape(entries: &[ComputedWriteChunkEntryPlan]) -> ComputedWriteChunkPlanShape {
940 debug_assert!(!entries.is_empty());
941 if entries.len() == 1 {
942 return ComputedWriteChunkPlanShape::Point;
943 }
944
945 let start = entries[0].row_in_chunk;
946 let end = entries[entries.len() - 1].row_in_chunk;
947 let span_len = end.saturating_sub(start).saturating_add(1);
948 if span_len != entries.len() {
949 return ComputedWriteChunkPlanShape::SparseOffsets {
950 entries: entries.len(),
951 span_len,
952 };
953 }
954
955 let runs = Self::run_count(entries);
956 if runs < entries.len() {
957 ComputedWriteChunkPlanShape::RunRange {
958 start,
959 len: entries.len(),
960 runs,
961 }
962 } else {
963 ComputedWriteChunkPlanShape::DenseRange {
964 start,
965 len: entries.len(),
966 }
967 }
968 }
969
970 fn run_count(entries: &[ComputedWriteChunkEntryPlan]) -> usize {
971 let mut runs = 0usize;
972 let mut prev: Option<&OverlayValue> = None;
973 for entry in entries {
974 if prev != Some(&entry.value) {
975 runs = runs.saturating_add(1);
976 prev = Some(&entry.value);
977 }
978 }
979 runs
980 }
981}
982
983#[cfg(feature = "tracing")]
984#[derive(Default)]
985struct TraceEvaluationCounters {
986 computed_vertices: usize,
987 cycles: usize,
988}
989
990pub struct Engine<R> {
991 pub(crate) graph: DependencyGraph,
992 resolver: R,
993 pub config: EvalConfig,
994 workbook_load_limits: crate::engine::WorkbookLoadLimits,
995 clock: crate::timezone::SnapshotClock,
1000 thread_pool: Option<Arc<rayon::ThreadPool>>,
1001 pub recalc_epoch: u64,
1002 snapshot_id: std::sync::atomic::AtomicU64,
1003 topology_epoch: u64,
1004 cached_static_schedule: Option<CachedScheduleEntry>,
1005 recent_schedules: Vec<CachedScheduleEntry>,
1010 base_schedule: Option<CachedScheduleEntry>,
1015 #[cfg(any(test, feature = "benchmark_internal"))]
1016 recalc_reuse_probe: std::sync::Mutex<RecalcReuseProbe>,
1017 spill_mgr: ShimSpillManager,
1018 arrow_sheets: SheetStore,
1020 format_registry: crate::format::FormatRegistry,
1022 derived_formats: crate::engine::derived_formats::DerivedFormats,
1024 #[cfg(test)]
1025 derived_format_operations_for_test: std::sync::atomic::AtomicU64,
1026 #[cfg(test)]
1027 family_members_for_test: std::sync::atomic::AtomicU64,
1028 #[cfg(test)]
1029 invariant_bound_members_for_test: std::sync::atomic::AtomicU64,
1030 #[cfg(test)]
1031 lifted_members_for_test: std::sync::atomic::AtomicU64,
1032 #[cfg(test)]
1033 chained_members_for_test: std::sync::atomic::AtomicU64,
1034 #[cfg(test)]
1035 lane_clean_reads_for_test: std::sync::atomic::AtomicU64,
1036 #[cfg(test)]
1037 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64,
1038 #[cfg(test)]
1039 memo_hits_for_test: std::sync::atomic::AtomicU64,
1040 compressed_at_build: Option<u64>,
1042 #[cfg(test)]
1043 computed_overlay_set_explicit_entry_operations_for_test: u64,
1044 #[cfg(test)]
1045 computed_overlay_stale_clear_range_effects_for_test: u64,
1046 #[cfg(test)]
1047 computed_overlay_stale_clear_offset_attempts_for_test: u64,
1048 #[cfg(test)]
1049 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64,
1050 has_edited: bool,
1052 overlay_compactions: u64,
1054
1055 computed_overlay_bytes_estimate: usize,
1057 computed_overlay_mirroring_disabled: bool,
1058 pub(crate) force_materialize_range_views: bool,
1061 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
1063 used_axis_bounds_cache: std::sync::RwLock<Option<UsedAxisBoundsCache>>,
1065 lookup_index_cache: LookupIndexCache,
1066 source_cache: Arc<std::sync::RwLock<SourceCache>>,
1067 source_formula_token: Arc<()>,
1069 recalc_plan_token: Arc<()>,
1071 staged_formulas: StagedFormulaMap,
1073 staged_formula_index: StagedFormulaIndex,
1075 blocked_pending_spills: Vec<(VertexId, CellRef, Region)>,
1077 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
1079 row_visibility_mask_cache: std::sync::RwLock<
1081 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
1082 >,
1083 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
1085 last_formula_ingest_report: Option<FormulaIngestReport>,
1087 formula_ingest_report_total: FormulaIngestReport,
1089 active_cancel_flag: Option<crate::engine::CancelToken>,
1091 active_evaluation_deadline: Option<Instant>,
1093
1094 action_depth: u32,
1099
1100 last_virtual_dep_telemetry: VirtualDepTelemetry,
1102 virtual_dep_fallback_activations: u64,
1103
1104 last_cycle_telemetry: CycleTelemetry,
1106
1107 next_evaluation_resource_request_id: u64,
1109 evaluation_resource_request_depth: usize,
1110 active_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1111 last_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1112 evaluation_resource_baseline: EvaluationResourceBaselineStats,
1113 evaluation_resource_request_started_at: Option<crate::instant::FzInstant>,
1114 evaluation_resource_budgets: crate::engine::EvaluationBudgets,
1115 evaluation_resource_config_diagnostic:
1116 Option<crate::engine::EvaluationResourceConfigDiagnostic>,
1117 active_resource_ledger: Option<ResourceLedger>,
1118 source_cache_footprints: Vec<std::sync::Weak<std::sync::atomic::AtomicU64>>,
1119 source_cache_accounted: u64,
1120
1121 pending_iterative_redirty: Vec<VertexId>,
1140 retained_scc_members: FxHashMap<VertexId, u64>,
1158 next_retained_scc_id: u64,
1159 retained_scc_config_fingerprint: u64,
1166 retained_scc_function_epoch_seen: u64,
1171 retained_scc_provider_revision_seen: Option<u64>,
1172 retained_scc_dirty_at_begin: Vec<(VertexId, u64)>,
1179
1180 iterative_state_values: FxHashMap<VertexId, LiteralValue>,
1193
1194 function_semantic_epoch_seen: u64,
1196 function_provider_revision_seen: Option<u64>,
1198
1199 #[cfg(feature = "tracing")]
1200 trace_evaluation_counters: TraceEvaluationCounters,
1201 #[cfg(test)]
1202 evaluation_request_begin_count_for_test: u64,
1203 #[cfg(any(test, feature = "test-support"))]
1204 before_prepared_span_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1205 #[cfg(test)]
1206 before_target_preparation_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1207 #[cfg(test)]
1208 before_target_planning_snapshot_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1209 #[cfg(test)]
1210 inject_target_semantic_stale_once_for_test: bool,
1211 #[cfg(test)]
1212 force_virtual_dep_changes_remaining_for_test: usize,
1213 freshness: freshness::Freshness,
1215 #[cfg(test)]
1216 fail_evaluation_commit_preflight_once_for_test: bool,
1217 #[cfg(test)]
1218 target_preparation_fault_for_test:
1219 Option<crate::engine::target_preparation::TargetPreparationFault>,
1220 #[cfg(test)]
1221 force_non_cycle_schedule_fallback_for_test: bool,
1222 #[cfg(test)]
1223 before_legacy_fallback_final_provider_sample_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1224 #[cfg(test)]
1225 after_eager_proposal_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1226}
1227
1228impl<R: EvaluationContext> Engine<R> {
1231 pub(crate) fn ingest_pipeline(&mut self) -> crate::engine::ingest_pipeline::IngestPipeline<'_> {
1232 self.graph.ingest_pipeline(&self.resolver)
1233 }
1234}
1235
1236pub struct EngineAction<'a, R>
1237where
1238 R: EvaluationContext,
1239{
1240 engine: &'a mut Engine<R>,
1241 name: String,
1242 capture: Option<*mut MutationCapture>,
1245 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
1248 atomic_policy: bool,
1250}
1251
1252impl<'a, R> EngineAction<'a, R>
1253where
1254 R: EvaluationContext,
1255{
1256 #[inline]
1257 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
1258 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1259 let coord = crate::reference::Coord::from_excel(row, col, true, true);
1260 crate::reference::CellRef::new(sheet_id, coord)
1261 }
1262
1263 #[inline]
1264 pub fn name(&self) -> &str {
1265 &self.name
1266 }
1267
1268 #[inline]
1269 pub fn set_cell_value(
1270 &mut self,
1271 sheet: &str,
1272 row: u32,
1273 col: u32,
1274 value: LiteralValue,
1275 ) -> Result<(), crate::engine::EditorError> {
1276 if self.capture.is_some() {
1277 let old_value = self.engine.read_cell_value(sheet, row, col);
1278 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1279 let addr = self.addr_for(sheet, row, col);
1280 let Some(capture_ptr) = self.capture else {
1281 return Err(crate::engine::EditorError::TransactionFailed {
1282 reason: "action_with_logger: missing mutation capture".to_string(),
1283 });
1284 };
1285
1286 let old_comp = if self.arrow_undo.is_some() {
1289 self.engine.read_computed_overlay_cell(sheet, row, col)
1290 } else {
1291 None
1292 };
1293
1294 if self.engine.graph_admission_enabled() {
1295 let admission =
1296 self.engine
1297 .graph
1298 .preview_value_mutation(addr.sheet_id, row, col)?;
1299 self.engine.preflight_graph_admission(admission)?;
1300 }
1301 if old_formula.is_none() {
1302 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1303 }
1304
1305 let delta_old_sem = if old_formula.is_some() {
1306 None
1307 } else {
1308 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1309 };
1310
1311 let start_len = unsafe { (&*capture_ptr).len() };
1312
1313 let capture = unsafe { &mut *capture_ptr };
1315 self.engine.edit_with_capture(capture, |editor| {
1316 editor.set_cell_value_with_old_state(
1317 addr,
1318 value.clone(),
1319 old_value.clone(),
1320 old_formula.clone(),
1321 );
1322 })?;
1323 self.engine.record_structural_change(StructuralScope::Cell {
1324 sheet: addr.sheet_id,
1325 row: addr.coord.row(),
1326 col: addr.coord.col(),
1327 });
1328
1329 if let Some(undo_ptr) = self.arrow_undo {
1330 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1332 let undo = unsafe { &mut *undo_ptr };
1333 self.engine
1334 .record_spill_ops_into_arrow_undo(undo, new_events);
1335
1336 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
1338 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1339 let row0 = row.saturating_sub(1);
1340 let col0 = col.saturating_sub(1);
1341 let delta_new_sem = Some(value.clone());
1342 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
1343 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
1344 }
1345 Ok(())
1346 } else {
1347 self.engine
1348 .set_cell_value(sheet, row, col, value)
1349 .map_err(crate::engine::EditorError::from)
1350 }
1351 }
1352
1353 #[inline]
1354 pub fn set_cell_formula(
1355 &mut self,
1356 sheet: &str,
1357 row: u32,
1358 col: u32,
1359 ast: ASTNode,
1360 ) -> Result<(), crate::engine::EditorError> {
1361 if self.capture.is_some() {
1362 let old_value = self.engine.read_cell_value(sheet, row, col);
1363 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1364 let addr = self.addr_for(sheet, row, col);
1365 let Some(capture_ptr) = self.capture else {
1366 return Err(crate::engine::EditorError::TransactionFailed {
1367 reason: "action_with_logger: missing mutation capture".to_string(),
1368 });
1369 };
1370
1371 let admitted_formula = if self.engine.graph_admission_enabled() {
1372 let placement =
1373 CellRef::new(addr.sheet_id, Coord::from_excel(row, col, true, true));
1374 let ingested = self.engine.ingest_pipeline().ingest_formula(
1375 FormulaAstInput::Tree(ast.clone()),
1376 placement,
1377 None,
1378 )?;
1379 let admission = self.engine.graph.preview_formula_mutations(&[(
1380 addr.sheet_id,
1381 row,
1382 col,
1383 ingested.dep_plan.clone(),
1384 )])?;
1385 self.engine.preflight_graph_admission(admission)?;
1386 Some((ingested.ast_id, ingested.dep_plan))
1387 } else {
1388 None
1389 };
1390 if old_formula.is_none() {
1391 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1392 }
1393 let delta_old = if self.arrow_undo.is_some() {
1394 if old_formula.is_some() {
1395 None
1396 } else {
1397 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1398 }
1399 } else {
1400 None
1401 };
1402 let start_len = unsafe { (&*capture_ptr).len() };
1403
1404 let capture = unsafe { &mut *capture_ptr };
1406 self.engine.edit_with_capture(capture, |editor| {
1407 if let Some((ast_id, plan)) = admitted_formula {
1408 editor.set_cell_formula_with_prepared_plan(
1409 addr,
1410 ast.clone(),
1411 old_value,
1412 old_formula,
1413 ast_id,
1414 plan,
1415 );
1416 } else {
1417 editor.set_cell_formula_with_old_state(
1418 addr,
1419 ast.clone(),
1420 old_value,
1421 old_formula,
1422 );
1423 }
1424 })?;
1425 self.engine.record_structural_change(StructuralScope::Cell {
1426 sheet: addr.sheet_id,
1427 row: addr.coord.row(),
1428 col: addr.coord.col(),
1429 });
1430
1431 if let Some(undo_ptr) = self.arrow_undo {
1432 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1433 let undo = unsafe { &mut *undo_ptr };
1434 self.engine
1435 .record_spill_ops_into_arrow_undo(undo, new_events);
1436 let delta_new: Option<LiteralValue> = None;
1437 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1438 let row0 = row.saturating_sub(1);
1439 let col0 = col.saturating_sub(1);
1440 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
1441 }
1442 Ok(())
1443 } else {
1444 self.engine
1445 .set_cell_formula(sheet, row, col, ast)
1446 .map_err(crate::engine::EditorError::from)
1447 }
1448 }
1449
1450 #[inline]
1451 pub fn set_row_hidden(
1452 &mut self,
1453 sheet: &str,
1454 row_1based: u32,
1455 hidden: bool,
1456 source: RowVisibilitySource,
1457 ) -> Result<(), crate::engine::EditorError> {
1458 if self.capture.is_some() {
1459 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1460 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
1461 let old_hidden = self
1462 .engine
1463 .row_visibility
1464 .get(&sheet_id)
1465 .map(|state| state.is_row_hidden(row0, Some(source)))
1466 .unwrap_or(false);
1467 if old_hidden == hidden {
1468 return Ok(());
1469 }
1470
1471 let _ = self
1472 .engine
1473 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1474
1475 let Some(capture_ptr) = self.capture else {
1476 return Err(crate::engine::EditorError::TransactionFailed {
1477 reason: "action_with_logger: missing mutation capture".to_string(),
1478 });
1479 };
1480 unsafe { &mut *capture_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
1481 sheet_id,
1482 row0,
1483 source,
1484 old_hidden,
1485 new_hidden: hidden,
1486 });
1487
1488 Ok(())
1489 } else {
1490 self.engine
1491 .set_row_hidden(sheet, row_1based, hidden, source)
1492 }
1493 }
1494
1495 #[inline]
1496 pub fn set_rows_hidden(
1497 &mut self,
1498 sheet: &str,
1499 start_row_1based: u32,
1500 end_row_1based: u32,
1501 hidden: bool,
1502 source: RowVisibilitySource,
1503 ) -> Result<(), crate::engine::EditorError> {
1504 if self.capture.is_some() {
1505 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1506 let (start_row0, end_row0) =
1507 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
1508
1509 let Some(capture_ptr) = self.capture else {
1510 return Err(crate::engine::EditorError::TransactionFailed {
1511 reason: "action_with_logger: missing mutation capture".to_string(),
1512 });
1513 };
1514 let capture = unsafe { &mut *capture_ptr };
1515
1516 for row0 in start_row0..=end_row0 {
1517 let old_hidden = self
1518 .engine
1519 .row_visibility
1520 .get(&sheet_id)
1521 .map(|state| state.is_row_hidden(row0, Some(source)))
1522 .unwrap_or(false);
1523 if old_hidden == hidden {
1524 continue;
1525 }
1526
1527 let _ = self
1528 .engine
1529 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1530
1531 capture.record(crate::engine::ChangeEvent::SetRowVisibility {
1532 sheet_id,
1533 row0,
1534 source,
1535 old_hidden,
1536 new_hidden: hidden,
1537 });
1538 }
1539
1540 Ok(())
1541 } else {
1542 self.engine
1543 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
1544 }
1545 }
1546
1547 #[inline]
1548 pub fn insert_rows(
1549 &mut self,
1550 sheet: &str,
1551 before: u32,
1552 count: u32,
1553 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1554 if count == 0 {
1555 return Ok(crate::engine::ShiftSummary::default());
1556 }
1557 if self.capture.is_some() {
1558 let Some(capture_ptr) = self.capture else {
1559 return Err(crate::engine::EditorError::TransactionFailed {
1560 reason: "action_atomic: missing mutation capture".to_string(),
1561 });
1562 };
1563
1564 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1565 let before0 = before.saturating_sub(1);
1566 let occupancy = self.engine.structural_row_occupancy(sheet, sheet_id);
1567 let affected_region = Engine::<R>::structural_row_region(sheet_id, before0);
1568
1569 let summary = {
1571 let capture = unsafe { &mut *capture_ptr };
1572 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1573 Ok(crate::engine::ShiftSummary::default());
1574 self.engine.edit_with_capture(capture, |editor| {
1575 editor.set_structural_occupancy(occupancy);
1576 out = editor.insert_rows(sheet_id, before0, count);
1577 })?;
1578 out?
1579 };
1580
1581 self.engine.ensure_arrow_sheet(sheet);
1583 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1584 asheet.insert_rows(before0 as usize, count as usize);
1585 }
1586 self.engine
1587 .purge_derived_formats_after_row(sheet_id, before0);
1588 self.engine
1589 .shift_row_visibility_insert(sheet_id, before0, count);
1590 self.engine.mark_moved_formula_vertices_dirty(&summary);
1591 self.engine
1592 .clear_computed_overlay_after_row(sheet, before0 as usize);
1593 self.engine
1594 .record_structural_change(StructuralScope::Region(affected_region));
1595 if let Some(undo_ptr) = self.arrow_undo {
1596 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
1597 }
1598 Ok(summary)
1599 } else {
1600 self.engine.insert_rows(sheet, before, count)
1601 }
1602 }
1603
1604 #[inline]
1605 pub fn delete_rows(
1606 &mut self,
1607 sheet: &str,
1608 start: u32,
1609 count: u32,
1610 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1611 if count == 0 {
1612 return Ok(crate::engine::ShiftSummary::default());
1613 }
1614 if self.atomic_policy {
1615 return Err(crate::engine::EditorError::TransactionUnsupported {
1616 reason:
1617 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
1618 .to_string(),
1619 });
1620 }
1621 self.engine.delete_rows(sheet, start, count)
1622 }
1623
1624 #[inline]
1625 pub fn insert_columns(
1626 &mut self,
1627 sheet: &str,
1628 before: u32,
1629 count: u32,
1630 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1631 if count == 0 {
1632 return Ok(crate::engine::ShiftSummary::default());
1633 }
1634 if self.capture.is_some() {
1635 let Some(capture_ptr) = self.capture else {
1636 return Err(crate::engine::EditorError::TransactionFailed {
1637 reason: "action_atomic: missing mutation capture".to_string(),
1638 });
1639 };
1640
1641 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1642 let before0 = before.saturating_sub(1);
1643 let occupancy = self.engine.structural_column_occupancy();
1644 let affected_region = Engine::<R>::structural_col_region(sheet_id, before0);
1645
1646 let summary = {
1647 let capture = unsafe { &mut *capture_ptr };
1648 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1649 Ok(crate::engine::ShiftSummary::default());
1650 self.engine.edit_with_capture(capture, |editor| {
1651 editor.set_structural_occupancy(occupancy);
1652 out = editor.insert_columns(sheet_id, before0, count);
1653 })?;
1654 out?
1655 };
1656
1657 self.engine.ensure_arrow_sheet(sheet);
1658 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1659 asheet.insert_columns(before0 as usize, count as usize);
1660 }
1661 self.engine
1662 .purge_derived_formats_after_col(sheet_id, before0);
1663 self.engine.mark_moved_formula_vertices_dirty(&summary);
1664 self.engine
1665 .clear_computed_overlay_after_col(sheet, before0 as usize);
1666 self.engine
1667 .record_structural_change(StructuralScope::Region(affected_region));
1668 if let Some(undo_ptr) = self.arrow_undo {
1669 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
1670 }
1671 Ok(summary)
1672 } else {
1673 self.engine.insert_columns(sheet, before, count)
1674 }
1675 }
1676
1677 #[inline]
1678 pub fn delete_columns(
1679 &mut self,
1680 sheet: &str,
1681 start: u32,
1682 count: u32,
1683 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1684 if count == 0 {
1685 return Ok(crate::engine::ShiftSummary::default());
1686 }
1687 if self.atomic_policy {
1688 return Err(crate::engine::EditorError::TransactionUnsupported {
1689 reason:
1690 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
1691 .to_string(),
1692 });
1693 }
1694 self.engine.delete_columns(sheet, start, count)
1695 }
1696
1697 #[inline]
1702 pub fn action<T>(
1703 &mut self,
1704 name: impl AsRef<str>,
1705 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1706 ) -> Result<T, crate::engine::EditorError> {
1707 self.engine.action(name, f)
1708 }
1709}
1710
1711struct ActionDepthGuard<'a, R> {
1712 engine: *mut Engine<R>,
1713 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
1714}
1715
1716impl<'a, R> Drop for ActionDepthGuard<'a, R> {
1717 fn drop(&mut self) {
1718 unsafe {
1721 let e = &mut *self.engine;
1722 e.action_depth = e.action_depth.saturating_sub(1);
1723 }
1724 }
1725}
1726
1727#[derive(Default)]
1728struct SourceCache {
1729 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
1730 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
1731}
1732
1733#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1734struct VisibilityMaskCacheKey {
1735 sheet_id: SheetId,
1736 start_row0: u32,
1737 end_row0: u32,
1738 mode: VisibilityMaskMode,
1739 version: u64,
1740}
1741
1742#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1743enum StructuralScope {
1744 Cell { sheet: SheetId, row: u32, col: u32 },
1745 Region(Region),
1746 Sheet(SheetId),
1747 RemovedSheet(SheetId),
1748 OpaqueGlobal,
1749 AllSheets,
1750}
1751
1752#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1753enum LoggedEditImpact {
1754 NoOp,
1755 DataOnly,
1756 Topology,
1757}
1758
1759#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1760enum LoggedEditDirection {
1761 Original,
1762 InverseReplay,
1763 ForwardReplay,
1764}
1765
1766#[derive(Clone, Copy)]
1767struct InvalidationBaseline {
1768 snapshot_id: u64,
1769 topology_epoch: u64,
1770}
1771
1772struct SourceCacheSession {
1773 cache: Arc<std::sync::RwLock<SourceCache>>,
1774}
1775
1776impl Drop for SourceCacheSession {
1777 fn drop(&mut self) {
1778 if let Ok(mut g) = self.cache.write() {
1779 *g = SourceCache::default();
1780 }
1781 }
1782}
1783
1784#[derive(Debug)]
1785#[non_exhaustive]
1786pub struct EvalResult {
1787 pub computed_vertices: usize,
1788 pub cycle_errors: usize,
1789 pub elapsed: std::time::Duration,
1790}
1791
1792#[derive(Clone, Debug, PartialEq, Eq)]
1793#[non_exhaustive]
1794pub struct TableMetadata {
1795 pub name: String,
1796 pub sheet: String,
1797 pub start_row: u32,
1798 pub start_col: u32,
1799 pub end_row: u32,
1800 pub end_col: u32,
1801 pub header_row: bool,
1802 pub headers: Vec<String>,
1803 pub totals_row: bool,
1804}
1805
1806#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1814#[non_exhaustive]
1815pub struct EngineBaselineStats {
1816 pub graph_vertex_count: usize,
1817 pub graph_formula_vertex_count: usize,
1818 pub graph_edge_count: usize,
1819 pub dirty_vertex_count: usize,
1820 pub evaluation_vertex_count: usize,
1821 pub formula_ast_root_count: usize,
1822 pub formula_ast_node_count: usize,
1823 pub staged_formula_count: usize,
1824 pub formula_plane_active_span_count: usize,
1825 pub formula_plane_producer_result_entries: usize,
1826 pub formula_plane_consumer_read_entries: usize,
1827 pub formula_plane_mixed_topology_cache_builds: u64,
1828 pub formula_plane_mixed_topology_cache_hits: u64,
1829 pub formula_plane_mixed_topology_cache_overflows: u64,
1830 pub formula_plane_dirty_pending_events: usize,
1831 pub formula_plane_dirty_region_events_recorded: u64,
1832 pub formula_plane_dirty_span_region_events_recorded: u64,
1833 pub formula_plane_dirty_whole_span_seeds_recorded: u64,
1834 pub formula_plane_dirty_global_invalidations: u64,
1835 pub formula_plane_structural_span_candidates: u64,
1836 pub formula_plane_cycle_member_span_demotions: u64,
1837 pub formula_plane_array_result_span_demotions: u64,
1838 pub retained_scc_members: usize,
1841}
1842
1843#[derive(Debug, Clone, Default)]
1844#[non_exhaustive]
1845pub struct VirtualDepTelemetry {
1846 pub candidate_vertices_total: usize,
1847 pub vdeps_vertices_total: usize,
1848 pub vdeps_edges_total: usize,
1849 pub builder_elapsed_ms_total: u128,
1850 pub schedule_virtual_passes: usize,
1851 pub schedule_static_passes: usize,
1852 pub schedule_cache_hits: usize,
1853 pub schedule_cache_misses: usize,
1854 pub reused_schedule_vertices_total: usize,
1855 pub replan_iterations: usize,
1856 pub changed_vdeps_total: usize,
1857 pub bailout_reason: Option<&'static str>,
1858 pub fallback_mode_activations: u64,
1859}
1860
1861#[derive(Debug, Clone, Default, PartialEq)]
1870#[non_exhaustive]
1871pub struct CycleTelemetry {
1872 pub static_sccs: usize,
1874 pub phantom_sccs: usize,
1876 pub live_cycles_witnessed: usize,
1878 pub circ_cells_stamped: usize,
1880 pub settle_passes_total: usize,
1883 pub max_passes_single_scc: usize,
1885 pub iterated_sccs: usize,
1888 pub converged_sccs: usize,
1891 pub capped_sccs: usize,
1897 pub max_abs_delta_at_stop: f64,
1901 pub nan_converged: usize,
1904 pub reused_sccs: usize,
1909 pub reused_scc_members: usize,
1911 pub elapsed_ms: u128,
1913}
1914
1915#[derive(Debug, Clone, Copy)]
1916struct ScheduleBuildMeta {
1917 candidate_vertices: usize,
1918 vdeps_vertices: usize,
1919 vdeps_edges: usize,
1920 builder_elapsed_ms: u128,
1921 used_virtual_schedule: bool,
1922 schedule_cache_hit: bool,
1923 schedule_cache_eligible: bool,
1924}
1925
1926#[cfg(any(test, feature = "benchmark_internal"))]
1927#[doc(hidden)]
1928#[derive(Debug, Clone, Default)]
1929pub struct RecalcReuseProbe {
1930 pub schedule_requests: usize,
1931 pub schedule_cache_hits: usize,
1932 pub schedule_cache_misses: usize,
1933 pub schedule_cache_ineligible: usize,
1934 pub schedule_builds: usize,
1935 pub schedule_base_restrictions: usize,
1937 pub schedule_shared_handles: usize,
1938 pub schedule_retained_bytes: usize,
1939 pub legacy_target_requests: usize,
1940 pub target_schedule_builds: usize,
1941 pub demand_builds: usize,
1942 pub demand_vertices: usize,
1943 pub demand_clean_formulas: usize,
1944 pub demand_explicit_edges: usize,
1945 pub demand_virtual_builder_calls: usize,
1946}
1947
1948#[cfg(any(test, feature = "benchmark_internal"))]
1949fn schedule_probe_retained_bytes(schedule: &crate::engine::Schedule) -> usize {
1950 fn vector_bytes<T>(values: &Vec<T>) -> usize {
1951 values.capacity() * std::mem::size_of::<T>()
1952 }
1953
1954 [
1955 vector_bytes(&schedule.units),
1956 vector_bytes(&schedule.layers),
1957 vector_bytes(&schedule.cycles),
1958 ]
1959 .into_iter()
1960 .chain(
1961 schedule
1962 .layers
1963 .iter()
1964 .map(|layer| vector_bytes(&layer.vertices)),
1965 )
1966 .chain(schedule.cycles.iter().map(vector_bytes))
1967 .sum()
1968}
1969
1970#[derive(Debug, Clone)]
1971struct CachedScheduleEntry {
1972 topology_epoch: u64,
1973 authority_revision: (u64, u64),
1976 candidate_vertices: VertexIdRuns,
1979 schedule: Arc<crate::engine::scheduler::Schedule>,
1980}
1981
1982#[derive(Debug, Clone, Default)]
1985struct VertexIdRuns(Vec<(u32, u32)>);
1986
1987impl VertexIdRuns {
1988 fn from_slice(ids: &[VertexId]) -> Self {
1989 let mut runs: Vec<(u32, u32)> = Vec::new();
1990 for v in ids {
1991 match runs.last_mut() {
1992 Some((first, len)) if first.checked_add(*len) == Some(v.0) => *len += 1,
1993 _ => runs.push((v.0, 1)),
1994 }
1995 }
1996 runs.shrink_to_fit();
1997 Self(runs)
1998 }
1999
2000 fn equals(&self, ids: &[VertexId]) -> bool {
2001 let mut rest = ids;
2002 for &(first, len) in &self.0 {
2003 let len = len as usize;
2004 if rest.len() < len {
2005 return false;
2006 }
2007 let (head, tail) = rest.split_at(len);
2008 if head
2009 .iter()
2010 .enumerate()
2011 .any(|(i, v)| v.0 != first.wrapping_add(i as u32))
2012 {
2013 return false;
2014 }
2015 rest = tail;
2016 }
2017 rest.is_empty()
2018 }
2019
2020 fn heap_bytes(&self) -> usize {
2021 self.0.capacity() * std::mem::size_of::<(u32, u32)>()
2022 }
2023
2024 fn len(&self) -> usize {
2026 self.0.iter().map(|&(_, len)| len as usize).sum()
2027 }
2028}
2029
2030#[cfg(test)]
2031mod vertex_id_runs_tests {
2032 use super::{VertexId, VertexIdRuns};
2033
2034 #[test]
2035 fn runs_compare_like_the_list() {
2036 let ids = |v: &[u32]| v.iter().map(|&i| VertexId(i)).collect::<Vec<_>>();
2037 let list = ids(&[5, 6, 7, 2, 3, 9, 10, 10]);
2038 let runs = VertexIdRuns::from_slice(&list);
2039 assert_eq!(runs.0, vec![(5, 3), (2, 2), (9, 2), (10, 1)]);
2040 assert!(runs.equals(&list));
2041 assert!(!runs.equals(&list[..7]));
2042 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 11])));
2043 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 10, 11])));
2044 assert!(VertexIdRuns::from_slice(&[]).equals(&[]));
2045 assert!(!VertexIdRuns::from_slice(&[]).equals(&list));
2046 let edge = ids(&[u32::MAX - 1, u32::MAX, 0]);
2047 assert!(VertexIdRuns::from_slice(&edge).equals(&edge));
2048 }
2049}
2050
2051enum EvaluationSchedule {
2053 Owned(crate::engine::scheduler::Schedule),
2054 Shared(Arc<crate::engine::scheduler::Schedule>),
2055}
2056
2057impl std::ops::Deref for EvaluationSchedule {
2058 type Target = crate::engine::scheduler::Schedule;
2059
2060 fn deref(&self) -> &Self::Target {
2061 match self {
2062 Self::Owned(schedule) => schedule,
2063 Self::Shared(schedule) => schedule,
2064 }
2065 }
2066}
2067
2068type ScheduleBuildOutput = (
2069 crate::engine::scheduler::Schedule,
2070 FxHashMap<VertexId, Vec<VertexId>>,
2071 ScheduleBuildMeta,
2072);
2073
2074type EvaluationScheduleBuildOutput = (
2075 EvaluationSchedule,
2076 FxHashMap<VertexId, Vec<VertexId>>,
2077 ScheduleBuildMeta,
2078);
2079
2080#[derive(Debug)]
2082pub struct RecalcPlan {
2083 key: RecalcPlanKey,
2084 kind: RecalcPlanKind,
2085}
2086
2087#[derive(Debug)]
2088struct RecalcPlanKey {
2089 engine_token: Arc<()>,
2090 revisions: PlanningRevisionSnapshot,
2091}
2092
2093#[derive(Clone, Debug, PartialEq, Eq)]
2094struct PlanningRevisionSnapshot {
2095 engine_topology_epoch: u64,
2096 graph_topology_revision: u64,
2097 staged: u64,
2098 symbols: u64,
2099 semantic: u64,
2100 provider: Option<u64>,
2101 deterministic_mode: crate::engine::DeterministicMode,
2102 budgets: crate::engine::EvaluationBudgets,
2103}
2104
2105#[derive(Debug)]
2106enum RecalcPlanKind {
2107 CompatibilityFull {
2108 schedule: crate::engine::Schedule,
2109 has_dynamic_refs: bool,
2110 },
2111 Target {
2112 targets: Vec<crate::engine::EvaluationTarget>,
2113 scope: crate::engine::PrepareScope,
2114 topology: RecalcTopology,
2115 dynamic_policy: DynamicPlanPolicy,
2116 },
2117}
2118
2119#[derive(Debug)]
2120enum RecalcTopology {
2121 RunLocalRecipe,
2122 Workbook,
2123}
2124
2125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2126enum DynamicPlanPolicy {
2127 BoundedTargetReplan,
2128}
2129
2130impl RecalcPlan {
2131 pub fn layer_count(&self) -> usize {
2134 match &self.kind {
2135 RecalcPlanKind::CompatibilityFull { schedule, .. } => schedule.layers.len(),
2136 RecalcPlanKind::Target { .. } => 0,
2137 }
2138 }
2139
2140 pub fn has_dynamic_refs(&self) -> bool {
2141 match &self.kind {
2142 RecalcPlanKind::CompatibilityFull {
2143 has_dynamic_refs, ..
2144 } => *has_dynamic_refs,
2145 RecalcPlanKind::Target { .. } => false,
2146 }
2147 }
2148
2149 #[cfg(test)]
2150 pub(crate) fn force_stale_reasons_for_test(
2151 &mut self,
2152 reasons: &[formualizer_common::PlanStaleReason],
2153 ) {
2154 use formualizer_common::PlanStaleReason;
2155 for reason in reasons {
2156 match reason {
2157 PlanStaleReason::Engine => {
2158 self.key.engine_token = Arc::new(());
2159 }
2160 PlanStaleReason::Provider => {
2161 self.key.revisions.provider = Some(
2162 self.key
2163 .revisions
2164 .provider
2165 .unwrap_or_default()
2166 .wrapping_add(1),
2167 );
2168 }
2169 PlanStaleReason::Semantic => {
2170 self.key.revisions.semantic = self.key.revisions.semantic.wrapping_add(1);
2171 }
2172 PlanStaleReason::Budget => {
2173 let current = self.key.revisions.budgets.work.max_work_units;
2174 self.key.revisions.budgets.work.max_work_units =
2175 Some(current.unwrap_or_default().wrapping_add(1));
2176 }
2177 PlanStaleReason::Staged => {
2178 self.key.revisions.staged = self.key.revisions.staged.wrapping_add(1);
2179 }
2180 PlanStaleReason::Symbols => {
2181 self.key.revisions.symbols = self.key.revisions.symbols.wrapping_add(1);
2182 }
2183 PlanStaleReason::Graph => {
2184 self.key.revisions.graph_topology_revision =
2185 self.key.revisions.graph_topology_revision.wrapping_add(1);
2186 }
2187 _ => {}
2188 }
2189 }
2190 }
2191}
2192
2193#[cfg(any(test, feature = "test-support"))]
2194pub(crate) mod criteria_mask_test_hooks {
2195 use std::cell::Cell;
2196
2197 thread_local! {
2198 static MASK_CALLS_ROWS: Cell<(usize, usize)> = const { Cell::new((0, 0)) };
2199 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
2200 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
2201 }
2202
2203 pub(crate) fn take_mask_work() -> (usize, usize) {
2204 MASK_CALLS_ROWS.with(|c| c.replace((0, 0)))
2205 }
2206
2207 pub(crate) fn note_mask(rows: usize) {
2208 MASK_CALLS_ROWS.with(|c| {
2209 let (calls, work) = c.get();
2210 c.set((calls + 1, work + rows));
2211 });
2212 }
2213
2214 pub fn reset_text_segment_counters() {
2215 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
2216 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
2217 }
2218
2219 pub fn text_segment_counters() -> (usize, usize) {
2220 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
2221 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
2222 (a, b)
2223 }
2224
2225 pub(crate) fn inc_total() {
2226 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
2227 }
2228 pub(crate) fn inc_all_null() {
2229 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
2230 }
2231}
2232
2233#[cfg(test)]
2234pub(crate) mod visibility_mask_test_hooks {
2235 use std::cell::Cell;
2236
2237 thread_local! {
2238 static HITS: Cell<usize> = const { Cell::new(0) };
2239 static MISSES: Cell<usize> = const { Cell::new(0) };
2240 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
2241 }
2242
2243 pub fn reset() {
2244 HITS.with(|c| c.set(0));
2245 MISSES.with(|c| c.set(0));
2246 EVICTIONS.with(|c| c.set(0));
2247 }
2248
2249 pub fn counters() -> (usize, usize, usize) {
2250 let hits = HITS.with(|c| c.get());
2251 let misses = MISSES.with(|c| c.get());
2252 let evictions = EVICTIONS.with(|c| c.get());
2253 (hits, misses, evictions)
2254 }
2255
2256 pub(crate) fn inc_hit() {
2257 HITS.with(|c| c.set(c.get() + 1));
2258 }
2259
2260 pub(crate) fn inc_miss() {
2261 MISSES.with(|c| c.set(c.get() + 1));
2262 }
2263
2264 pub(crate) fn inc_eviction() {
2265 EVICTIONS.with(|c| c.set(c.get() + 1));
2266 }
2267}
2268
2269fn is_numeric_text_equality(pred: &crate::args::CriteriaPredicate) -> bool {
2270 match pred {
2271 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(text)) => {
2272 text.trim().parse::<f64>().is_ok_and(f64::is_finite)
2273 }
2274 _ => false,
2275 }
2276}
2277
2278fn compute_criteria_mask(
2279 view: &RangeView<'_>,
2280 col_in_view: usize,
2281 pred: &crate::args::CriteriaPredicate,
2282) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
2283 use crate::compute_prelude::{boolean, cmp, concat_arrays};
2284 use arrow::compute::kernels::comparison::{ilike, nilike};
2285 use arrow_array::{
2286 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
2287 };
2288
2289 fn apply_numeric_pred(
2291 chunk: &Float64Array,
2292 pred: &crate::args::CriteriaPredicate,
2293 ) -> Option<BooleanArray> {
2294 match pred {
2295 crate::args::CriteriaPredicate::Gt(n) => {
2296 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
2297 }
2298 crate::args::CriteriaPredicate::Ge(n) => {
2299 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2300 }
2301 crate::args::CriteriaPredicate::Lt(n) => {
2302 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
2303 }
2304 crate::args::CriteriaPredicate::Le(n) => {
2305 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2306 }
2307 crate::args::CriteriaPredicate::Eq(v) => match v {
2308 formualizer_common::LiteralValue::Number(x) => {
2309 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
2310 }
2311 formualizer_common::LiteralValue::Int(i) => {
2312 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2313 }
2314 _ => None,
2315 },
2316 crate::args::CriteriaPredicate::Ne(v) => match v {
2317 formualizer_common::LiteralValue::Number(x) => {
2318 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
2319 }
2320 formualizer_common::LiteralValue::Int(i) => {
2321 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2322 }
2323 _ => None,
2324 },
2325 _ => None,
2326 }
2327 }
2328
2329 let is_numeric_pred = matches!(
2331 pred,
2332 crate::args::CriteriaPredicate::Gt(_)
2333 | crate::args::CriteriaPredicate::Ge(_)
2334 | crate::args::CriteriaPredicate::Lt(_)
2335 | crate::args::CriteriaPredicate::Le(_)
2336 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
2337 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
2338 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
2339 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
2340 );
2341
2342 if is_numeric_pred {
2346 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2347 for res in view.numbers_slices() {
2348 let (_rs, _rl, cols_seg) = res.ok()?;
2349 if col_in_view < cols_seg.len() {
2350 let chunk = cols_seg[col_in_view].as_ref();
2351 let mask = apply_numeric_pred(chunk, pred)?;
2352 bool_parts.push(mask);
2353 }
2354 }
2355
2356 if bool_parts.is_empty() {
2357 return None;
2358 } else if bool_parts.len() == 1 {
2359 return Some(std::sync::Arc::new(bool_parts.remove(0)));
2360 } else {
2361 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2363 .iter()
2364 .map(|a| a as &dyn arrow_array::Array)
2365 .collect();
2366 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2367 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2368 return Some(std::sync::Arc::new(ba));
2369 }
2370 }
2371
2372 if is_numeric_text_equality(pred)
2376 || matches!(pred, crate::args::CriteriaPredicate::TextLike { .. })
2377 {
2378 for tags in view.type_tags_slices() {
2379 let (_, _, cols) = tags.ok()?;
2380 let tags = cols.get(col_in_view)?;
2381 if tags.values().iter().any(|tag| {
2382 *tag == crate::arrow_store::TypeTag::Empty as u8
2383 || *tag == crate::arrow_store::TypeTag::Number as u8
2384 || *tag == crate::arrow_store::TypeTag::Boolean as u8
2385 }) {
2386 let mut mask = BooleanBuilder::new();
2387 for chunk in view.iter_row_chunks() {
2388 let chunk = chunk.ok()?;
2389 for row in chunk.row_start..chunk.row_start + chunk.row_len {
2390 mask.append_value(crate::builtins::criteria_match(
2391 pred,
2392 &view.get_cell(row, col_in_view),
2393 ));
2394 }
2395 }
2396 return Some(std::sync::Arc::new(mask.finish()));
2397 }
2398 }
2399 }
2400
2401 if matches!(pred, crate::args::CriteriaPredicate::TextLike { pattern, .. }
2405 if pattern.contains(['~', '%', '_', '\\']))
2406 {
2407 return None;
2408 }
2409
2410 let (text_kind, text_pat, empty_special) = match pred {
2413 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
2414 (0u8, t.to_lowercase(), t.is_empty())
2415 }
2416 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
2417 (1u8, t.to_lowercase(), false)
2418 }
2419 crate::args::CriteriaPredicate::TextLike {
2420 pattern,
2421 case_insensitive,
2422 } => {
2423 let p = if *case_insensitive {
2424 pattern.to_lowercase()
2425 } else {
2426 pattern.clone()
2427 };
2428 (2u8, p.replace('*', "%").replace('?', "_"), false)
2429 }
2430 _ => return None,
2431 };
2432
2433 let text_pat_is_empty = text_pat.is_empty();
2434 let ne_matches_blank = text_kind == 1 && !text_pat_is_empty;
2435 let pat = StringArray::new_scalar(text_pat);
2436 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2437
2438 let mut tag_slices = view.type_tags_slices();
2439 for res in view.iter_row_chunks() {
2440 let cs = res.ok()?;
2441 if cs.row_len == 0 {
2442 continue;
2443 }
2444 #[cfg(test)]
2445 criteria_mask_test_hooks::inc_total();
2446
2447 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
2448 if col_in_view >= slices.len() {
2449 return None;
2450 }
2451
2452 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
2453 if empty_special || (text_kind == 1 && text_pat_is_empty) {
2454 let (tag_start, tag_len, tags) = tag_slices.next()?.ok()?;
2455 if tag_start != cs.row_start || tag_len != cs.row_len {
2456 return None;
2457 }
2458 let tags = tags.get(col_in_view)?;
2459 let strings = seg_opt.and_then(|a| a.as_any().downcast_ref::<StringArray>());
2463 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2464 for i in 0..cs.row_len {
2465 let blank = tags.value(i) == crate::arrow_store::TypeTag::Empty as u8
2466 || (tags.value(i) == crate::arrow_store::TypeTag::Text as u8
2467 && strings.is_some_and(|s| s.is_valid(i) && s.value(i).is_empty()));
2468 bb.append_value(if text_kind == 0 { blank } else { !blank });
2469 }
2470 #[cfg(test)]
2471 if seg_opt.is_none() {
2472 criteria_mask_test_hooks::inc_all_null();
2473 }
2474 bool_parts.push(bb.finish());
2475 continue;
2476 }
2477 let seg = match seg_opt {
2478 Some(s) => s,
2479 None => {
2480 #[cfg(test)]
2481 criteria_mask_test_hooks::inc_all_null();
2482 if (text_kind == 0 && empty_special) || ne_matches_blank {
2483 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2485 bb.append_n(cs.row_len, true);
2486 bool_parts.push(bb.finish());
2487 } else {
2488 bool_parts.push(BooleanArray::new_null(cs.row_len));
2490 }
2491 continue;
2492 }
2493 };
2494
2495 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
2496 let mut m = match text_kind {
2497 0 => ilike(seg_sa, &pat).ok()?,
2498 1 => nilike(seg_sa, &pat).ok()?,
2499 2 => ilike(seg_sa, &pat).ok()?,
2500 _ => return None,
2501 };
2502
2503 if ((text_kind == 0 && empty_special) || ne_matches_blank) && seg_sa.null_count() > 0 {
2509 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
2511 for i in 0..seg_sa.len() {
2512 bb.append_value(seg_sa.is_null(i));
2513 }
2514 let nulls = bb.finish();
2515 m = boolean::or_kleene(&m, &nulls).ok()?;
2516 }
2517
2518 bool_parts.push(m);
2519 }
2520
2521 if bool_parts.is_empty() {
2522 None
2523 } else if bool_parts.len() == 1 {
2524 Some(std::sync::Arc::new(bool_parts.remove(0)))
2525 } else {
2526 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2527 .iter()
2528 .map(|a| a as &dyn arrow_array::Array)
2529 .collect();
2530 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2531 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2532 Some(std::sync::Arc::new(ba))
2533 }
2534}
2535
2536#[derive(Debug, Clone)]
2537pub struct LayerInfo {
2538 pub vertex_count: usize,
2539 pub parallel_eligible: bool,
2540 pub sample_cells: Vec<String>, }
2542
2543#[derive(Debug, Clone)]
2544pub struct EvalPlan {
2545 pub total_vertices_to_evaluate: usize,
2546 pub layers: Vec<LayerInfo>,
2547 pub cycles_detected: usize,
2548 pub dirty_count: usize,
2549 pub volatile_count: usize,
2550 pub parallel_enabled: bool,
2551 pub estimated_parallel_layers: usize,
2552 pub target_cells: Vec<String>,
2553}
2554
2555#[inline]
2558fn plane_mode_ignored() -> bool {
2559 true
2560}
2561
2562#[cfg(feature = "test-support")]
2565#[doc(hidden)]
2566pub fn formula_plane_mode_ignored_for_test() -> bool {
2567 plane_mode_ignored()
2568}
2569
2570impl<R> Engine<R>
2571where
2572 R: EvaluationContext,
2573{
2574 pub fn new(resolver: R, config: EvalConfig) -> Self {
2581 let config = if plane_mode_ignored() {
2586 EvalConfig {
2587 formula_plane_mode: FormulaPlaneMode::Off,
2588 ..config
2589 }
2590 } else {
2591 config
2592 };
2593 if let Err(msg) = config.cycle.validate() {
2594 panic!("invalid CycleConfig: {msg}");
2595 }
2596 crate::builtins::load_builtins();
2597 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2598 &config.evaluation_budgets,
2599 config.max_vertices,
2600 config.max_memory_mb,
2601 config.max_eval_time,
2602 );
2603
2604 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2605 #[cfg(feature = "system-clock")]
2606 {
2607 Arc::new(crate::timezone::SystemClock::new(
2608 crate::timezone::TimeZoneSpec::default(),
2609 ))
2610 }
2611 #[cfg(not(feature = "system-clock"))]
2612 {
2613 Arc::new(crate::timezone::FixedClock::new(
2614 chrono::DateTime::UNIX_EPOCH,
2615 crate::timezone::TimeZoneSpec::Utc,
2616 ))
2617 }
2618 });
2619
2620 let thread_pool = if config.enable_parallel {
2622 let mut builder = ThreadPoolBuilder::new();
2623 if let Some(max_threads) = config.max_threads {
2624 builder = builder.num_threads(max_threads);
2625 }
2626
2627 match builder.build() {
2628 Ok(pool) => Some(Arc::new(pool)),
2629 Err(_) => {
2630 None
2632 }
2633 }
2634 } else {
2635 None
2636 };
2637
2638 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
2640 let function_provider_revision_seen = resolver.planning_semantic_revision();
2641 let mut engine = Self {
2642 graph: DependencyGraph::new_with_config(config.clone()),
2643 resolver,
2644 config,
2645 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
2646 clock: crate::timezone::SnapshotClock::new(clock),
2647 thread_pool,
2648 recalc_epoch: 0,
2649 snapshot_id: std::sync::atomic::AtomicU64::new(1),
2650 topology_epoch: 0,
2651 cached_static_schedule: None,
2652 recent_schedules: Vec::new(),
2653 base_schedule: None,
2654 #[cfg(any(test, feature = "benchmark_internal"))]
2655 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
2656 spill_mgr: ShimSpillManager::default(),
2657 arrow_sheets: SheetStore::default(),
2658 format_registry: crate::format::FormatRegistry::default(),
2659 derived_formats: Default::default(),
2660 #[cfg(test)]
2661 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
2662 #[cfg(test)]
2663 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
2664 #[cfg(test)]
2665 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
2666 #[cfg(test)]
2667 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
2668 #[cfg(test)]
2669 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
2670 #[cfg(test)]
2671 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
2672 #[cfg(test)]
2673 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
2674 #[cfg(test)]
2675 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
2676 compressed_at_build: None,
2677 #[cfg(test)]
2678 computed_overlay_set_explicit_entry_operations_for_test: 0,
2679 #[cfg(test)]
2680 computed_overlay_stale_clear_range_effects_for_test: 0,
2681 #[cfg(test)]
2682 computed_overlay_stale_clear_offset_attempts_for_test: 0,
2683 #[cfg(test)]
2684 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
2685 has_edited: false,
2686 overlay_compactions: 0,
2687 computed_overlay_bytes_estimate: 0,
2688 computed_overlay_mirroring_disabled: false,
2689 force_materialize_range_views: false,
2690 row_bounds_cache: std::sync::RwLock::new(None),
2691 used_axis_bounds_cache: std::sync::RwLock::new(None),
2692 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
2693 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
2694 source_formula_token: Arc::new(()),
2695 recalc_plan_token: Arc::new(()),
2696 staged_formulas: std::collections::HashMap::new(),
2697 staged_formula_index: StagedFormulaIndex::default(),
2698 blocked_pending_spills: Vec::new(),
2699 row_visibility: FxHashMap::default(),
2700 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
2701 formula_parse_diagnostics: Vec::new(),
2702 last_formula_ingest_report: None,
2703 formula_ingest_report_total: FormulaIngestReport::default(),
2704 active_cancel_flag: None,
2705 active_evaluation_deadline: None,
2706 action_depth: 0,
2707 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
2708 virtual_dep_fallback_activations: 0,
2709 last_cycle_telemetry: CycleTelemetry::default(),
2710 next_evaluation_resource_request_id: 1,
2711 evaluation_resource_request_depth: 0,
2712 active_evaluation_resource_request: None,
2713 last_evaluation_resource_request: None,
2714 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
2715 evaluation_resource_request_started_at: None,
2716 evaluation_resource_budgets: resolved_resources.budgets,
2717 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
2718 active_resource_ledger: None,
2719 source_cache_footprints: Vec::new(),
2720 source_cache_accounted: 0,
2721 pending_iterative_redirty: Vec::new(),
2722 retained_scc_members: FxHashMap::default(),
2723 next_retained_scc_id: 0,
2724 retained_scc_config_fingerprint: 0,
2725 retained_scc_function_epoch_seen: 0,
2726 retained_scc_provider_revision_seen: None,
2727 retained_scc_dirty_at_begin: Vec::new(),
2728 iterative_state_values: FxHashMap::default(),
2729 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
2730 function_provider_revision_seen,
2731 #[cfg(feature = "tracing")]
2732 trace_evaluation_counters: TraceEvaluationCounters::default(),
2733 #[cfg(test)]
2734 evaluation_request_begin_count_for_test: 0,
2735 #[cfg(any(test, feature = "test-support"))]
2736 before_prepared_span_commit_hook: None,
2737 #[cfg(test)]
2738 before_target_preparation_commit_hook: None,
2739 #[cfg(test)]
2740 before_target_planning_snapshot_hook: None,
2741 #[cfg(test)]
2742 inject_target_semantic_stale_once_for_test: false,
2743 #[cfg(test)]
2744 force_virtual_dep_changes_remaining_for_test: 0,
2745 freshness: Default::default(),
2746 #[cfg(test)]
2747 fail_evaluation_commit_preflight_once_for_test: false,
2748 #[cfg(test)]
2749 target_preparation_fault_for_test: None,
2750 #[cfg(test)]
2751 force_non_cycle_schedule_fallback_for_test: false,
2752 #[cfg(test)]
2753 before_legacy_fallback_final_provider_sample_hook: None,
2754 #[cfg(test)]
2755 after_eager_proposal_commit_hook: None,
2756 };
2757 engine.config.arrow_storage_enabled = true;
2759 engine.config.delta_overlay_enabled = true;
2760 engine.config.write_formula_overlay_enabled = true;
2761 let default_sheet = engine.graph.default_sheet_name().to_string();
2762 engine.ensure_arrow_sheet(&default_sheet);
2763 engine
2764 }
2765
2766 pub fn with_thread_pool(
2771 resolver: R,
2772 config: EvalConfig,
2773 thread_pool: Arc<rayon::ThreadPool>,
2774 ) -> Self {
2775 if let Err(msg) = config.cycle.validate() {
2776 panic!("invalid CycleConfig: {msg}");
2777 }
2778 crate::builtins::load_builtins();
2779 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2780 &config.evaluation_budgets,
2781 config.max_vertices,
2782 config.max_memory_mb,
2783 config.max_eval_time,
2784 );
2785 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2786 #[cfg(feature = "system-clock")]
2787 {
2788 Arc::new(crate::timezone::SystemClock::new(
2789 crate::timezone::TimeZoneSpec::default(),
2790 ))
2791 }
2792 #[cfg(not(feature = "system-clock"))]
2793 {
2794 Arc::new(crate::timezone::FixedClock::new(
2795 chrono::DateTime::UNIX_EPOCH,
2796 crate::timezone::TimeZoneSpec::Utc,
2797 ))
2798 }
2799 });
2800 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
2802 let function_provider_revision_seen = resolver.planning_semantic_revision();
2803 let mut engine = Self {
2804 graph: DependencyGraph::new_with_config(config.clone()),
2805 resolver,
2806 config,
2807 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
2808 clock: crate::timezone::SnapshotClock::new(clock),
2809 thread_pool: Some(thread_pool),
2810 recalc_epoch: 0,
2811 snapshot_id: std::sync::atomic::AtomicU64::new(1),
2812 topology_epoch: 0,
2813 cached_static_schedule: None,
2814 recent_schedules: Vec::new(),
2815 base_schedule: None,
2816 #[cfg(any(test, feature = "benchmark_internal"))]
2817 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
2818 spill_mgr: ShimSpillManager::default(),
2819 arrow_sheets: SheetStore::default(),
2820 format_registry: crate::format::FormatRegistry::default(),
2821 derived_formats: Default::default(),
2822 #[cfg(test)]
2823 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
2824 #[cfg(test)]
2825 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
2826 #[cfg(test)]
2827 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
2828 #[cfg(test)]
2829 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
2830 #[cfg(test)]
2831 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
2832 #[cfg(test)]
2833 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
2834 #[cfg(test)]
2835 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
2836 #[cfg(test)]
2837 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
2838 compressed_at_build: None,
2839 #[cfg(test)]
2840 computed_overlay_set_explicit_entry_operations_for_test: 0,
2841 #[cfg(test)]
2842 computed_overlay_stale_clear_range_effects_for_test: 0,
2843 #[cfg(test)]
2844 computed_overlay_stale_clear_offset_attempts_for_test: 0,
2845 #[cfg(test)]
2846 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
2847 has_edited: false,
2848 overlay_compactions: 0,
2849 computed_overlay_bytes_estimate: 0,
2850 computed_overlay_mirroring_disabled: false,
2851 force_materialize_range_views: false,
2852 row_bounds_cache: std::sync::RwLock::new(None),
2853 used_axis_bounds_cache: std::sync::RwLock::new(None),
2854 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
2855 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
2856 source_formula_token: Arc::new(()),
2857 recalc_plan_token: Arc::new(()),
2858 staged_formulas: std::collections::HashMap::new(),
2859 staged_formula_index: StagedFormulaIndex::default(),
2860 blocked_pending_spills: Vec::new(),
2861 row_visibility: FxHashMap::default(),
2862 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
2863 formula_parse_diagnostics: Vec::new(),
2864 last_formula_ingest_report: None,
2865 formula_ingest_report_total: FormulaIngestReport::default(),
2866 active_cancel_flag: None,
2867 active_evaluation_deadline: None,
2868 action_depth: 0,
2869 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
2870 virtual_dep_fallback_activations: 0,
2871 last_cycle_telemetry: CycleTelemetry::default(),
2872 next_evaluation_resource_request_id: 1,
2873 evaluation_resource_request_depth: 0,
2874 active_evaluation_resource_request: None,
2875 last_evaluation_resource_request: None,
2876 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
2877 evaluation_resource_request_started_at: None,
2878 evaluation_resource_budgets: resolved_resources.budgets,
2879 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
2880 active_resource_ledger: None,
2881 source_cache_footprints: Vec::new(),
2882 source_cache_accounted: 0,
2883 pending_iterative_redirty: Vec::new(),
2884 retained_scc_members: FxHashMap::default(),
2885 next_retained_scc_id: 0,
2886 retained_scc_config_fingerprint: 0,
2887 retained_scc_function_epoch_seen: 0,
2888 retained_scc_provider_revision_seen: None,
2889 retained_scc_dirty_at_begin: Vec::new(),
2890 iterative_state_values: FxHashMap::default(),
2891 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
2892 function_provider_revision_seen,
2893 #[cfg(feature = "tracing")]
2894 trace_evaluation_counters: TraceEvaluationCounters::default(),
2895 #[cfg(test)]
2896 evaluation_request_begin_count_for_test: 0,
2897 #[cfg(any(test, feature = "test-support"))]
2898 before_prepared_span_commit_hook: None,
2899 #[cfg(test)]
2900 before_target_preparation_commit_hook: None,
2901 #[cfg(test)]
2902 before_target_planning_snapshot_hook: None,
2903 #[cfg(test)]
2904 inject_target_semantic_stale_once_for_test: false,
2905 #[cfg(test)]
2906 force_virtual_dep_changes_remaining_for_test: 0,
2907 freshness: Default::default(),
2908 #[cfg(test)]
2909 fail_evaluation_commit_preflight_once_for_test: false,
2910 #[cfg(test)]
2911 target_preparation_fault_for_test: None,
2912 #[cfg(test)]
2913 force_non_cycle_schedule_fallback_for_test: false,
2914 #[cfg(test)]
2915 before_legacy_fallback_final_provider_sample_hook: None,
2916 #[cfg(test)]
2917 after_eager_proposal_commit_hook: None,
2918 };
2919 engine.config.arrow_storage_enabled = true;
2921 engine.config.delta_overlay_enabled = true;
2922 engine.config.write_formula_overlay_enabled = true;
2923 let default_sheet = engine.graph.default_sheet_name().to_string();
2924 engine.ensure_arrow_sheet(&default_sheet);
2925 engine
2926 }
2927
2928 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
2929 &self.workbook_load_limits
2930 }
2931
2932 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
2933 self.workbook_load_limits = limits;
2934 }
2935
2936 fn clear_source_cache(&self) {
2937 if let Ok(mut g) = self.source_cache.write() {
2938 *g = SourceCache::default();
2939 }
2940 }
2941
2942 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
2943 &self.last_virtual_dep_telemetry
2944 }
2945
2946 pub fn last_cycle_telemetry(&self) -> &CycleTelemetry {
2950 &self.last_cycle_telemetry
2951 }
2952
2953 pub fn last_evaluation_resource_request_stats(
2955 &self,
2956 ) -> Option<&EvaluationResourceRequestStats> {
2957 self.last_evaluation_resource_request.as_ref()
2958 }
2959
2960 pub fn evaluation_resource_baseline_stats(&self) -> EvaluationResourceBaselineStats {
2962 self.evaluation_resource_baseline
2963 }
2964
2965 pub fn evaluation_resource_budgets(&self) -> &crate::engine::EvaluationBudgets {
2966 &self.evaluation_resource_budgets
2967 }
2968
2969 pub fn evaluation_resource_config_diagnostic(
2971 &self,
2972 ) -> Option<&crate::engine::EvaluationResourceConfigDiagnostic> {
2973 self.evaluation_resource_config_diagnostic.as_ref()
2974 }
2975
2976 pub fn reset_evaluation_resource_telemetry(&mut self) {
2978 self.evaluation_resource_baseline = EvaluationResourceBaselineStats::default();
2979 self.last_evaluation_resource_request = None;
2980 }
2981
2982 fn reconcile_source_cache_footprints(&mut self) -> Result<(), ExcelError> {
2985 self.blocked_pending_spills.retain(|&(vertex, anchor, _)| {
2986 self.graph.vertex_exists(vertex)
2987 && self.graph.get_cell_ref(vertex) == Some(anchor)
2988 && matches!(
2989 self.graph.get_vertex_kind(vertex),
2990 VertexKind::FormulaScalar | VertexKind::FormulaArray
2991 )
2992 });
2993 if self.blocked_pending_spills.is_empty() {
2994 self.blocked_pending_spills = Vec::new();
2995 }
2996 let mut bytes = (self.blocked_pending_spills.capacity()
2997 * std::mem::size_of::<(VertexId, CellRef, Region)>()) as u64;
2998 self.source_cache_footprints.retain(|weak| {
2999 let Some(footprint) = weak.upgrade() else {
3000 return false;
3001 };
3002 bytes = bytes.saturating_add(footprint.load(std::sync::atomic::Ordering::Acquire));
3003 true
3004 });
3005 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3006 ledger
3007 .release_retained(self.source_cache_accounted)
3008 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3009 ledger.observe_retained(bytes);
3011 self.source_cache_accounted = bytes;
3012 ledger
3013 .reserve_retained(0)
3014 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3015 }
3016 Ok(())
3017 }
3018
3019 fn duration_ns(duration: std::time::Duration) -> u64 {
3020 u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
3021 }
3022
3023 fn observe_evaluation_resource_request<T>(
3024 &mut self,
3025 kind: EvaluationRequestKind,
3026 evaluate: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3027 ) -> Result<T, ExcelError> {
3028 let outermost = self.evaluation_resource_request_depth == 0;
3029 #[cfg(feature = "tracing")]
3030 if outermost {
3031 self.trace_evaluation_counters = TraceEvaluationCounters::default();
3032 }
3033 #[cfg(feature = "tracing")]
3034 let request_kind = if matches!(
3035 kind,
3036 EvaluationRequestKind::Full
3037 | EvaluationRequestKind::FullWithDelta
3038 | EvaluationRequestKind::FullCancellable
3039 | EvaluationRequestKind::FullLogged
3040 ) {
3041 "full"
3042 } else {
3043 "targeted"
3044 };
3045 #[cfg(feature = "tracing")]
3046 let _request_span = outermost.then(|| {
3047 crate::engine::trace::fz_span!(
3048 tracing::Level::INFO,
3049 "evaluate",
3050 "evaluate.request",
3051 kind = request_kind,
3052 mode = ?FormulaPlaneMode::Off
3053 )
3054 });
3055 if outermost {
3056 let request_id = self.next_evaluation_resource_request_id;
3057 self.next_evaluation_resource_request_id = request_id
3058 .checked_add(1)
3059 .expect("evaluation resource request ID exhausted");
3060 self.active_evaluation_resource_request = Some(EvaluationResourceRequestStats::new(
3061 request_id,
3062 kind,
3063 FormulaPlaneMode::Off,
3064 self.staged_formula_count(),
3065 ));
3066 self.evaluation_resource_baseline.record_started(request_id);
3067 self.evaluation_resource_request_started_at = Some(crate::instant::FzInstant::now());
3068 self.source_cache_accounted = 0;
3069 self.active_resource_ledger = Some(ResourceLedger::new(
3070 Some(request_id),
3071 self.evaluation_resource_budgets.clone(),
3072 ));
3073 }
3074 self.evaluation_resource_request_depth =
3075 self.evaluation_resource_request_depth.saturating_add(1);
3076 let result = if outermost {
3077 self.reconcile_source_cache_footprints()
3078 .and_then(|()| self.resource_checkpoint(0))
3079 .and_then(|()| evaluate(self))
3080 } else {
3081 evaluate(self)
3082 };
3083 if outermost && result.is_err() {
3084 self.freshness_abort_pass();
3085 }
3086 self.evaluation_resource_request_depth =
3087 self.evaluation_resource_request_depth.saturating_sub(1);
3088
3089 let reconciliation = if outermost {
3090 self.reconcile_source_cache_footprints()
3091 } else {
3092 Ok(())
3093 };
3094 let result = result.and_then(|value| reconciliation.map(|()| value));
3095 if outermost {
3096 let total_ns = self
3097 .evaluation_resource_request_started_at
3098 .take()
3099 .map(|start| Self::duration_ns(start.elapsed()))
3100 .unwrap_or(0);
3101 let mut stats = self
3102 .active_evaluation_resource_request
3103 .take()
3104 .expect("outer evaluation resource request has active stats");
3105 let mut ledger = self
3106 .active_resource_ledger
3107 .take()
3108 .expect("outer evaluation resource request has active ledger");
3109 ledger.release_all_scratch();
3110 stats.ledger.update(ledger.snapshot());
3111 stats.outcome = match &result {
3112 Ok(_) => EvaluationRequestOutcome::Success,
3113 Err(error) if error.kind == ExcelErrorKind::Cancelled => {
3114 EvaluationRequestOutcome::Cancelled
3115 }
3116 Err(_) => EvaluationRequestOutcome::Error,
3117 };
3118 if stats.dirty_lease == FormulaDirtyLeaseOutcome::Acquired {
3119 stats.dirty_lease = if stats.outcome == EvaluationRequestOutcome::Cancelled {
3120 FormulaDirtyLeaseOutcome::RetainedOnCancellation
3121 } else {
3122 FormulaDirtyLeaseOutcome::RetainedOnError
3123 };
3124 }
3125 stats.phases.total_ns = total_ns;
3126 let attributed = stats
3127 .phases
3128 .staged_prepare_ns
3129 .saturating_add(stats.phases.topology_ns)
3130 .saturating_add(stats.phases.materialization_ns);
3131 stats.phases.evaluation_ns = total_ns.saturating_sub(attributed);
3132 self.evaluation_resource_baseline.record_finished(&stats);
3133 self.last_evaluation_resource_request = Some(stats);
3134 crate::engine::trace::fz_event!(
3135 tracing::Level::INFO,
3136 "evaluate",
3137 "evaluate.summary",
3138 computed_vertices = self.trace_evaluation_counters.computed_vertices,
3139 cycles = self.trace_evaluation_counters.cycles,
3140 cancelled = matches!(
3141 &result,
3142 Err(error) if error.kind == ExcelErrorKind::Cancelled
3143 )
3144 );
3145 }
3146 result
3147 }
3148
3149 pub fn set_evaluation_resource_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
3150 self.evaluation_resource_budgets = budgets.clone();
3151 self.config.evaluation_budgets = budgets.clone();
3152 self.graph.set_evaluation_budgets(budgets);
3153 }
3154
3155 #[cfg(test)]
3156 pub(crate) fn set_evaluation_budgets_for_test(
3157 &mut self,
3158 budgets: crate::engine::EvaluationBudgets,
3159 ) {
3160 self.set_evaluation_resource_budgets(budgets);
3161 }
3162
3163 fn preflight_evaluation_commit_window(
3164 &mut self,
3165 bounded_writes: usize,
3166 ) -> Result<crate::instant::FzInstant, ExcelError> {
3167 #[cfg(test)]
3168 if std::mem::take(&mut self.fail_evaluation_commit_preflight_once_for_test) {
3169 return Err(crate::engine::ResourceLedgerError::Exhausted(
3170 formualizer_common::ResourceExhaustionDetail {
3171 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3172 limit: 0,
3173 observed: 1,
3174 request_id: self
3175 .active_evaluation_resource_request
3176 .as_ref()
3177 .map(|stats| stats.request_id),
3178 },
3179 )
3180 .into_excel_error());
3181 }
3182 let estimate = std::time::Duration::from_nanos(
3183 u64::try_from(bounded_writes)
3184 .unwrap_or(u64::MAX)
3185 .saturating_mul(100),
3186 );
3187 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3188 ledger
3189 .preflight_commit_window(estimate)
3190 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3191 }
3192 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3193 stats.evaluation_commit_preflight_count =
3194 stats.evaluation_commit_preflight_count.saturating_add(1);
3195 stats.evaluation_commit_estimated_ns = stats
3196 .evaluation_commit_estimated_ns
3197 .saturating_add(Self::duration_ns(estimate));
3198 }
3199 Ok(crate::instant::FzInstant::now())
3200 }
3201
3202 fn observe_evaluation_commit_window(&mut self, started: crate::instant::FzInstant) {
3203 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3204 stats.evaluation_commit_actual_ns = stats
3205 .evaluation_commit_actual_ns
3206 .saturating_add(Self::duration_ns(started.elapsed()));
3207 }
3208 }
3209
3210 fn live_cancellation_after_work(&self, message: &'static str) -> Result<(), ExcelError> {
3222 if self
3223 .active_cancel_flag
3224 .as_ref()
3225 .is_some_and(|cancel| cancel.is_cancelled())
3226 {
3227 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(message));
3228 }
3229 Ok(())
3230 }
3231
3232 fn cancellation_checkpoint(&self, message: &'static str) -> Result<(), ExcelError> {
3233 if self
3234 .active_cancel_flag
3235 .as_ref()
3236 .is_some_and(|cancel| cancel.is_cancelled())
3237 {
3238 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(message));
3239 }
3240 if self
3241 .active_evaluation_deadline
3242 .is_some_and(|deadline| Instant::now() >= deadline)
3243 {
3244 return Err(crate::engine::ResourceLedgerError::Exhausted(
3245 formualizer_common::ResourceExhaustionDetail {
3246 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3247 limit: 0,
3248 observed: 1,
3249 request_id: self
3250 .active_evaluation_resource_request
3251 .as_ref()
3252 .map(|request| request.request_id),
3253 },
3254 )
3255 .into_excel_error()
3256 .with_message(message));
3257 }
3258 Ok(())
3259 }
3260
3261 fn resource_checkpoint(&mut self, work_units: u64) -> Result<(), ExcelError> {
3262 let Some(ledger) = self.active_resource_ledger.as_mut() else {
3263 return Ok(());
3264 };
3265 ledger
3266 .charge_work(work_units)
3267 .and_then(|()| ledger.checkpoint_deadline())
3268 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3269 }
3270
3271 fn charge_bounded_work(&mut self, mut work_units: u64) -> Result<(), ExcelError> {
3272 if work_units == 0 {
3273 return self.resource_checkpoint(0);
3274 }
3275 while work_units > 0 {
3276 let chunk = work_units.min(256);
3277 self.resource_checkpoint(chunk)?;
3278 work_units -= chunk;
3279 }
3280 Ok(())
3281 }
3282
3283 fn reserve_request_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3284 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3285 ledger.observe_scratch(bytes);
3288 }
3289 Ok(())
3290 }
3291
3292 fn release_request_scratch(&mut self, bytes: u64) {
3293 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3294 let released = ledger.release_scratch(bytes);
3295 debug_assert!(
3296 released.is_ok(),
3297 "request scratch release exceeded the outstanding reservation"
3298 );
3299 }
3300 }
3301
3302 fn reserve_topology_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3303 self.active_resource_ledger
3304 .as_mut()
3305 .map_or(Ok(()), |ledger| ledger.reserve_schedule_discovery(bytes))
3306 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3307 }
3308
3309 fn reserve_graph_source_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3310 self.active_resource_ledger
3311 .as_mut()
3312 .map_or(Ok(()), |ledger| ledger.reserve_graph_source(bytes))
3313 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3314 }
3315
3316 fn with_request_scratch<T>(
3317 &mut self,
3318 bytes: u64,
3319 work: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3320 ) -> Result<T, ExcelError> {
3321 self.reserve_request_scratch(bytes)?;
3322 let result = work(self);
3323 self.release_request_scratch(bytes);
3324 result
3325 }
3326
3327 fn observe_staged_preparation(
3328 &mut self,
3329 selected: usize,
3330 retained: usize,
3331 elapsed: std::time::Duration,
3332 ) {
3333 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3334 stats.staged_selected = stats.staged_selected.saturating_add(selected as u64);
3335 stats.staged_retained = retained as u64;
3336 stats.phases.staged_prepare_ns = stats
3337 .phases
3338 .staged_prepare_ns
3339 .saturating_add(Self::duration_ns(elapsed));
3340 }
3341 }
3342
3343 fn graph_admission_enabled(&self) -> bool {
3344 crate::engine::resource_ledger::graph_admission_enabled(&self.evaluation_resource_budgets)
3345 }
3346
3347 fn preflight_graph_admission(
3348 &mut self,
3349 usage: crate::engine::resource_ledger::GraphAdmission,
3350 ) -> Result<(), ExcelError> {
3351 let request_id = self
3352 .active_evaluation_resource_request
3353 .as_ref()
3354 .map(|stats| stats.request_id);
3355 crate::engine::resource_ledger::preflight_graph_admission(
3356 &self.evaluation_resource_budgets,
3357 usage,
3358 request_id,
3359 )
3360 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3361 }
3362
3363 fn prepared_legacy_admission(
3364 &mut self,
3365 plan: &PreparedLegacyGraphPlan,
3366 materialization_cells: u64,
3367 ) -> Result<(), ExcelError> {
3368 if !self.graph_admission_enabled() {
3369 return Ok(());
3370 }
3371 let stats = self.graph.baseline_stats();
3372 let added_edges = plan.planned_edge_count().ok_or_else(|| {
3373 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3374 })?;
3375 let removed_edges = plan.removed_edge_count().ok_or_else(|| {
3376 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3377 })?;
3378 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
3379 final_vertices: stats
3380 .graph_vertex_count
3381 .checked_add(plan.new_vertex_count())
3382 .ok_or_else(|| {
3383 ExcelError::new(ExcelErrorKind::NImpl)
3384 .with_message("graph vertex count overflow")
3385 })?,
3386 final_edges: stats
3387 .graph_edge_count
3388 .checked_sub(removed_edges)
3389 .and_then(|count| count.checked_add(added_edges))
3390 .ok_or_else(|| {
3391 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3392 })?,
3393 materialization_cells,
3394 added_vertices: plan.new_vertex_count(),
3395 added_edges,
3396 })
3397 }
3398
3399 fn observe_target_admission_failure(
3400 &mut self,
3401 reason: formualizer_common::ResourceExhaustionReason,
3402 ) {
3403 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3404 stats.target_admission_failure = Some(reason);
3405 }
3406 }
3407
3408 fn observe_target_preparation_report(
3409 &mut self,
3410 report: &crate::engine::PreparedTargetGraphReport,
3411 ) {
3412 let reason_bit = |reason: crate::engine::OpaqueReason| -> u64 {
3413 let index = match reason {
3414 crate::engine::OpaqueReason::DynamicReference => 0,
3415 crate::engine::OpaqueReason::RuntimeTextReference => 1,
3416 crate::engine::OpaqueReason::UnknownFunction => 2,
3417 crate::engine::OpaqueReason::UnknownCustomFunction => 3,
3418 crate::engine::OpaqueReason::UnresolvedCrossSheetBinding => 4,
3419 crate::engine::OpaqueReason::UnresolvedName => 5,
3420 crate::engine::OpaqueReason::UnresolvedTable => 6,
3421 crate::engine::OpaqueReason::FormulaName => 7,
3422 crate::engine::OpaqueReason::DeferredSourcePackage => 8,
3423 crate::engine::OpaqueReason::UnsupportedSourceSemantics => 9,
3424 crate::engine::OpaqueReason::UncertainDefaultSheetBinding => 10,
3425 };
3426 1u64 << index
3427 };
3428 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3429 stats.staged_selected = report.selected_staged_cells as u64;
3430 stats.staged_retained = report.retained_staged_cells as u64;
3431 stats.target_requested = report.requested_targets as u64;
3432 stats.target_normalized_regions = report.normalized_regions as u64;
3433 stats.target_scope_level = match &report.widened_scope {
3434 crate::engine::PrepareScope::Exact => 0,
3435 crate::engine::PrepareScope::Sheets(_) => 1,
3436 crate::engine::PrepareScope::Workbook => 2,
3437 };
3438 stats.target_widening_reason_bits = report
3439 .widening_reasons
3440 .iter()
3441 .copied()
3442 .fold(0, |bits, reason| bits | reason_bit(reason));
3443 stats.graph_source_scratch_estimated = report.estimated_scratch_bytes;
3444 stats.graph_source_scratch_observed = report.observed_scratch_bytes;
3445 stats.target_commit_estimated_work = report.estimated_commit_work;
3446 stats.target_commit_actual_work = report.actual_commit_work;
3447 stats.target_commit_window_ns = Self::duration_ns(report.commit_window);
3448 stats.phases.staged_prepare_ns = stats
3449 .phases
3450 .staged_prepare_ns
3451 .saturating_add(Self::duration_ns(report.commit_window));
3452 }
3453 }
3454
3455 fn begin_evaluation_request(&mut self) {
3459 self.freshness_begin_request();
3460 #[cfg(test)]
3461 {
3462 self.evaluation_request_begin_count_for_test = self
3463 .evaluation_request_begin_count_for_test
3464 .saturating_add(1);
3465 }
3466 self.last_cycle_telemetry = CycleTelemetry::default();
3467 self.graph.authority_sync();
3468 self.pending_iterative_redirty.clear();
3471 self.reconcile_retained_sccs_at_request_begin();
3472 self.clock.refresh();
3476 }
3477
3478 fn redirty_for_next_recalc(&mut self) {
3487 self.graph.redirty_volatiles();
3488 let pending = std::mem::take(&mut self.pending_iterative_redirty);
3489 let dirty_at_begin = std::mem::take(&mut self.retained_scc_dirty_at_begin);
3490 for (vertex, scc) in dirty_at_begin {
3491 if self.retained_scc_members.get(&vertex) == Some(&scc) {
3492 self.retained_scc_members.remove(&vertex);
3498 if !self.graph.is_dirty(vertex) {
3499 self.iterative_state_values.remove(&vertex);
3500 }
3501 }
3502 }
3503 if !self.iterative_state_values.is_empty() || !self.retained_scc_members.is_empty() {
3504 let graph = &self.graph;
3505 self.iterative_state_values
3506 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3507 self.retained_scc_members
3508 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3509 }
3510 for &vertex in &pending {
3515 if !self.graph.is_live_formula_vertex(vertex) {
3516 continue;
3517 }
3518 if let Some(cell) = self.graph.get_cell_ref(vertex) {
3519 let sheet_name = self.graph.sheet_name(cell.sheet_id);
3520 match self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1) {
3521 Some(value) if !matches!(value, LiteralValue::Empty) => {
3522 self.iterative_state_values.insert(vertex, value);
3523 }
3524 _ => {
3525 self.iterative_state_values.remove(&vertex);
3526 }
3527 }
3528 }
3529 }
3530 if !pending.is_empty() {
3531 self.graph.redirty_iterative_members(&pending);
3532 }
3533 }
3534
3535 fn retained_scc_config_fingerprint(&self) -> u64 {
3540 use std::hash::{Hash, Hasher};
3541 let mut hasher = rustc_hash::FxHasher::default();
3542 let config = &self.config;
3543 std::mem::discriminant(&config.cycle.detection).hash(&mut hasher);
3544 match config.cycle.policy {
3545 CyclePolicy::Error => 0u8.hash(&mut hasher),
3546 CyclePolicy::Iterate {
3547 max_iterations,
3548 max_change,
3549 } => {
3550 1u8.hash(&mut hasher);
3551 max_iterations.hash(&mut hasher);
3552 max_change.to_bits().hash(&mut hasher);
3553 }
3554 }
3555 config.date_system.hash(&mut hasher);
3556 config.workbook_seed.hash(&mut hasher);
3557 std::mem::discriminant(&config.volatile_level).hash(&mut hasher);
3558 format!("{:?}", config.deterministic_mode).hash(&mut hasher);
3561 config.range_expansion_limit.hash(&mut hasher);
3562 config.max_open_ended_rows.hash(&mut hasher);
3563 config.max_open_ended_cols.hash(&mut hasher);
3564 hasher.finish()
3565 }
3566
3567 fn reconcile_retained_sccs_at_request_begin(&mut self) {
3573 self.retained_scc_dirty_at_begin.clear();
3574 if self.retained_scc_members.is_empty() {
3575 return;
3576 }
3577 let graph = &self.graph;
3580 self.retained_scc_members
3581 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3582 if self.retained_scc_config_fingerprint() != self.retained_scc_config_fingerprint {
3583 let members: Vec<VertexId> = self.retained_scc_members.keys().copied().collect();
3584 self.retained_scc_members.clear();
3585 self.graph.mark_dirty_many(&members);
3586 return;
3587 }
3588 let changes =
3589 crate::function_registry::semantic_changes_since(self.retained_scc_function_epoch_seen);
3590 let global_changed = changes.epoch != self.retained_scc_function_epoch_seen;
3591 let provider_revision = self.resolver.planning_semantic_revision();
3592 let provider_changed = provider_revision != self.retained_scc_provider_revision_seen;
3593 if global_changed || provider_changed {
3594 let changed: BTreeSet<(String, String)> = changes.keys.into_iter().collect();
3595 let affected: Vec<VertexId> = self
3596 .retained_scc_members
3597 .keys()
3598 .copied()
3599 .filter(|&vertex| {
3600 let Some(ast) = self.graph.get_formula(vertex) else {
3601 return true;
3602 };
3603 (global_changed
3604 && (!changes.complete || Self::ast_uses_changed_function(&ast, &changed)))
3605 || (provider_changed && Self::ast_contains_function(&ast))
3606 })
3607 .collect();
3608 for vertex in &affected {
3609 self.retained_scc_members.remove(vertex);
3610 }
3611 if !affected.is_empty() {
3612 self.graph.mark_dirty_many(&affected);
3613 }
3614 self.retained_scc_function_epoch_seen = changes.epoch;
3615 self.retained_scc_provider_revision_seen = provider_revision;
3616 }
3617 let mut dirty_sccs: FxHashSet<u64> = FxHashSet::default();
3618 let mut all_sccs: FxHashSet<u64> = FxHashSet::default();
3619 for (&vertex, &scc) in &self.retained_scc_members {
3620 all_sccs.insert(scc);
3621 if self.graph.is_dirty(vertex) {
3622 dirty_sccs.insert(scc);
3623 self.retained_scc_dirty_at_begin.push((vertex, scc));
3624 }
3625 }
3626 let reused_members = self
3627 .retained_scc_members
3628 .values()
3629 .filter(|scc| !dirty_sccs.contains(scc))
3630 .count();
3631 let t = &mut self.last_cycle_telemetry;
3632 t.reused_sccs = all_sccs.len() - dirty_sccs.len();
3633 t.reused_scc_members = reused_members;
3634 }
3635
3636 pub fn virtual_dep_fallback_activations(&self) -> u64 {
3637 self.virtual_dep_fallback_activations
3638 }
3639
3640 #[cfg(test)]
3641 pub(crate) fn lookup_index_flights_built_for_test(&self) -> usize {
3642 self.lookup_index_cache
3643 .flights_built
3644 .load(std::sync::atomic::Ordering::Relaxed)
3645 }
3646
3647 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
3648 self.lookup_index_cache.report()
3649 }
3650
3651 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
3652 let start_row = view.start_row();
3653 let end_row = view.end_row();
3654 let start_col = view.start_col();
3655 let end_col = view.end_col();
3656 for row in start_row..=end_row {
3657 let Ok(row_u32) = u32::try_from(row) else {
3658 return true;
3659 };
3660 for col in start_col..=end_col {
3661 let Ok(col_u32) = u32::try_from(col) else {
3662 return true;
3663 };
3664 let cell_ref = self
3665 .graph
3666 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
3667 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
3668 && self.graph.is_volatile(vertex_id)
3669 {
3670 return true;
3671 }
3672 }
3673 }
3674 false
3675 }
3676
3677 fn build_lookup_index_impl(
3678 &self,
3679 view: &RangeView<'_>,
3680 axis: LookupAxis,
3681 ) -> Option<Arc<LookupIndex>> {
3682 let (rows, cols) = view.dims();
3683 if rows == 0 || cols == 0 {
3684 self.lookup_index_cache.note_skipped_tiny();
3685 return None;
3686 }
3687 let len = match axis {
3688 LookupAxis::ColumnInView(col) => {
3689 if col >= cols {
3690 self.lookup_index_cache.note_skipped_tiny();
3691 return None;
3692 }
3693 rows
3694 }
3695 LookupAxis::RowInView(row) => {
3696 if row >= rows {
3697 self.lookup_index_cache.note_skipped_tiny();
3698 return None;
3699 }
3700 cols
3701 }
3702 };
3703 if len < 64 {
3704 self.lookup_index_cache.note_skipped_tiny();
3705 return None;
3706 }
3707
3708 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
3709 let key = LookupIndexKey {
3710 sheet_id,
3711 start_row: u32::try_from(view.start_row()).ok()?,
3712 start_col: u32::try_from(view.start_col()).ok()?,
3713 end_row: u32::try_from(view.end_row()).ok()?,
3714 end_col: u32::try_from(view.end_col()).ok()?,
3715 axis,
3716 snapshot_id: self.data_snapshot_id(),
3717 };
3718 if let Some(index) = self.lookup_index_cache.get(&key) {
3719 return Some(index);
3720 }
3721 if self
3722 .lookup_index_cache
3723 .would_exceed_cap(estimate_bytes(len, 0))
3724 {
3725 self.lookup_index_cache.note_skipped_cap();
3726 return None;
3727 }
3728 if !self.lookup_index_cache.should_build(key) {
3729 return None;
3730 }
3731 self.lookup_index_cache.single_flight(key, || {
3733 if let Some(index) = self.lookup_index_cache.recheck(&key) {
3734 return Some(index);
3735 }
3736 if self.lookup_index_cache.is_known_volatile(&key) {
3737 self.lookup_index_cache.note_skipped_volatile();
3738 return None;
3739 }
3740 if self.lookup_view_contains_volatile(view, sheet_id) {
3741 self.lookup_index_cache.note_volatile_key(key);
3742 self.lookup_index_cache.note_skipped_volatile();
3743 return None;
3744 }
3745 #[cfg(test)]
3746 self.lookup_index_cache
3747 .flights_built
3748 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3749 match LookupIndex::build(view, axis, self.config.date_system).ok()? {
3750 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
3751 BuildOutcome::ErrorInLookupAxis => {
3752 self.lookup_index_cache.note_skipped_error();
3753 None
3754 }
3755 BuildOutcome::Degenerate => {
3756 self.lookup_index_cache.note_skipped_tiny();
3757 None
3758 }
3759 }
3760 })
3761 }
3762
3763 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
3764 if !self.config.enable_virtual_dep_telemetry {
3765 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
3766 fallback_mode_activations: self.virtual_dep_fallback_activations,
3767 ..VirtualDepTelemetry::default()
3768 };
3769 }
3770 }
3771
3772 fn source_cache_session(&self) -> SourceCacheSession {
3773 self.clear_source_cache();
3774 SourceCacheSession {
3775 cache: self.source_cache.clone(),
3776 }
3777 }
3778
3779 fn resolve_source_scalar_cached(
3780 &self,
3781 name: &str,
3782 version: Option<u64>,
3783 ) -> Result<LiteralValue, ExcelError> {
3784 let key = (name.to_string(), version);
3785 if let Ok(mut g) = self.source_cache.write() {
3786 if let Some(v) = g.scalars.get(&key) {
3787 return Ok(v.clone());
3788 }
3789
3790 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
3791 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3792 ExcelError::new(ExcelErrorKind::Ref)
3793 .with_message(format!("Unresolved source scalar: {name}"))
3794 } else {
3795 err
3796 }
3797 })?;
3798 g.scalars.insert(key, v.clone());
3799 Ok(v)
3800 } else {
3801 self.resolver.resolve_source_scalar(name).map_err(|err| {
3802 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3803 ExcelError::new(ExcelErrorKind::Ref)
3804 .with_message(format!("Unresolved source scalar: {name}"))
3805 } else {
3806 err
3807 }
3808 })
3809 }
3810 }
3811
3812 fn resolve_source_table_cached(
3813 &self,
3814 name: &str,
3815 version: Option<u64>,
3816 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
3817 let key = (name.to_string(), version);
3818 if let Ok(mut g) = self.source_cache.write() {
3819 if let Some(t) = g.tables.get(&key) {
3820 return Ok(t.clone());
3821 }
3822
3823 let t = self.resolver.resolve_source_table(name).map_err(|err| {
3824 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3825 ExcelError::new(ExcelErrorKind::Ref)
3826 .with_message(format!("Unresolved source table: {name}"))
3827 } else {
3828 err
3829 }
3830 })?;
3831 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
3832 g.tables.insert(key, t.clone());
3833 Ok(t)
3834 } else {
3835 self.resolver
3836 .resolve_source_table(name)
3837 .map_err(|err| {
3838 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
3839 ExcelError::new(ExcelErrorKind::Ref)
3840 .with_message(format!("Unresolved source table: {name}"))
3841 } else {
3842 err
3843 }
3844 })
3845 .map(Arc::from)
3846 }
3847 }
3848
3849 fn source_table_to_range_view(
3850 &self,
3851 table: &dyn crate::traits::Table,
3852 spec: &Option<formualizer_parse::parser::TableSpecifier>,
3853 ) -> Result<RangeView<'static>, ExcelError> {
3854 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
3855
3856 let owned = match spec {
3857 Some(TableSpecifier::Column(c)) => {
3858 let c = c.trim();
3859 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
3860 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3861 "Complex structured references not yet supported".to_string(),
3862 ));
3863 }
3864 table.get_column(c)?.materialise().into_owned()
3865 }
3866 Some(TableSpecifier::ColumnRange(start, end)) => {
3867 let cols = table.columns();
3868 let start = start.trim();
3869 let end = end.trim();
3870 let start_key = start.to_lowercase();
3871 let end_key = end.to_lowercase();
3872 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
3873 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
3874 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
3875 if si > ei {
3876 std::mem::swap(&mut si, &mut ei);
3877 }
3878 let h = table.data_height();
3879 let w = ei - si + 1;
3880 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
3881 for (offset, ci) in (si..=ei).enumerate() {
3882 let cname = &cols[ci];
3883 let col_range = table.get_column(cname)?;
3884 let (rh, _) = col_range.dimensions();
3885 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
3886 row[offset] = col_range.get(r, 0)?;
3887 }
3888 }
3889 rows
3890 } else {
3891 return Err(ExcelError::new(ExcelErrorKind::Ref)
3892 .with_message("Column range refers to unknown column(s)".to_string()));
3893 }
3894 }
3895 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
3896 | Some(TableSpecifier::Headers) => table
3897 .headers_row()
3898 .map(|r| r.materialise().into_owned())
3899 .unwrap_or_default(),
3900 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
3901 | Some(TableSpecifier::Totals) => table
3902 .totals_row()
3903 .map(|r| r.materialise().into_owned())
3904 .unwrap_or_default(),
3905 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
3906 table
3907 .data_body()
3908 .map(|r| r.materialise().into_owned())
3909 .unwrap_or_default()
3910 }
3911 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
3912 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
3913 if let Some(h) = table.headers_row() {
3914 out.extend(h.iter_rows());
3915 }
3916 if let Some(body) = table.data_body() {
3917 out.extend(body.iter_rows());
3918 }
3919 if let Some(tr) = table.totals_row() {
3920 out.extend(tr.iter_rows());
3921 }
3922 out
3923 }
3924 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
3925 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
3926 "@ (This Row) requires table-aware context; not yet supported".to_string(),
3927 ));
3928 }
3929 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
3930 return Err(ExcelError::new(ExcelErrorKind::NImpl)
3931 .with_message("Complex structured references not yet supported".to_string()));
3932 }
3933 None => {
3934 return Err(ExcelError::new(ExcelErrorKind::NImpl)
3935 .with_message("Table reference without specifier is unsupported".to_string()));
3936 }
3937 };
3938
3939 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
3940 }
3941
3942 pub fn default_sheet_id(&self) -> SheetId {
3943 self.graph.default_sheet_id()
3944 }
3945
3946 pub fn default_sheet_name(&self) -> &str {
3947 self.graph.default_sheet_name()
3948 }
3949
3950 pub fn set_workbook_seed(&mut self, seed: u64) {
3952 self.config.workbook_seed = seed;
3953 }
3954
3955 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
3957 self.config.volatile_level = level;
3958 }
3959
3960 pub fn set_temporal_egress(&mut self, policy: crate::engine::TemporalEgress) {
3962 self.config.temporal_egress = policy;
3963 }
3964
3965 pub fn temporal_egress(&self) -> crate::engine::TemporalEgress {
3966 self.config.temporal_egress
3967 }
3968
3969 pub fn set_deterministic_mode(
3971 &mut self,
3972 mode: crate::engine::DeterministicMode,
3973 ) -> Result<(), ExcelError> {
3974 let clock = mode.build_clock()?;
3975 self.config.deterministic_mode = mode;
3976 self.clock = crate::timezone::SnapshotClock::new(clock);
3977 Ok(())
3978 }
3979
3980 pub fn set_clock(&mut self, clock: Arc<dyn crate::timezone::ClockProvider>) {
3988 self.clock = crate::timezone::SnapshotClock::new(clock);
3989 }
3990
3991 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
3992 self.config.deterministic_mode.validate()
3993 }
3994
3995 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
3996 self.graph.sheet_id(name)
3997 }
3998
3999 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
4000 self.add_sheet(name)
4001 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
4002 }
4003
4004 pub fn sheet_name(&self, id: SheetId) -> &str {
4005 self.graph.sheet_name(id)
4006 }
4007
4008 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
4009 let id = self.graph.add_sheet(name)?;
4010 self.ensure_arrow_sheet(name);
4011 self.mark_topology_edited();
4012 Ok(id)
4013 }
4014
4015 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
4016 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
4017 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
4018 })?;
4019 if new_name.is_empty() || new_name.len() > 255 {
4020 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
4021 }
4022 if self.graph.sheet_id(new_name).is_some() {
4023 return Err(ExcelError::new(ExcelErrorKind::Value)
4024 .with_message(format!("Sheet '{new_name}' already exists")));
4025 }
4026 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
4027
4028 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
4029 let mut copied_sheet = source_sheet;
4030 copied_sheet.name = Arc::<str>::from(new_name);
4031 self.arrow_sheets.sheets.push(copied_sheet);
4032 } else {
4033 self.ensure_arrow_sheet(new_name);
4034 }
4035
4036 let duplicated_formulas = self
4037 .graph
4038 .formula_vertices()
4039 .into_iter()
4040 .filter(|vertex| {
4041 self.graph
4042 .get_cell_ref(*vertex)
4043 .is_some_and(|cell| cell.sheet_id == new_id)
4044 })
4045 .collect::<Vec<_>>();
4046 self.graph.mark_vertices_dirty_batch(&duplicated_formulas);
4047 self.mark_topology_edited();
4048 Ok(new_id)
4049 }
4050
4051 fn ensure_arrow_sheet(&mut self, name: &str) {
4052 if self.arrow_sheets.sheet(name).is_some() {
4053 return;
4054 }
4055 self.arrow_sheets
4056 .sheets
4057 .push(crate::arrow_store::ArrowSheet {
4058 name: std::sync::Arc::<str>::from(name),
4059 date_system: self.config.date_system,
4060 columns: Vec::new(),
4061 nrows: 0,
4062 chunk_starts: Vec::new(),
4063 chunk_rows: 32 * 1024,
4064 });
4065 }
4066
4067 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
4068 let name = self.graph.sheet_name(sheet_id).to_string();
4069 self.purge_derived_formats_for_sheet(sheet_id);
4070 self.graph.remove_sheet(sheet_id)?;
4071 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
4072 self.clear_all_computed_overlays();
4076 self.mark_all_formula_vertices_dirty();
4077 self.clear_staged_formulas_for_sheet(&name);
4078 if self.row_visibility.remove(&sheet_id).is_some() {
4079 self.invalidate_row_visibility_mask_cache();
4080 }
4081 self.record_structural_change(StructuralScope::RemovedSheet(sheet_id));
4082 self.mark_topology_edited();
4083 Ok(())
4084 }
4085
4086 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
4088 if let Some(asheet) = self
4089 .arrow_sheets
4090 .sheets
4091 .iter_mut()
4092 .find(|s| s.name.as_ref() == target_name)
4093 {
4094 asheet.name = std::sync::Arc::<str>::from(new_name);
4095 return true;
4096 }
4097 false
4098 }
4099
4100 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
4101 let old_name = self.graph.sheet_name(sheet_id).to_string();
4102
4103 self.rename_sheet_in_arrow_store(&old_name, new_name);
4106
4107 match self.graph.rename_sheet(sheet_id, new_name) {
4109 Ok(_) => {
4110 self.rename_staged_formula_sheet(&old_name, new_name);
4111 let sheet_vertices: Vec<VertexId> = self
4113 .graph
4114 .grid_vertices_in_sheet(sheet_id)
4115 .map(|(id, _)| id)
4116 .collect();
4117 for v_id in sheet_vertices {
4118 self.graph.mark_vertex_dirty(v_id);
4119 }
4120 self.mark_topology_edited();
4122 Ok(())
4123 }
4124 Err(e) => {
4125 self.rename_sheet_in_arrow_store(new_name, &old_name);
4127 Err(e)
4128 }
4129 }
4130 }
4131
4132 pub fn named_ranges_iter(
4133 &self,
4134 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
4135 self.graph.named_ranges_iter()
4136 }
4137
4138 pub fn sheet_named_ranges_iter(
4139 &self,
4140 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
4141 self.graph.sheet_named_ranges_iter()
4142 }
4143
4144 pub fn resolve_name_entry(
4145 &self,
4146 name: &str,
4147 current_sheet: SheetId,
4148 ) -> Option<&crate::engine::named_range::NamedRange> {
4149 self.graph.resolve_name_entry(name, current_sheet)
4150 }
4151
4152 pub(crate) fn name_query_scope(
4162 &self,
4163 scope_sheet: Option<&str>,
4164 ) -> Result<NameScope, ExcelError> {
4165 match scope_sheet {
4166 None => Ok(NameScope::Workbook),
4167 Some(sheet) => self
4168 .graph
4169 .sheet_id(sheet)
4170 .map(NameScope::Sheet)
4171 .ok_or_else(|| {
4172 ExcelError::new(ExcelErrorKind::Ref)
4173 .with_message(format!("name scope sheet not found: {sheet}"))
4174 }),
4175 }
4176 }
4177
4178 fn resolve_sheet_locator(
4191 &self,
4192 locator: &crate::reference::SharedSheetLocator<'_>,
4193 context_sheet: SheetId,
4194 ) -> Result<SheetId, ExcelError> {
4195 self.graph
4196 .sheet_reg()
4197 .resolve_locator(locator, context_sheet)
4198 }
4199
4200 pub fn has_name(&self, name: &str, scope_sheet: Option<&str>) -> bool {
4201 let Ok(scope) = self.name_query_scope(scope_sheet) else {
4202 return false;
4203 };
4204 self.graph
4205 .resolve_name_entry_in_scope(name, scope)
4206 .is_some()
4207 }
4208
4209 pub fn resolved_name_value(
4213 &self,
4214 name: &str,
4215 scope_sheet: Option<&str>,
4216 ) -> Option<LiteralValue> {
4217 let scope = self.name_query_scope(scope_sheet).ok()?;
4218 let entry = self.graph.resolve_name_entry_in_scope(name, scope)?;
4219 self.graph.get_value(entry.vertex)
4220 }
4221
4222 pub fn table_metadata(&self, name: &str) -> Option<TableMetadata> {
4223 let entry = self.graph.resolve_table_entry(name)?;
4224 Some(TableMetadata {
4225 name: entry.name.clone(),
4226 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
4227 start_row: entry.range.start.coord.row() + 1,
4228 start_col: entry.range.start.coord.col() + 1,
4229 end_row: entry.range.end.coord.row() + 1,
4230 end_col: entry.range.end.coord.col() + 1,
4231 header_row: entry.header_row,
4232 headers: entry.headers.clone(),
4233 totals_row: entry.totals_row,
4234 })
4235 }
4236
4237 pub fn tables(&self) -> Vec<TableMetadata> {
4239 self.graph
4240 .table_names()
4241 .into_iter()
4242 .filter_map(|name| self.table_metadata(&name))
4243 .collect()
4244 }
4245
4246 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4247 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
4248
4249 for (name, named) in self.graph.named_ranges_iter() {
4250 out.push(crate::engine::named_range::NamedRangeSnapshot {
4251 name: name.clone(),
4252 scope: NameScope::Workbook,
4253 definition: named.definition.clone(),
4254 });
4255 }
4256
4257 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
4258 out.push(crate::engine::named_range::NamedRangeSnapshot {
4259 name: name.clone(),
4260 scope: NameScope::Sheet(*sheet_id),
4261 definition: named.definition.clone(),
4262 });
4263 }
4264
4265 out.sort_by(|a, b| {
4266 let a_scope = match a.scope {
4267 NameScope::Workbook => (0u8, 0u32),
4268 NameScope::Sheet(id) => (1u8, u32::from(id)),
4269 };
4270 let b_scope = match b.scope {
4271 NameScope::Workbook => (0u8, 0u32),
4272 NameScope::Sheet(id) => (1u8, u32::from(id)),
4273 };
4274 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
4275 });
4276
4277 out
4278 }
4279
4280 pub fn named_ranges_snapshot_for_sheet(
4281 &self,
4282 sheet_id: SheetId,
4283 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4284 self.named_ranges_snapshot()
4285 .into_iter()
4286 .filter(|entry| match entry.scope {
4287 NameScope::Workbook => true,
4288 NameScope::Sheet(id) => id == sheet_id,
4289 })
4290 .collect()
4291 }
4292
4293 pub fn define_name(
4294 &mut self,
4295 name: &str,
4296 definition: NamedDefinition,
4297 scope: NameScope,
4298 ) -> Result<(), ExcelError> {
4299 self.graph.validate_define_name(name, scope)?;
4300 self.graph.define_name(name, definition, scope)?;
4301 self.record_structural_change(StructuralScope::AllSheets);
4302
4303 self.mark_topology_edited();
4304
4305 Ok(())
4306 }
4307
4308 pub fn update_name(
4309 &mut self,
4310 name: &str,
4311 definition: NamedDefinition,
4312 scope: NameScope,
4313 ) -> Result<(), ExcelError> {
4314 self.graph.validate_existing_name(name, scope)?;
4315 self.graph.update_name(name, definition, scope)?;
4316 self.record_structural_change(StructuralScope::AllSheets);
4317
4318 self.mark_topology_edited();
4319
4320 Ok(())
4321 }
4322
4323 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
4324 self.graph.validate_existing_name(name, scope)?;
4325 self.graph.delete_name(name, scope)?;
4326 self.record_structural_change(StructuralScope::AllSheets);
4327
4328 self.mark_topology_edited();
4329
4330 Ok(())
4331 }
4332
4333 pub fn define_table(
4334 &mut self,
4335 name: &str,
4336 range: crate::reference::RangeRef,
4337 header_row: bool,
4338 headers: Vec<String>,
4339 totals_row: bool,
4340 ) -> Result<(), ExcelError> {
4341 self.graph
4342 .define_table(name, range, header_row, headers, totals_row)?;
4343 self.record_structural_change(StructuralScope::AllSheets);
4344 self.mark_topology_edited();
4345 Ok(())
4346 }
4347
4348 pub fn define_source_scalar(
4349 &mut self,
4350 name: &str,
4351 version: Option<u64>,
4352 ) -> Result<(), ExcelError> {
4353 self.graph.define_source_scalar(name, version)?;
4354 self.record_structural_change(StructuralScope::OpaqueGlobal);
4355 self.mark_topology_edited();
4356 Ok(())
4357 }
4358
4359 pub fn define_source_table(
4360 &mut self,
4361 name: &str,
4362 version: Option<u64>,
4363 ) -> Result<(), ExcelError> {
4364 self.graph.define_source_table(name, version)?;
4365 self.record_structural_change(StructuralScope::OpaqueGlobal);
4366 self.mark_topology_edited();
4367 Ok(())
4368 }
4369
4370 pub fn set_source_scalar_version(
4371 &mut self,
4372 name: &str,
4373 version: Option<u64>,
4374 ) -> Result<(), ExcelError> {
4375 self.graph.set_source_scalar_version(name, version)?;
4376 Ok(())
4377 }
4378
4379 pub fn set_source_table_version(
4380 &mut self,
4381 name: &str,
4382 version: Option<u64>,
4383 ) -> Result<(), ExcelError> {
4384 self.graph.set_source_table_version(name, version)?;
4385 Ok(())
4386 }
4387
4388 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
4389 self.graph.invalidate_source(name)?;
4390 Ok(())
4391 }
4392
4393 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
4394 self.graph.get_value(vertex)
4395 }
4396
4397 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
4398 self.graph.get_cell_value(sheet, row, col)
4399 }
4400
4401 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
4402 self.graph.get_vertex_for_cell(cell)
4403 }
4404
4405 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
4406 self.graph.get_evaluation_vertices()
4407 }
4408
4409 pub fn baseline_stats(&self) -> EngineBaselineStats {
4411 let graph = self.graph.baseline_stats();
4412 EngineBaselineStats {
4413 graph_vertex_count: graph.graph_vertex_count,
4414 graph_formula_vertex_count: graph.graph_formula_vertex_count,
4415 graph_edge_count: graph.graph_edge_count,
4416 dirty_vertex_count: graph.dirty_vertex_count,
4417 evaluation_vertex_count: graph.evaluation_vertex_count,
4418 formula_ast_root_count: graph.formula_ast_root_count,
4419 formula_ast_node_count: graph.formula_ast_node_count,
4420 staged_formula_count: self.staged_formula_count(),
4421 formula_plane_active_span_count: 0,
4422 formula_plane_producer_result_entries: 0,
4423 formula_plane_consumer_read_entries: 0,
4424 formula_plane_mixed_topology_cache_builds: 0,
4425 formula_plane_mixed_topology_cache_hits: 0,
4426 formula_plane_mixed_topology_cache_overflows: 0,
4427 formula_plane_dirty_pending_events: 0,
4428 formula_plane_dirty_region_events_recorded: 0,
4429 formula_plane_dirty_span_region_events_recorded: 0,
4430 formula_plane_dirty_whole_span_seeds_recorded: 0,
4431 formula_plane_dirty_global_invalidations: 0,
4432 formula_plane_structural_span_candidates: 0,
4433 formula_plane_cycle_member_span_demotions: 0,
4434 formula_plane_array_result_span_demotions: 0,
4435 retained_scc_members: self.retained_scc_members.len(),
4436 }
4437 }
4438
4439 pub(crate) fn inspection_mutation_revision(&self) -> u64 {
4441 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
4442 }
4443
4444 #[cfg(test)]
4445 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
4446 self.used_axis_bounds_cache
4447 .read()
4448 .ok()
4449 .and_then(|guard| {
4450 guard.as_ref().map(|cache| {
4451 (
4452 cache.row_hits.load(Ordering::Relaxed),
4453 cache.row_misses.load(Ordering::Relaxed),
4454 cache.col_hits.load(Ordering::Relaxed),
4455 cache.col_misses.load(Ordering::Relaxed),
4456 )
4457 })
4458 })
4459 .unwrap_or((0, 0, 0, 0))
4460 }
4461
4462 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
4463 self.graph.set_first_load_assume_new(enabled);
4464 }
4465
4466 pub fn first_load_assume_new(&self) -> bool {
4467 self.graph.first_load_assume_new()
4468 }
4469
4470 pub fn reset_ensure_touched(&mut self) {
4471 self.graph.reset_ensure_touched();
4472 }
4473
4474 pub fn finalize_sheet_index(&mut self, sheet: &str) {
4475 self.graph.finalize_sheet_index(sheet);
4476 }
4477
4478 pub fn action<T>(
4487 &mut self,
4488 name: impl AsRef<str>,
4489 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4490 ) -> Result<T, crate::engine::EditorError> {
4491 if self.action_depth != 0 {
4492 return Err(crate::engine::EditorError::TransactionFailed {
4493 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
4494 .to_string(),
4495 });
4496 }
4497
4498 self.action_depth = 1;
4499 let engine_ptr: *mut Engine<R> = self;
4500 let _guard = ActionDepthGuard {
4501 engine: engine_ptr,
4502 _marker: std::marker::PhantomData,
4503 };
4504
4505 let mut tx = EngineAction {
4506 engine: self,
4507 name: name.as_ref().to_string(),
4508 capture: None,
4509 arrow_undo: None,
4510 atomic_policy: false,
4511 };
4512 f(&mut tx)
4513 }
4514
4515 pub fn action_atomic<T>(
4519 &mut self,
4520 name: impl Into<String>,
4521 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4522 ) -> Result<T, crate::engine::EditorError> {
4523 let (v, _j) = self.action_atomic_journal(name, f)?;
4524 Ok(v)
4525 }
4526
4527 pub fn action_atomic_journal<T>(
4529 &mut self,
4530 name: impl Into<String>,
4531 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4532 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4533 if self.action_depth != 0 {
4534 return Err(crate::engine::EditorError::TransactionFailed {
4535 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4536 .to_string(),
4537 });
4538 }
4539
4540 self.action_depth = 1;
4541 let engine_ptr: *mut Engine<R> = self;
4542 let _guard = ActionDepthGuard {
4543 engine: engine_ptr,
4544 _marker: std::marker::PhantomData,
4545 };
4546
4547 let name_str = name.into();
4548 let mut capture = MutationCapture::new(Default::default());
4549 let start_len = capture.len();
4550 self.action_atomic_impl(&mut capture, start_len, true, name_str, f)
4551 }
4552
4553 fn action_atomic_impl<T>(
4554 &mut self,
4555 capture: &mut MutationCapture,
4556 start_len: usize,
4557 expand_runs: bool,
4558 name: String,
4559 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4560 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4561 let invalidation_baseline = self.invalidation_baseline();
4562 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
4563 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
4564
4565 let capture_ptr: *mut MutationCapture = capture;
4566 let mut tx = EngineAction {
4567 engine: self,
4568 name: name.clone(),
4569 capture: Some(capture_ptr),
4570 arrow_undo: Some(arrow_ptr),
4571 atomic_policy: true,
4572 };
4573
4574 let res = f(&mut tx);
4575
4576 let capture_ref = unsafe { &*capture_ptr };
4583 let has_runs = capture_ref.lazy_len() > 0;
4584 let graph_events: Vec<crate::engine::ChangeEvent> = if expand_runs || res.is_err() {
4585 capture_ref.expanded_events_from(start_len, 0)
4586 } else {
4587 capture_ref.events()[start_len..].to_vec()
4588 };
4589 let graph_batch = crate::engine::GraphUndoBatch {
4590 events: graph_events,
4591 };
4592 let affected_cells = arrow_undo.ops.len();
4593 let journal = crate::engine::ActionJournal {
4594 name,
4595 graph: graph_batch,
4596 arrow: arrow_undo,
4597 affected_cells,
4598 };
4599
4600 match res {
4601 Ok(v) => {
4602 if !journal.graph.is_empty() || !journal.arrow.is_empty() || has_runs {
4603 for event in &journal.graph.events {
4604 self.record_change_for_event(event);
4605 }
4606 let mut impact = Self::classify_change_events(
4607 &journal.graph.events,
4608 LoggedEditDirection::Original,
4609 )
4610 .max(Self::classify_arrow_undo(&journal.arrow));
4611 if has_runs {
4612 impact = impact.max(LoggedEditImpact::Topology);
4613 }
4614 self.apply_logged_edit_impact(impact, invalidation_baseline);
4615 }
4616 Ok((v, journal))
4617 }
4618 Err(e) => {
4619 if let Err(rb) = self.rollback_from_action_journal(&journal, invalidation_baseline)
4620 {
4621 return Err(crate::engine::EditorError::TransactionFailed {
4622 reason: format!(
4623 "Engine::action_atomic rollback failed after error '{e}': {rb}"
4624 ),
4625 });
4626 }
4627 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
4628 for event in &journal.graph.events {
4629 self.record_change_for_event(event);
4630 }
4631 }
4632 Err(e)
4633 }
4634 }
4635 }
4636
4637 pub fn action_with_logger<T>(
4644 &mut self,
4645 log: &mut crate::engine::ChangeLog,
4646 name: impl AsRef<str>,
4647 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4648 ) -> Result<T, crate::engine::EditorError> {
4649 if self.action_depth != 0 {
4650 return Err(crate::engine::EditorError::TransactionFailed {
4651 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4652 .to_string(),
4653 });
4654 }
4655
4656 self.action_depth = 1;
4657 let engine_ptr: *mut Engine<R> = self;
4658 let _guard = ActionDepthGuard {
4659 engine: engine_ptr,
4660 _marker: std::marker::PhantomData,
4661 };
4662
4663 let name_str = name.as_ref().to_string();
4664 let mut capture = MutationCapture::new(log.current_meta());
4665 let start_len = capture.len();
4666 capture.begin_compound(name_str.clone());
4667
4668 let res = self.action_atomic_impl(&mut capture, start_len, false, name_str, f);
4671 capture.close_compounds();
4672
4673 match res {
4674 Ok((v, _journal)) => {
4675 log.publish_capture(capture);
4676 Ok(v)
4677 }
4678 Err(e) => {
4679 log.discard_capture(capture);
4681 Err(e)
4682 }
4683 }
4684 }
4685
4686 fn rollback_from_action_journal(
4687 &mut self,
4688 journal: &crate::engine::ActionJournal,
4689 invalidation_baseline: InvalidationBaseline,
4690 ) -> Result<(), crate::engine::EditorError> {
4691 self.invalidate_for_action_journal(
4694 journal,
4695 LoggedEditDirection::InverseReplay,
4696 invalidation_baseline,
4697 );
4698 journal.graph.undo(&mut self.graph)?;
4700 self.apply_inverse_row_visibility_events(&journal.graph.events);
4702 self.apply_arrow_undo_batch(&journal.arrow, true);
4704 Ok(())
4705 }
4706
4707 fn rollback_from_change_events(
4708 &mut self,
4709 events: &[crate::engine::ChangeEvent],
4710 invalidation_baseline: InvalidationBaseline,
4711 ) -> Result<(), crate::engine::EditorError> {
4712 use crate::engine::ChangeEvent;
4713
4714 self.invalidate_for_change_events(
4717 events,
4718 LoggedEditDirection::InverseReplay,
4719 invalidation_baseline,
4720 );
4721
4722 self.graph
4724 .authority_set_replay(crate::engine::authority::history::Replay::Undo);
4725 let rolled_back = (|| {
4726 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
4727 let mut compound_stack: Vec<usize> = Vec::new();
4728 for (i, ev) in events.iter().enumerate().rev() {
4729 match ev {
4730 ChangeEvent::CompoundEnd { depth } => {
4731 compound_stack.push(*depth);
4732 if let Some(description) =
4733 crate::engine::graph::editor::change_log::compound_start_description(
4734 i,
4735 |j| &events[j],
4736 )
4737 {
4738 editor.inverse_compound_end(description);
4739 }
4740 }
4741 ChangeEvent::CompoundStart { depth, .. } => {
4742 if compound_stack.last() == Some(depth) {
4743 compound_stack.pop();
4744 }
4745 editor.apply_inverse(ev.clone())?;
4746 }
4747 ChangeEvent::SetRowVisibility { .. } => {
4748 }
4750 _ => {
4751 editor.apply_inverse(ev.clone())?;
4752 }
4753 }
4754 }
4755 Ok::<_, crate::engine::EditorError>(())
4756 })();
4757 self.graph
4758 .authority_set_replay(crate::engine::authority::history::Replay::Forward);
4759 rolled_back?;
4760
4761 for ev in events.iter().rev() {
4763 self.apply_inverse_row_visibility_event(ev);
4764 }
4765
4766 for ev in events.iter().rev() {
4768 self.mirror_inverse_change_to_arrow(ev);
4769 }
4770
4771 Ok(())
4772 }
4773
4774 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
4775 let sheet_id = self.graph.sheet_id(sheet)?;
4776 let coord = Coord::from_excel(row, col, true, true);
4777 let cell = CellRef::new(sheet_id, coord);
4778 let vid = self.graph.get_vertex_for_cell(&cell)?;
4779 self.graph.get_formula(vid)
4780 }
4781
4782 pub fn define_name_with_logger(
4783 &mut self,
4784 log: &mut crate::engine::ChangeLog,
4785 name: &str,
4786 definition: NamedDefinition,
4787 scope: NameScope,
4788 ) -> Result<(), crate::engine::EditorError> {
4789 self.graph
4790 .validate_define_name(name, scope)
4791 .map_err(crate::engine::EditorError::Excel)?;
4792
4793 {
4794 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4795 editor.define_name(name, definition, scope)?;
4796 }
4797 self.record_structural_change(StructuralScope::AllSheets);
4798
4799 self.mark_topology_edited();
4800
4801 Ok(())
4802 }
4803
4804 pub fn update_name_with_logger(
4805 &mut self,
4806 log: &mut crate::engine::ChangeLog,
4807 name: &str,
4808 definition: NamedDefinition,
4809 scope: NameScope,
4810 ) -> Result<(), crate::engine::EditorError> {
4811 self.graph
4812 .validate_existing_name(name, scope)
4813 .map_err(crate::engine::EditorError::Excel)?;
4814 {
4815 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4816 editor.update_name(name, definition, scope)?;
4817 }
4818 self.record_structural_change(StructuralScope::AllSheets);
4819
4820 self.mark_topology_edited();
4821
4822 Ok(())
4823 }
4824
4825 pub fn delete_name_with_logger(
4826 &mut self,
4827 log: &mut crate::engine::ChangeLog,
4828 name: &str,
4829 scope: NameScope,
4830 ) -> Result<(), crate::engine::EditorError> {
4831 self.graph
4832 .validate_existing_name(name, scope)
4833 .map_err(crate::engine::EditorError::Excel)?;
4834 {
4835 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
4836 editor.delete_name(name, scope)?;
4837 }
4838 self.record_structural_change(StructuralScope::AllSheets);
4839
4840 self.mark_topology_edited();
4841
4842 Ok(())
4843 }
4844
4845 pub fn edit_with_logger<T>(
4846 &mut self,
4847 log: &mut crate::engine::ChangeLog,
4848 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
4849 ) -> Result<T, crate::engine::EditorError> {
4850 let mut capture = MutationCapture::new(log.current_meta());
4851 let result = self.edit_with_capture(&mut capture, f);
4852 capture.close_compounds();
4853 match result {
4854 Ok(value) => {
4855 log.publish_capture(capture);
4856 Ok(value)
4857 }
4858 Err(error) => {
4859 log.discard_capture(capture);
4860 Err(error)
4861 }
4862 }
4863 }
4864
4865 fn edit_with_capture<T>(
4866 &mut self,
4867 capture: &mut MutationCapture,
4868 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
4869 ) -> Result<T, crate::engine::EditorError> {
4870 let invalidation_baseline = self.invalidation_baseline();
4871 let start_len = capture.len();
4872 let lazy_start = capture.lazy_len();
4873
4874 struct ArrowSpillReader<'a> {
4877 sheets: &'a crate::arrow_store::SheetStore,
4878 }
4879 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
4880 fn read_cell_value(
4881 &self,
4882 sheet: &str,
4883 row: u32,
4884 col: u32,
4885 ) -> Option<formualizer_common::LiteralValue> {
4886 use formualizer_common::LiteralValue;
4887 let asheet = self.sheets.sheet(sheet)?;
4888 let r0 = row.saturating_sub(1) as usize;
4889 let c0 = col.saturating_sub(1) as usize;
4890 let v = asheet.get_cell_value(r0, c0);
4891 if matches!(v, LiteralValue::Empty) {
4892 None
4893 } else {
4894 Some(v)
4895 }
4896 }
4897 }
4898
4899 let ret = {
4900 let spill_reader = ArrowSpillReader {
4901 sheets: &self.arrow_sheets,
4902 };
4903 let mut editor = crate::engine::VertexEditor::with_capture_and_spill_reader(
4904 &mut self.graph,
4905 capture,
4906 &spill_reader,
4907 );
4908 f(&mut editor)
4909 };
4910
4911 let new_events = capture.events()[start_len..].to_vec();
4915 let new_runs = capture.lazy_len() > lazy_start;
4916 if new_events.iter().any(|event| {
4917 matches!(
4918 event,
4919 ChangeEvent::DefineName { .. }
4920 | ChangeEvent::UpdateName { .. }
4921 | ChangeEvent::DeleteName { .. }
4922 )
4923 }) {
4924 let all = capture.expanded_events_from(start_len, lazy_start);
4925 self.rollback_from_change_events(&all, invalidation_baseline)?;
4926 return Err(crate::engine::EditorError::TransactionUnsupported {
4927 reason: "name mutations must use Engine's prepared logged-name APIs".to_string(),
4928 });
4929 }
4930
4931 self.clear_logged_cell_format_states(&new_events);
4934 for ev in &new_events {
4935 self.mirror_forward_change_to_arrow(ev);
4936 }
4937 for ev in &new_events {
4938 self.record_change_for_event(ev);
4939 }
4940
4941 if self.action_depth == 0 {
4944 let mut impact =
4945 Self::classify_change_events(&new_events, LoggedEditDirection::Original);
4946 if new_runs {
4947 impact = impact.max(LoggedEditImpact::Topology);
4948 }
4949 self.apply_logged_edit_impact(impact, invalidation_baseline);
4950 }
4951
4952 Ok(ret)
4953 }
4954
4955 pub(crate) fn preflight_replay_admission(
4956 &mut self,
4957 events: &[ChangeEvent],
4958 forward: bool,
4959 ) -> Result<(), crate::engine::EditorError> {
4960 if !self.graph_admission_enabled() {
4961 return Ok(());
4962 }
4963 let mut vertex_delta = 0i128;
4964 let mut edge_delta = 0i128;
4965 let mut added_vertices = 0usize;
4966 let mut added_edges = 0usize;
4967 let mut formula_cells = BTreeSet::new();
4968 for event in events {
4969 match event {
4970 ChangeEvent::AddVertex {
4971 formula,
4972 coord,
4973 sheet_id,
4974 ..
4975 } => {
4976 let delta = if forward { 1 } else { -1 };
4977 vertex_delta += delta;
4978 if forward {
4979 added_vertices = added_vertices.saturating_add(1);
4980 if formula.is_some() {
4981 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
4982 }
4983 }
4984 }
4985 ChangeEvent::RemoveVertex {
4986 old_formula,
4987 coord,
4988 sheet_id,
4989 ..
4990 } => {
4991 let delta = if forward { -1 } else { 1 };
4992 vertex_delta += delta;
4993 if !forward {
4994 added_vertices = added_vertices.saturating_add(1);
4995 if old_formula.is_some()
4996 && let (Some(sheet_id), Some(coord)) = (sheet_id, coord)
4997 {
4998 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
4999 }
5000 }
5001 }
5002 ChangeEvent::EdgeAdded { .. } => {
5003 let delta = if forward { 1 } else { -1 };
5004 edge_delta += delta;
5005 if forward {
5006 added_edges = added_edges.saturating_add(1);
5007 }
5008 }
5009 ChangeEvent::EdgeRemoved { .. } => {
5010 let delta = if forward { -1 } else { 1 };
5011 edge_delta += delta;
5012 if !forward {
5013 added_edges = added_edges.saturating_add(1);
5014 }
5015 }
5016 ChangeEvent::SetFormula {
5017 addr, old_formula, ..
5018 } => {
5019 if forward || old_formula.is_some() {
5020 formula_cells.insert((addr.sheet_id, addr.coord.row(), addr.coord.col()));
5021 }
5022 }
5023 _ => {}
5024 }
5025 }
5026 let stats = self.graph.baseline_stats();
5027 let final_vertices = i128::try_from(stats.graph_vertex_count)
5028 .ok()
5029 .and_then(|count| count.checked_add(vertex_delta))
5030 .and_then(|count| usize::try_from(count).ok())
5031 .ok_or_else(|| {
5032 crate::engine::EditorError::Excel(
5033 ExcelError::new(ExcelErrorKind::NImpl)
5034 .with_message("replay vertex count overflow"),
5035 )
5036 })?;
5037 let final_edges = i128::try_from(stats.graph_edge_count)
5038 .ok()
5039 .and_then(|count| count.checked_add(edge_delta))
5040 .and_then(|count| usize::try_from(count).ok())
5041 .ok_or_else(|| {
5042 crate::engine::EditorError::Excel(
5043 ExcelError::new(ExcelErrorKind::NImpl)
5044 .with_message("replay edge count overflow"),
5045 )
5046 })?;
5047 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
5048 final_vertices,
5049 final_edges,
5050 materialization_cells: formula_cells.len() as u64,
5051 added_vertices,
5052 added_edges,
5053 })
5054 .map_err(crate::engine::EditorError::Excel)
5055 }
5056
5057 pub fn undo_logged(
5062 &mut self,
5063 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5064 log: &mut crate::engine::ChangeLog,
5065 ) -> Result<(), crate::engine::EditorError> {
5066 let pending_events = log
5067 .last_group_indices()
5068 .into_iter()
5069 .map(|index| log.events()[index].clone())
5070 .collect::<Vec<_>>();
5071 self.preflight_replay_admission(&pending_events, false)?;
5072 let invalidation_baseline = self.invalidation_baseline();
5073 self.invalidate_for_change_events(
5076 &pending_events,
5077 LoggedEditDirection::InverseReplay,
5078 invalidation_baseline,
5079 );
5080 let batch = undo.undo(&mut self.graph, log)?;
5081 for item in batch.iter().rev() {
5082 self.apply_inverse_row_visibility_event(&item.event);
5083 self.apply_inverse_staged_formula_event(&item.event);
5084 }
5085 if !batch.is_empty() {
5086 let events = batch
5087 .iter()
5088 .map(|item| item.event.clone())
5089 .collect::<Vec<_>>();
5090 self.clear_logged_cell_format_states(&events);
5091 }
5092 self.mirror_undo_batch_to_arrow(&batch);
5093 if !batch.is_empty() {
5094 for item in &batch {
5095 self.record_change_for_event(&item.event);
5096 }
5097 }
5098 crate::engine::trace::fz_event!(
5099 tracing::Level::INFO,
5100 "history",
5101 "history.replay",
5102 op = "undo",
5103 events_replayed = batch.len(),
5104 ownership_resyncs = batch.len()
5105 );
5106 Ok(())
5107 }
5108
5109 pub fn redo_logged(
5110 &mut self,
5111 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5112 log: &mut crate::engine::ChangeLog,
5113 ) -> Result<(), crate::engine::EditorError> {
5114 let pending_events = undo.pending_redo_events();
5115 self.preflight_replay_admission(&pending_events, true)?;
5116 let invalidation_baseline = self.invalidation_baseline();
5117 self.invalidate_for_change_events(
5118 &pending_events,
5119 LoggedEditDirection::ForwardReplay,
5120 invalidation_baseline,
5121 );
5122 let batch = undo.redo(&mut self.graph, log)?;
5123 for item in &batch {
5124 self.apply_forward_row_visibility_event(&item.event);
5125 self.apply_forward_staged_formula_event(&item.event);
5126 }
5127 if !batch.is_empty() {
5128 let events = batch
5129 .iter()
5130 .map(|item| item.event.clone())
5131 .collect::<Vec<_>>();
5132 self.clear_logged_cell_format_states(&events);
5133 }
5134 self.mirror_redo_batch_to_arrow(&batch);
5135 if !batch.is_empty() {
5136 for item in &batch {
5137 self.record_change_for_event(&item.event);
5138 }
5139 }
5140 crate::engine::trace::fz_event!(
5141 tracing::Level::INFO,
5142 "history",
5143 "history.replay",
5144 op = "redo",
5145 events_replayed = batch.len(),
5146 ownership_resyncs = batch.len()
5147 );
5148 Ok(())
5149 }
5150
5151 pub fn undo_action(
5155 &mut self,
5156 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5157 ) -> Result<(), crate::engine::EditorError> {
5158 let Some(journal) = undo.pop_undo_action() else {
5159 return Ok(());
5160 };
5161 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, false) {
5162 undo.push_done_action(journal);
5163 return Err(error);
5164 }
5165 let invalidation_baseline = self.invalidation_baseline();
5166
5167 self.invalidate_for_action_journal(
5168 &journal,
5169 LoggedEditDirection::InverseReplay,
5170 invalidation_baseline,
5171 );
5172 journal.graph.undo(&mut self.graph)?;
5173 self.apply_inverse_row_visibility_events(&journal.graph.events);
5174 self.apply_arrow_undo_batch(&journal.arrow, true);
5175 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5176 for event in &journal.graph.events {
5177 self.record_change_for_event(event);
5178 }
5179 }
5180
5181 #[cfg(feature = "tracing")]
5182 let events_replayed = journal.graph.events.len();
5183 crate::engine::trace::fz_event!(
5184 tracing::Level::INFO,
5185 "history",
5186 "history.replay",
5187 op = "undo",
5188 events_replayed,
5189 ownership_resyncs = events_replayed
5190 );
5191 undo.push_redo_action(journal);
5192 Ok(())
5193 }
5194
5195 pub fn redo_action(
5199 &mut self,
5200 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5201 ) -> Result<(), crate::engine::EditorError> {
5202 let Some(journal) = undo.pop_redo_action() else {
5203 return Ok(());
5204 };
5205 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, true) {
5206 undo.push_redo_action(journal);
5207 return Err(error);
5208 }
5209 let invalidation_baseline = self.invalidation_baseline();
5210 self.invalidate_for_action_journal(
5211 &journal,
5212 LoggedEditDirection::ForwardReplay,
5213 invalidation_baseline,
5214 );
5215 journal.graph.redo(&mut self.graph)?;
5216 self.apply_forward_row_visibility_events(&journal.graph.events);
5217 self.apply_arrow_undo_batch(&journal.arrow, false);
5218 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5219 for event in &journal.graph.events {
5220 self.record_change_for_event(event);
5221 }
5222 }
5223
5224 #[cfg(feature = "tracing")]
5225 let events_replayed = journal.graph.events.len();
5226 crate::engine::trace::fz_event!(
5227 tracing::Level::INFO,
5228 "history",
5229 "history.replay",
5230 op = "redo",
5231 events_replayed,
5232 ownership_resyncs = events_replayed
5233 );
5234 undo.push_done_action(journal);
5235 Ok(())
5236 }
5237
5238 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
5239 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
5240 let row = addr.coord.row() + 1;
5242 let col = addr.coord.col() + 1;
5243 (sheet, row, col)
5244 }
5245
5246 fn mirror_undo_batch_to_arrow(
5247 &mut self,
5248 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5249 ) {
5250 for item in batch.iter().rev() {
5252 self.mirror_inverse_change_to_arrow(&item.event);
5253 }
5254 }
5255
5256 fn mirror_redo_batch_to_arrow(
5257 &mut self,
5258 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5259 ) {
5260 for item in batch.iter() {
5262 self.mirror_forward_change_to_arrow(&item.event);
5263 }
5264 }
5265
5266 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5267 use crate::engine::ChangeEvent;
5268 use formualizer_common::LiteralValue;
5269
5270 match ev {
5271 ChangeEvent::SetValue {
5272 addr,
5273 old_value,
5274 old_formula,
5275 ..
5276 } => {
5277 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5278 if old_formula.is_some() {
5279 self.clear_delta_overlay_cell(&sheet, row, col);
5280 } else {
5281 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5282 self.mirror_value_to_overlay(&sheet, row, col, &v);
5283 }
5284 }
5285 ChangeEvent::SetFormula {
5286 addr,
5287 old_value,
5288 old_formula,
5289 ..
5290 } => {
5291 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5292 if old_formula.is_some() {
5293 self.clear_delta_overlay_cell(&sheet, row, col);
5294 } else {
5295 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5296 self.mirror_value_to_overlay(&sheet, row, col, &v);
5297 }
5298 }
5299 ChangeEvent::SpillCommitted { old, new, .. } => {
5300 self.mirror_spill_snapshot(new, true);
5302 if let Some(snap) = old {
5303 self.mirror_spill_snapshot(snap, false);
5304 }
5305 }
5306 ChangeEvent::SpillCleared { old, .. } => {
5307 self.mirror_spill_snapshot(old, false);
5309 }
5310 ChangeEvent::SetRowVisibility { .. } => {
5311 }
5313 _ => {}
5314 }
5315 }
5316
5317 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5318 use crate::engine::ChangeEvent;
5319
5320 match ev {
5321 ChangeEvent::SetValue { addr, new, .. } => {
5322 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5323 self.mirror_value_to_overlay(&sheet, row, col, new);
5324 }
5325 ChangeEvent::SetFormula { addr, .. } => {
5326 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5327 self.clear_delta_overlay_cell(&sheet, row, col);
5328 }
5330 ChangeEvent::SpillCommitted { old, new, .. } => {
5331 if let Some(snap) = old {
5332 self.mirror_spill_snapshot(snap, true);
5333 }
5334 self.mirror_spill_snapshot(new, false);
5335 }
5336 ChangeEvent::SpillCleared { old, .. } => {
5337 self.mirror_spill_snapshot(old, true);
5338 }
5339 ChangeEvent::SetRowVisibility { .. } => {
5340 }
5342 _ => {
5343 }
5345 }
5346 }
5347
5348 fn mirror_spill_snapshot(
5349 &mut self,
5350 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
5351 clear_only: bool,
5352 ) {
5353 use formualizer_common::LiteralValue;
5354
5355 let mut i = 0usize;
5356 for row in &snap.values {
5357 for v in row {
5358 if let Some(cell) = snap.target_cells.get(i) {
5359 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5360 let out = if clear_only {
5361 LiteralValue::Empty
5362 } else {
5363 v.clone()
5364 };
5365 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
5366 }
5367 i += 1;
5368 }
5369 }
5370 if clear_only {
5372 for cell in snap.target_cells.iter().skip(i) {
5373 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5374 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
5375 }
5376 }
5377 }
5378
5379 pub fn set_default_sheet_by_name(&mut self, name: &str) {
5380 self.graph.set_default_sheet_by_name(name);
5381 }
5382
5383 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
5384 self.graph.set_default_sheet_by_id(id);
5385 }
5386
5387 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
5388 self.graph.set_sheet_index_mode(mode);
5389 }
5390
5391 #[cfg(feature = "test-support")]
5392 #[doc(hidden)]
5393 pub fn mark_all_formulas_dirty_for_test(&mut self) {
5394 self.mark_all_formula_vertices_dirty();
5395 }
5396
5397 #[cfg(feature = "test-support")]
5398 #[doc(hidden)]
5399 pub fn take_criteria_mask_work_for_test() -> (usize, usize) {
5400 criteria_mask_test_hooks::take_mask_work()
5401 }
5402
5403 #[cfg(feature = "test-support")]
5404 #[doc(hidden)]
5405 pub fn lookup_index_cache_report_for_test(&self) -> LookupIndexCacheReport {
5406 self.lookup_index_cache.report()
5407 }
5408
5409 #[cfg(any(test, feature = "benchmark_internal"))]
5410 #[doc(hidden)]
5411 pub fn reset_recalc_reuse_probe(&mut self) {
5412 *self.recalc_reuse_probe.get_mut().unwrap() = RecalcReuseProbe::default();
5413 }
5414
5415 #[cfg(any(test, feature = "benchmark_internal"))]
5416 #[doc(hidden)]
5417 pub fn recalc_reuse_probe(&self) -> RecalcReuseProbe {
5418 let mut probe = self.recalc_reuse_probe.lock().unwrap().clone();
5419 if let Some(cached) = self.cached_static_schedule.as_ref() {
5420 let entry_bytes = |e: &CachedScheduleEntry| {
5421 std::mem::size_of::<CachedScheduleEntry>()
5422 + e.candidate_vertices.heap_bytes()
5423 + std::mem::size_of::<crate::engine::Schedule>()
5424 + 2 * std::mem::size_of::<usize>()
5425 + schedule_probe_retained_bytes(&e.schedule)
5426 };
5427 probe.schedule_retained_bytes = entry_bytes(cached)
5428 + self.recent_schedules.iter().map(entry_bytes).sum::<usize>()
5429 + self.base_schedule.as_ref().map_or(0, entry_bytes)
5430 + self.recent_schedules.capacity() * std::mem::size_of::<CachedScheduleEntry>();
5431 }
5432 probe
5433 }
5434
5435 #[cfg(test)]
5436 pub(crate) fn cached_static_schedule_for_test(&self) -> Option<Arc<crate::engine::Schedule>> {
5437 self.cached_static_schedule
5438 .as_ref()
5439 .map(|cached| Arc::clone(&cached.schedule))
5440 }
5441
5442 fn clear_cached_static_schedule(&mut self) {
5443 self.cached_static_schedule = None;
5444 self.recent_schedules.clear();
5445 self.base_schedule = None;
5446 }
5447
5448 fn retain_recent_schedule(&mut self, entry: CachedScheduleEntry) {
5451 const RECENT_SCHEDULES: usize = 8;
5452 const RECENT_SCHEDULE_VERTICES: usize = 65_536;
5453 let revision = self.schedule_cache_authority_revision();
5454 let epoch = self.topology_epoch;
5455 self.recent_schedules
5456 .retain(|e| e.topology_epoch == epoch && e.authority_revision == revision);
5457 let current =
5458 |e: &CachedScheduleEntry| e.topology_epoch == epoch && e.authority_revision == revision;
5459 if self.base_schedule.as_ref().is_some_and(|b| !current(b)) {
5460 self.base_schedule = None;
5461 }
5462 let mut entry = entry;
5465 if current(&entry)
5466 && entry.candidate_vertices.len() > BASE_SCHEDULE_MIN_VERTICES
5467 && self
5468 .base_schedule
5469 .as_ref()
5470 .is_none_or(|b| entry.candidate_vertices.len() > b.candidate_vertices.len())
5471 {
5472 match self.base_schedule.replace(entry) {
5473 Some(previous) => entry = previous,
5474 None => return,
5475 }
5476 }
5477 if entry.topology_epoch != epoch
5478 || entry.authority_revision != revision
5479 || entry.candidate_vertices.len() > RECENT_SCHEDULE_VERTICES / 4
5480 {
5481 return;
5482 }
5483 self.recent_schedules.push(entry);
5484 let mut total: usize = self
5485 .recent_schedules
5486 .iter()
5487 .map(|e| e.candidate_vertices.len())
5488 .sum();
5489 while self.recent_schedules.len() > RECENT_SCHEDULES || total > RECENT_SCHEDULE_VERTICES {
5490 let oldest = self.recent_schedules.remove(0);
5491 total -= oldest.candidate_vertices.len();
5492 }
5493 }
5494
5495 fn invalidation_baseline(&self) -> InvalidationBaseline {
5496 InvalidationBaseline {
5497 snapshot_id: self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed),
5498 topology_epoch: self.topology_epoch,
5499 }
5500 }
5501
5502 fn classify_change_events(
5503 events: &[crate::engine::ChangeEvent],
5504 direction: LoggedEditDirection,
5505 ) -> LoggedEditImpact {
5506 use crate::engine::ChangeEvent;
5507
5508 events
5509 .iter()
5510 .map(|event| match event {
5511 ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
5512 LoggedEditImpact::NoOp
5513 }
5514 ChangeEvent::SetRowVisibility { .. }
5515 | ChangeEvent::SetValue {
5516 old_formula: None,
5517 old_value: Some(_),
5518 ..
5519 } => LoggedEditImpact::DataOnly,
5520 ChangeEvent::SetValue {
5524 old_formula: None,
5525 old_value: None,
5526 ..
5527 } if direction == LoggedEditDirection::Original => LoggedEditImpact::DataOnly,
5528 ChangeEvent::SetValue { .. }
5529 | ChangeEvent::SetFormula { .. }
5530 | ChangeEvent::AddVertex { .. }
5531 | ChangeEvent::RemoveVertex { .. }
5532 | ChangeEvent::VertexMoved { .. }
5533 | ChangeEvent::FormulaAdjusted { .. }
5534 | ChangeEvent::NamedRangeAdjusted { .. }
5535 | ChangeEvent::EdgeAdded { .. }
5536 | ChangeEvent::EdgeRemoved { .. }
5537 | ChangeEvent::DefineName { .. }
5538 | ChangeEvent::UpdateName { .. }
5539 | ChangeEvent::DeleteName { .. }
5540 | ChangeEvent::SpillCommitted { .. }
5541 | ChangeEvent::SpillCleared { .. }
5542 | ChangeEvent::StagedFormulaCellChanged { .. } => LoggedEditImpact::Topology,
5543 })
5544 .max()
5545 .unwrap_or(LoggedEditImpact::NoOp)
5546 }
5547
5548 fn classify_arrow_undo(arrow: &crate::engine::ArrowUndoBatch) -> LoggedEditImpact {
5549 use crate::engine::ArrowOp;
5550
5551 arrow
5552 .ops
5553 .iter()
5554 .map(|op| match op {
5555 ArrowOp::SetDeltaCell { .. } | ArrowOp::SetComputedCell { .. } => {
5556 LoggedEditImpact::DataOnly
5557 }
5558 ArrowOp::RestoreComputedRect { .. }
5559 | ArrowOp::InsertRows { .. }
5560 | ArrowOp::InsertCols { .. } => LoggedEditImpact::Topology,
5561 })
5562 .max()
5563 .unwrap_or(LoggedEditImpact::NoOp)
5564 }
5565
5566 fn apply_logged_edit_impact(
5567 &mut self,
5568 impact: LoggedEditImpact,
5569 baseline: InvalidationBaseline,
5570 ) {
5571 match impact {
5572 LoggedEditImpact::NoOp => {}
5573 LoggedEditImpact::DataOnly => {
5574 if self.topology_epoch == baseline.topology_epoch
5575 && self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
5576 == baseline.snapshot_id
5577 {
5578 self.mark_data_edited();
5579 }
5580 }
5581 LoggedEditImpact::Topology => {
5582 if self.topology_epoch == baseline.topology_epoch {
5585 self.mark_topology_edited();
5586 }
5587 }
5588 }
5589 }
5590
5591 fn invalidate_for_change_events(
5592 &mut self,
5593 events: &[crate::engine::ChangeEvent],
5594 direction: LoggedEditDirection,
5595 baseline: InvalidationBaseline,
5596 ) {
5597 self.apply_logged_edit_impact(Self::classify_change_events(events, direction), baseline);
5598 }
5599
5600 fn invalidate_for_action_journal(
5601 &mut self,
5602 journal: &crate::engine::ActionJournal,
5603 direction: LoggedEditDirection,
5604 baseline: InvalidationBaseline,
5605 ) {
5606 let impact = Self::classify_change_events(&journal.graph.events, direction)
5607 .max(Self::classify_arrow_undo(&journal.arrow));
5608 self.apply_logged_edit_impact(impact, baseline);
5609 }
5610
5611 pub fn mark_data_edited(&mut self) {
5614 self.lookup_index_cache.clear();
5615 self.snapshot_id
5616 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5617 self.has_edited = true;
5618 }
5619
5620 pub fn mark_topology_edited(&mut self) {
5622 self.lookup_index_cache.clear();
5623 self.snapshot_id
5624 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5625 self.topology_epoch = self.topology_epoch.wrapping_add(1);
5626 self.graph.bump_topology_revision();
5627 self.clear_cached_static_schedule();
5628 if let Some(ledger) = self.active_resource_ledger.as_mut() {
5629 let released = ledger.account_mixed_cache(0);
5630 debug_assert!(released.is_ok());
5631 }
5632 self.has_edited = true;
5633 self.graph.authority_sync_eager();
5637 }
5638
5639 fn mark_all_formula_vertices_dirty(&mut self) {
5640 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
5641 for vertex in vertices {
5642 self.graph.mark_vertex_dirty(vertex);
5643 }
5644 }
5645
5646 fn mark_moved_formula_vertices_dirty(
5647 &mut self,
5648 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
5649 ) {
5650 for vertex in &summary.vertices_moved {
5651 if self.graph.has_formula(*vertex) {
5652 self.graph.mark_vertex_dirty(*vertex);
5653 }
5654 }
5655 }
5656
5657 pub fn sheet_store(&self) -> &SheetStore {
5659 &self.arrow_sheets
5660 }
5661
5662 pub(crate) fn has_row_visibility_state(&self) -> bool {
5665 !self.row_visibility.is_empty()
5666 }
5667
5668 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
5670 &mut self.arrow_sheets
5671 }
5672
5673 pub fn has_staged_formulas(&self) -> bool {
5674 !self.staged_formulas.is_empty()
5675 }
5676
5677 pub fn staged_formula_count(&self) -> usize {
5678 self.staged_formulas.values().map(StagedSheet::len).sum()
5679 }
5680
5681 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
5683 self.staged_formulas
5684 .entry(sheet.to_string())
5685 .or_default()
5686 .stage(row, col, text);
5687 self.staged_formula_index.stage(sheet, row, col);
5688 if let Some(sheet) = self.graph.sheet_id(sheet) {
5689 self.invalidate_pending_spills(StructuralScope::Cell {
5690 sheet,
5691 row: row.saturating_sub(1),
5692 col: col.saturating_sub(1),
5693 });
5694 }
5695 }
5696
5697 fn index_removed_staged_sheet(&mut self, sheet: &str, staged: &StagedSheet) {
5698 for (row, col, _) in &staged.entries {
5699 self.staged_formula_index.remove(sheet, *row, *col);
5700 }
5701 if staged.deferred_package.is_some() {
5702 self.staged_formula_index.set_package(sheet, None);
5703 }
5704 }
5705
5706 fn restore_staged_sheet(&mut self, sheet: String, staged: StagedSheet) {
5707 self.staged_formulas.insert(sheet, staged);
5708 }
5709
5710 #[doc(hidden)]
5711 pub fn source_formula_ingress(&mut self) -> SourceFormulaIngress<'_, R> {
5712 SourceFormulaIngress { engine: self }
5713 }
5714
5715 #[doc(hidden)]
5716 #[doc(hidden)]
5719 #[cfg(any(test, feature = "test-support"))]
5720 pub fn set_before_prepared_span_commit_hook(
5721 &mut self,
5722 hook: impl FnOnce() + Send + Sync + 'static,
5723 ) {
5724 self.before_prepared_span_commit_hook = Some(Box::new(hook));
5725 }
5726
5727 #[doc(hidden)]
5728 #[cfg(test)]
5730 pub(crate) fn set_before_target_preparation_commit_hook(
5731 &mut self,
5732 hook: impl FnOnce() + Send + Sync + 'static,
5733 ) {
5734 self.before_target_preparation_commit_hook = Some(Box::new(hook));
5735 }
5736
5737 fn stage_deferred_formula_package(&mut self, package: crate::engine::DeferredFormulaPackage) {
5738 if let Ok(replay) = package.replay.lock()
5739 && let Some(footprint) = replay.selection_cache_footprint()
5740 && !self
5741 .source_cache_footprints
5742 .iter()
5743 .any(|known| known.ptr_eq(&footprint))
5744 {
5745 self.source_cache_footprints.push(footprint);
5746 }
5747 let sheet = package.sheet_name.clone();
5748 let staged = self.staged_formulas.entry(sheet.clone()).or_default();
5749 debug_assert!(staged.deferred_package.is_none());
5750 staged.deferred_package = Some(package);
5751 staged.reconcile_attached_deferred_package();
5752 self.staged_formula_index
5753 .set_package(&sheet, staged.deferred_package.as_ref());
5754 if let Some(sheet_id) = self.graph.sheet_id(&sheet) {
5755 let package = self
5756 .staged_formulas
5757 .get(&sheet)
5758 .and_then(|staged| staged.deferred_package.as_ref());
5759 for &(vertex, anchor, region) in &self.blocked_pending_spills {
5760 if anchor.sheet_id == sheet_id
5761 && self.graph.vertex_exists(vertex)
5762 && self.graph.get_cell_ref(vertex) == Some(anchor)
5763 && self.staged_formula_index.package_occupies_spill(
5764 &sheet,
5765 (anchor.coord.row() + 1, anchor.coord.col() + 1),
5766 (
5767 region.rows.query_bounds().1 + 1,
5768 region.cols.query_bounds().1 + 1,
5769 ),
5770 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
5771 )
5772 {
5773 self.graph.mark_vertex_dirty(vertex);
5774 }
5775 }
5776 }
5777 }
5778
5779 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
5780 let mut removed = None;
5781 let mut remove_sheet = false;
5782 let mut had_package = false;
5783 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
5784 had_package = entries.deferred_package.is_some();
5785 removed = entries.remove(row, col);
5786 remove_sheet = entries.is_empty();
5787 }
5788 let ordinary_removed = self.staged_formula_index.remove(sheet, row, col);
5789 if !ordinary_removed && had_package {
5790 self.staged_formula_index.touch_package(sheet);
5791 }
5792 if remove_sheet {
5793 self.staged_formulas.remove(sheet);
5794 self.staged_formula_index.set_package(sheet, None);
5795 }
5796 if (ordinary_removed || had_package)
5797 && let Some(sheet) = self.graph.sheet_id(sheet)
5798 {
5799 self.invalidate_pending_spills(StructuralScope::Cell {
5800 sheet,
5801 row: row.saturating_sub(1),
5802 col: col.saturating_sub(1),
5803 });
5804 }
5805 removed
5806 }
5807
5808 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
5809 if self.staged_formulas.remove(sheet).is_some() {
5810 self.staged_formula_index.clear_sheet(sheet);
5811 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
5812 self.invalidate_pending_spills(StructuralScope::Sheet(sheet_id));
5813 }
5814 }
5815 }
5816
5817 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
5818 let Some(entries) = self.staged_formulas.remove(old) else {
5819 return;
5820 };
5821 self.staged_formula_index.clear_sheet(old);
5822 let (formulas, mut package) = entries.into_parts();
5823 for (row, col, text) in formulas {
5824 self.stage_formula_text(new, row, col, text);
5825 }
5826 if let Some(package) = package.as_mut() {
5827 package.sheet_name = new.to_string();
5828 }
5829 if let Some(package) = package {
5830 self.stage_deferred_formula_package(package);
5831 }
5832 }
5833
5834 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
5836 self.staged_formulas
5837 .get(sheet)
5838 .and_then(|v| v.get(row, col))
5839 }
5840
5841 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
5842 &self.formula_parse_diagnostics
5843 }
5844
5845 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
5846 std::mem::take(&mut self.formula_parse_diagnostics)
5847 }
5848
5849 pub fn clear_formula_parse_diagnostics(&mut self) {
5850 self.formula_parse_diagnostics.clear();
5851 }
5852
5853 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
5854 self.last_formula_ingest_report.as_ref()
5855 }
5856
5857 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
5858 &self.formula_ingest_report_total
5859 }
5860
5861 #[cfg(test)]
5862 pub(crate) fn set_before_target_planning_snapshot_hook_for_test(
5863 &mut self,
5864 hook: impl FnOnce() + Send + Sync + 'static,
5865 ) {
5866 self.before_target_planning_snapshot_hook = Some(Box::new(hook));
5867 }
5868
5869 #[cfg(test)]
5870 pub(crate) fn inject_target_semantic_stale_once_for_test(&mut self) {
5871 self.inject_target_semantic_stale_once_for_test = true;
5872 }
5873
5874 #[cfg(test)]
5875 pub(crate) fn force_virtual_dep_changes_for_test(&mut self, rounds: usize) {
5876 self.force_virtual_dep_changes_remaining_for_test = rounds;
5877 }
5878
5879 #[cfg(test)]
5880 pub(crate) fn fail_evaluation_commit_preflight_once_for_test(&mut self) {
5881 self.fail_evaluation_commit_preflight_once_for_test = true;
5882 }
5883
5884 #[cfg(test)]
5885 pub(crate) fn set_target_preparation_fault_for_test(
5886 &mut self,
5887 fault: crate::engine::target_preparation::TargetPreparationFault,
5888 ) {
5889 self.target_preparation_fault_for_test = Some(fault);
5890 }
5891
5892 #[cfg(test)]
5893 pub(crate) fn staged_formula_index_revision_for_test(&self) -> u64 {
5894 self.staged_formula_index.revision()
5895 }
5896
5897 #[cfg(test)]
5898 pub(crate) fn deferred_package_for_test(
5899 &self,
5900 sheet: &str,
5901 ) -> &crate::engine::DeferredFormulaPackage {
5902 self.staged_formulas
5903 .get(sheet)
5904 .unwrap()
5905 .deferred_package
5906 .as_ref()
5907 .unwrap()
5908 }
5909
5910 #[cfg(test)]
5911 pub(crate) fn staged_formula_index_is_consistent_for_test(&self) -> bool {
5912 let ordinary_storage = self
5913 .staged_formulas
5914 .values()
5915 .map(|sheet| sheet.entries.len())
5916 .sum::<usize>();
5917 let package_storage = self
5918 .staged_formulas
5919 .values()
5920 .filter(|sheet| sheet.deferred_package.is_some())
5921 .count();
5922 ordinary_storage == self.staged_formula_index.ordinary_count()
5923 && package_storage == self.staged_formula_index.package_count()
5924 && self.staged_formulas.iter().all(|(name, sheet)| {
5925 sheet.entries.iter().all(|(row, col, _)| {
5926 let leases = self
5927 .staged_formula_index
5928 .leases_in_region(name, *row, *col, *row, *col);
5929 leases.len() == 1 && leases[0].row == *row && leases[0].col == *col
5930 })
5931 })
5932 }
5933
5934 #[cfg(test)]
5935 pub(crate) fn evaluation_request_begin_count_for_test(&self) -> u64 {
5936 self.evaluation_request_begin_count_for_test
5937 }
5938
5939 #[cfg(test)]
5940 pub(crate) fn set_before_legacy_fallback_final_provider_sample_hook(
5941 &mut self,
5942 hook: impl FnOnce() + Send + Sync + 'static,
5943 ) {
5944 self.before_legacy_fallback_final_provider_sample_hook = Some(Box::new(hook));
5945 }
5946
5947 #[cfg(test)]
5948 pub(crate) fn set_after_eager_proposal_commit_hook(
5949 &mut self,
5950 hook: impl FnOnce() + Send + Sync + 'static,
5951 ) {
5952 self.after_eager_proposal_commit_hook = Some(Box::new(hook));
5953 }
5954
5955 #[cfg(test)]
5956 pub(crate) fn topology_epoch_for_test(&self) -> u64 {
5957 self.topology_epoch
5958 }
5959
5960 #[cfg(test)]
5961 pub(crate) fn graph_topology_revision_for_test(&self) -> u64 {
5962 self.graph.topology_revision()
5963 }
5964
5965 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
5966 self.formula_ingest_report_total.mode = report.mode;
5967 self.formula_ingest_report_total.accumulate(&report);
5968 self.last_formula_ingest_report = Some(report);
5969 }
5970
5971 fn collect_planning_function_requests(
5972 ast: &ASTNode,
5973 requests: &mut Vec<(String, String, usize)>,
5974 ) {
5975 match &ast.node_type {
5976 ASTNodeType::Function { name, args } => {
5977 requests.push((String::new(), name.clone(), args.len()));
5978 for arg in args {
5979 Self::collect_planning_function_requests(arg, requests);
5980 }
5981 }
5982 ASTNodeType::BinaryOp { left, right, .. } => {
5983 Self::collect_planning_function_requests(left, requests);
5984 Self::collect_planning_function_requests(right, requests);
5985 }
5986 ASTNodeType::UnaryOp { expr, .. } => {
5987 Self::collect_planning_function_requests(expr, requests);
5988 }
5989 ASTNodeType::Call { callee, args } => {
5990 Self::collect_planning_function_requests(callee, requests);
5991 for arg in args {
5992 Self::collect_planning_function_requests(arg, requests);
5993 }
5994 }
5995 ASTNodeType::Array(rows) => {
5996 for cell in rows.iter().flatten() {
5997 Self::collect_planning_function_requests(cell, requests);
5998 }
5999 }
6000 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {}
6001 }
6002 }
6003
6004 fn prepared_function_semantics_changed(
6005 &self,
6006 preparation: &crate::engine::FormulaCompressedPreparation,
6007 guard: &crate::function_registry::SemanticEpochReadGuard,
6008 ) -> bool {
6009 if preparation.function_semantic_epoch == guard.epoch() {
6010 return false;
6011 }
6012
6013 guard
6014 .semantic_changes_affect_requests_since(preparation.function_semantic_epoch, Vec::new())
6015 }
6016
6017 pub(crate) fn prepare_source_formula_families(
6018 &mut self,
6019 sheet_name: &str,
6020 families: &[crate::engine::SourceFormulaFamily],
6021 ) -> crate::engine::FormulaCompressedPreparation {
6022 crate::engine::FormulaCompressedPreparation {
6023 engine_token: Arc::clone(&self.source_formula_token),
6024 function_semantic_epoch: crate::function_registry::semantic_epoch(),
6025 function_provider_revision: None,
6026 function_semantics_used: false,
6027 sheet_name: Arc::from(sheet_name),
6028 rejected: BTreeMap::new(),
6029 eager_replay: Vec::new(),
6030 preparation_spool_replays: 0,
6031 clean_rejected_anchor_counts: [0; 3],
6032 fragmented_rejected_anchor_counts: [0; 3],
6033 exact_replay: None,
6034 replay_disposition: crate::engine::FormulaReplayDisposition::default(),
6035 }
6036 }
6037
6038 fn prepare_source_formula_proposals(
6039 &mut self,
6040 sheet_name: &str,
6041 families: &[crate::engine::SourceFormulaFamily],
6042 authority_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6043 replay_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6044 formula_record_count: u64,
6045 replay: Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6046 suppressed: &BTreeSet<(u32, u32)>,
6047 consumed: &FxHashSet<(crate::engine::SourceFamilyId, crate::engine::SourceCoord)>,
6048 consumed_engine: Option<&Arc<()>>,
6049 ) -> Result<crate::engine::FormulaCompressedPreparation, ExcelError> {
6050 let mut preparation = self.prepare_source_formula_families(sheet_name, families);
6051 preparation.exact_replay = Some(Arc::clone(&replay));
6052 preparation
6053 .replay_disposition
6054 .register_consumed_members(consumed_engine, consumed.iter().copied());
6055 if !preparation
6056 .replay_disposition
6057 .consumed_engine_matches(&self.source_formula_token)
6058 {
6059 return Err(ExcelError::new(ExcelErrorKind::Value)
6060 .with_message("ResidualConsumedEngineMismatch"));
6061 }
6062 preparation
6063 .replay_disposition
6064 .extend_suppressed_excel_coords(suppressed.iter().copied());
6065 Ok(preparation)
6066 }
6067
6068 fn formula_batch_from_exact_replay(
6069 &mut self,
6070 sheet_name: &str,
6071 replayed: impl IntoIterator<Item = crate::engine::DeferredReplayFormula>,
6072 ) -> Result<FormulaIngestBatch, ExcelError> {
6073 let mut cache = rustc_hash::FxHashMap::default();
6074 let mut formulas = Vec::new();
6075 for record in replayed {
6076 let key = if record.text.starts_with('=') {
6077 record.text
6078 } else {
6079 format!("={}", record.text)
6080 };
6081 let ast_id = if let Some(cached) = cache.get(&key) {
6082 *cached
6083 } else {
6084 let parsed = match formualizer_parse::parser::parse(&key) {
6085 Ok(parsed) => parsed,
6086 Err(error) => {
6087 let Some(parsed) = self.handle_formula_parse_error(
6088 sheet_name,
6089 record.row,
6090 record.col,
6091 &key,
6092 error.to_string(),
6093 )?
6094 else {
6095 continue;
6096 };
6097 parsed
6098 }
6099 };
6100 let ast_id = self.intern_formula_ast(&parsed);
6101 cache.insert(key.clone(), ast_id);
6102 ast_id
6103 };
6104 formulas.push(
6105 FormulaIngestRecord::new(record.row, record.col, ast_id, Some(Arc::from(key)))
6106 .with_source_proof(record.source_order, record.family, record.partition_owner),
6107 );
6108 }
6109 Ok(FormulaIngestBatch::new(sheet_name.to_string(), formulas))
6110 }
6111
6112 fn prepare_target_combined_legacy_graph(
6113 &self,
6114 packages: &[PreparedTargetSourcePackage],
6115 ordinary: &[PreparedOrdinaryStagedFormula],
6116 ) -> Result<(PreparedLegacyGraphPlan, usize), ExcelError> {
6117 let mut planned_by_coord = BTreeMap::new();
6118 for package in packages {
6119 for (row, col, ast_id, plan) in &package.legacy {
6120 planned_by_coord.insert((package.sheet_id, *row, *col), (*ast_id, plan.clone()));
6121 }
6122 }
6123 for formula in ordinary {
6124 if let Some((ast_id, plan)) = formula.ast_id.zip(formula.plan.clone()) {
6125 planned_by_coord.insert(
6126 (formula.sheet_id, formula.lease.row, formula.lease.col),
6127 (ast_id, plan),
6128 );
6129 }
6130 }
6131 let planned = planned_by_coord
6132 .into_iter()
6133 .map(|((sheet_id, row, col), (ast_id, plan))| (sheet_id, row, col, ast_id, plan))
6134 .collect::<Vec<_>>();
6135 let formula_count = planned.len();
6136 let graph = self
6137 .graph
6138 .prepare_legacy_graph_plan_multi_sheet(planned)
6139 .map_err(|error| {
6140 ExcelError::new(ExcelErrorKind::Value)
6141 .with_message(format!("target graph preparation failed: {error}"))
6142 })?;
6143 Ok((graph, formula_count))
6144 }
6145
6146 fn replay_target_coordinates(
6147 &mut self,
6148 replay: &Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6149 coordinates: &[(u32, u32)],
6150 deadline: Option<std::time::Instant>,
6151 scratch: &mut u64,
6152 ) -> Result<Option<Vec<crate::engine::DeferredReplayFormula>>, ExcelError> {
6153 if coordinates.is_empty() {
6154 return Ok(Some(Vec::new()));
6155 }
6156 let mut guard = replay.lock().map_err(|_| {
6157 ExcelError::new(ExcelErrorKind::Value)
6158 .with_message("deferred formula spool lock poisoned")
6159 })?;
6160 let retained = guard.selection_cache_footprint().is_some();
6161 let records = guard.replay_selected_exact(coordinates, &mut |work, bytes| {
6162 self.target_preparation_checkpoint(deadline, work)?;
6163 if retained && let Some(ledger) = self.active_resource_ledger.as_mut() {
6164 ledger
6165 .reserve_retained(bytes)
6166 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
6167 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
6168 }
6169 self.reserve_graph_source_scratch(bytes)?;
6170 *scratch = scratch.saturating_add(bytes);
6171 Ok(())
6172 });
6173 drop(guard);
6174 let reconciled = self.reconcile_source_cache_footprints();
6175 let records = records?;
6176 reconciled?;
6177 Ok(records)
6178 }
6179
6180 fn prepare_target_exact_source_selection(
6181 &mut self,
6182 sheet: &str,
6183 lease: StagedPackageLease,
6184 coordinates: Vec<(u32, u32)>,
6185 previous: &BTreeSet<(u32, u32)>,
6186 allow_partial_shared: bool,
6187 deadline: Option<std::time::Instant>,
6188 scratch: &mut u64,
6189 ) -> Result<Option<PreparedTargetSourcePackage>, ExcelError> {
6190 let package = self
6191 .staged_formulas
6192 .get(sheet)
6193 .and_then(|s| s.deferred_package.as_ref())
6194 .unwrap();
6195 if !package.source_geometry_complete
6196 || ((!package.families.is_empty() || !package.partitioned_families.is_empty())
6197 && !package.coordinates_cover_families)
6198 || package.reconciliation_replay.is_some()
6199 {
6200 return Ok(None);
6201 }
6202 let selected_points: BTreeSet<_> = coordinates
6203 .into_iter()
6204 .filter(|point| !package.suppressed.contains(point))
6205 .collect();
6206 let mut prepared = PreparedTargetSourcePackage::empty_selection(
6207 sheet,
6208 self.graph.sheet_id(sheet).unwrap(),
6209 lease,
6210 );
6211 if selected_points.is_empty() {
6212 return Ok(Some(prepared));
6213 }
6214 let mut routing = crate::engine::FormulaReplayDisposition::default();
6215 for partition in &package.partitioned_families {
6216 routing
6217 .register_partition(partition, false)
6218 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?;
6219 }
6220 routing.extend_suppressed_excel_coords(package.suppressed.iter().copied());
6221 routing.register_consumed_members(
6222 package.consumed_engine.as_ref(),
6223 package.consumed_members.iter().copied(),
6224 );
6225 let replay = Arc::clone(&package.replay);
6226 let source_report = package.accounting_report();
6227 prepared.source_report = source_report;
6228 prepared.disposition = routing.clone();
6229
6230 let replay_points: BTreeSet<_> = selected_points
6231 .iter()
6232 .copied()
6233 .filter(|&(row, col)| !prepared.direct_contains(row, col))
6234 .collect();
6235 let coordinates: Vec<_> = replay_points.iter().copied().collect();
6236 let records = self.replay_target_coordinates(&replay, &coordinates, deadline, scratch)?;
6237 prepared.spool_replays = u64::from(!coordinates.is_empty());
6238 let Some(mut replay_records) = records else {
6239 return Ok(None);
6240 };
6241 for record in &mut replay_records {
6242 if record.family.is_none() {
6243 record.partition_owner = routing
6244 .ordinary_disposition(crate::engine::SourceCoord {
6245 row: record.row.saturating_sub(1),
6246 col: record.col.saturating_sub(1),
6247 })
6248 .1;
6249 }
6250 }
6251 replay_records.sort_by_key(|record| record.source_order);
6252 if replay_records
6253 .iter()
6254 .any(|record| !selected_points.contains(&(record.row, record.col)))
6255 || replay_records
6256 .windows(2)
6257 .any(|records| records[0].source_order == records[1].source_order)
6258 {
6259 return Err(ExcelError::new(ExcelErrorKind::Value)
6260 .with_message("invalid indexed exact source selection"));
6261 }
6262 let represented: BTreeSet<_> = replay_records.iter().map(|r| (r.row, r.col)).collect();
6263 if represented != replay_points {
6264 return Err(ExcelError::new(ExcelErrorKind::Value)
6265 .with_message("incomplete indexed exact source selection"));
6266 }
6267 let source = self
6270 .staged_formulas
6271 .get(sheet)
6272 .unwrap()
6273 .deferred_package
6274 .as_ref()
6275 .unwrap();
6276 let contains = |rect: crate::engine::SourceRect, coord: crate::engine::SourceCoord| {
6277 coord.row >= rect.start.row
6278 && coord.row <= rect.end.row
6279 && coord.col >= rect.start.col
6280 && coord.col <= rect.end.col
6281 };
6282 let mut checked = BTreeSet::new();
6283 for record in replay_records.iter().rev() {
6284 if !checked.insert((record.row, record.col)) {
6285 continue;
6286 }
6287 let coord = crate::engine::SourceCoord {
6288 row: record.row - 1,
6289 col: record.col - 1,
6290 };
6291 let agrees = |owner, shared: bool| {
6292 record.family == shared.then_some(owner)
6293 && record.partition_owner.or(record.family) == Some(owner)
6294 };
6295 let mut valid = true;
6296 for family in &source.families {
6297 let owns = match &family.members {
6298 crate::engine::SourceFamilyMembers::CompleteDomain(domain) => {
6299 contains(domain.rect(), coord)
6300 }
6301 crate::engine::SourceFamilyMembers::ExplicitMembers(members) => {
6302 members.as_slice().binary_search(&coord).is_ok()
6303 }
6304 };
6305 if owns && !agrees(family.source_id, true) {
6306 valid = false;
6307 }
6308 }
6309 for family in &source.partitioned_families {
6310 if family
6311 .fragments
6312 .iter()
6313 .any(|fragment| contains(fragment.rect(), coord))
6314 && !agrees(family.source_id, true)
6315 {
6316 valid = false;
6317 }
6318 if let Some(member) = family
6319 .legacy_members
6320 .as_slice()
6321 .iter()
6322 .find(|member| member.coord == coord)
6323 && !agrees(
6324 family.source_id,
6325 member.kind == crate::engine::PartitionLegacyMemberKind::SharedFamilyMember,
6326 )
6327 {
6328 valid = false;
6329 }
6330 }
6331 if !valid {
6332 return Err(ExcelError::new(ExcelErrorKind::Value)
6333 .with_message("indexed source ownership mismatch"));
6334 }
6335 }
6336 prepared.selected_points = Some(selected_points);
6337 prepared.replay_records = replay_records;
6338 Ok(Some(prepared))
6339 }
6340
6341 fn prepare_target_source_package(
6342 &mut self,
6343 sheet: &str,
6344 lease: StagedPackageLease,
6345 deadline: Option<std::time::Instant>,
6346 ) -> Result<PreparedTargetSourcePackage, ExcelError> {
6347 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
6348 ExcelError::new(ExcelErrorKind::Ref)
6349 .with_message(format!("deferred source sheet not found: {sheet}"))
6350 })?;
6351 let (
6352 source_report,
6353 families,
6354 partitions,
6355 replay,
6356 invalidated,
6357 suppressed,
6358 consumed_members,
6359 consumed_engine,
6360 reconciliation_replay,
6361 ) = {
6362 let package = self
6363 .staged_formulas
6364 .get(sheet)
6365 .and_then(|staged| staged.deferred_package.as_ref())
6366 .ok_or_else(|| {
6367 ExcelError::new(ExcelErrorKind::Value)
6368 .with_message("staged deferred source package is unavailable")
6369 })?;
6370 if package.sheet_name != sheet {
6371 return Err(ExcelError::new(ExcelErrorKind::Value)
6372 .with_message("deferred formula package sheet mismatch"));
6373 }
6374 (
6375 package.accounting_report(),
6376 package.families.clone(),
6377 package.partitioned_families.clone(),
6378 Arc::clone(&package.replay),
6379 package.invalidated.clone(),
6380 package.suppressed.clone(),
6381 package.consumed_members.clone(),
6382 package.consumed_engine.clone(),
6383 package.reconciliation_replay.clone(),
6384 )
6385 };
6386
6387 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
6388 for partition in &partitions {
6389 replay_disposition
6390 .register_partition(partition, false)
6391 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
6392 }
6393 replay_disposition.extend_suppressed_excel_coords(suppressed.iter().copied());
6394 replay_disposition
6395 .register_consumed_members(consumed_engine.as_ref(), consumed_members.iter().copied());
6396 if !replay_disposition.consumed_engine_matches(&self.source_formula_token) {
6397 return Err(ExcelError::new(ExcelErrorKind::Value)
6398 .with_message("ResidualConsumedEngineMismatch"));
6399 }
6400 self.target_preparation_checkpoint(deadline, 1)?;
6401 let mut replay_records = if let Some(mut records) = reconciliation_replay {
6402 records.retain(|record| {
6403 let Some((row, col)) = record.row.checked_sub(1).zip(record.col.checked_sub(1))
6404 else {
6405 return true;
6406 };
6407 let coord = crate::engine::SourceCoord { row, col };
6408 let disposition = record.family.map_or_else(
6409 || replay_disposition.ordinary_disposition(coord).0,
6410 |family| replay_disposition.shared_disposition(family, coord),
6411 );
6412 !matches!(
6413 disposition,
6414 crate::engine::FormulaReplayCoordinateDisposition::Direct
6415 | crate::engine::FormulaReplayCoordinateDisposition::Suppressed
6416 )
6417 });
6418 records
6419 } else {
6420 replay
6421 .lock()
6422 .map_err(|_| {
6423 ExcelError::new(ExcelErrorKind::Value)
6424 .with_message("deferred formula spool lock poisoned")
6425 })?
6426 .replay_partitioned(&replay_disposition, &partitions)
6427 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?
6428 };
6429 replay_records.sort_by_key(|record| record.source_order);
6430 for chunk in replay_records.chunks(256) {
6431 self.target_preparation_checkpoint(deadline, chunk.len() as u64)?;
6432 }
6433 if replay_records
6434 .windows(2)
6435 .any(|records| records[0].source_order == records[1].source_order)
6436 {
6437 return Err(ExcelError::new(ExcelErrorKind::Value)
6438 .with_message("duplicate deferred source-order proof"));
6439 }
6440
6441 let disposition = replay_disposition;
6442 let direct_families = 0usize;
6443 let direct_cells = 0u64;
6444 let direct_fragments = 0u64;
6445 let direct_complete_families = 0u64;
6446 let direct_complete_cells = 0u64;
6447 let direct_partition_families = 0u64;
6448 let direct_partition_cells = 0u64;
6449 let anchor_parses = 0u64;
6450 let anchor_asts = 0u64;
6451 let anchor_analyses = 0u64;
6452
6453 Ok(PreparedTargetSourcePackage {
6454 sheet: sheet.to_string(),
6455 sheet_id,
6456 lease,
6457 selected_points: None,
6458 complete_selections: Default::default(),
6459 deferred_shared: false,
6460 direct_domains: Vec::new(),
6461 source_report,
6462 replay_records,
6463 spool_replays: 1,
6464 disposition,
6465 legacy: Vec::new(),
6466 direct_families,
6467 direct_cells,
6468 direct_fragments,
6469 direct_complete_families,
6470 direct_complete_cells,
6471 direct_partition_families,
6472 direct_partition_cells,
6473 anchor_parses,
6474 anchor_asts,
6475 anchor_analyses,
6476 })
6477 }
6478
6479 fn fallback_planning_snapshot(
6480 &self,
6481 batch: &FormulaIngestBatch,
6482 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
6483 let mut requests = Vec::new();
6484 for formula in &batch.formulas {
6485 let ast = self
6486 .graph
6487 .data_store()
6488 .retrieve_ast(formula.ast_id, self.graph.sheet_reg())
6489 .ok_or_else(|| {
6490 ExcelError::new(ExcelErrorKind::Value)
6491 .with_message("ordered fallback AST is unavailable")
6492 })?;
6493 Self::collect_planning_function_requests(&ast, &mut requests);
6494 }
6495 requests.sort();
6496 requests.dedup();
6497 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
6498 &self.resolver,
6499 requests,
6500 )
6501 .map_err(|error| ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}")))
6502 }
6503
6504 fn prepare_legacy_batch_fallback(
6505 &mut self,
6506 batch: FormulaIngestBatch,
6507 function_provider: &dyn crate::traits::FunctionProvider,
6508 ) -> Result<
6509 (
6510 crate::engine::graph::prepared_legacy_graph::PreparedLegacyGraphPlan,
6511 u64,
6512 ),
6513 ExcelError,
6514 > {
6515 let formula_count = batch.formulas.len() as u64;
6516 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
6517 let mut planned = Vec::with_capacity(batch.formulas.len());
6518 for record in batch.formulas {
6519 let placement = CellRef::new(
6520 sheet_id,
6521 Coord::from_excel(record.row, record.col, true, true),
6522 );
6523 let ingested = self
6524 .graph
6525 .ingest_pipeline(function_provider)
6526 .enable_function_semantics()
6527 .ingest_formula(
6528 FormulaAstInput::RawArena(record.ast_id),
6529 placement,
6530 record.formula_text,
6531 )
6532 .map_err(|error| {
6533 ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}"))
6534 })?;
6535 planned.push((record.row, record.col, ingested.ast_id, ingested.dep_plan));
6536 }
6537 let plan = self
6538 .graph
6539 .prepare_legacy_graph_plan(sheet_id, planned)
6540 .map_err(|error| {
6541 ExcelError::new(ExcelErrorKind::Value).with_message(error.to_string())
6542 })?;
6543 Ok((plan, formula_count))
6544 }
6545
6546 fn publish_compressed_partial_report(
6547 &mut self,
6548 report: &FormulaIngestReport,
6549 direct_report: &FormulaIngestReport,
6550 ) {
6551 if direct_report.source_family_promoted == 0
6552 && direct_report.graph_formula_cells_materialized == 0
6553 {
6554 return;
6555 }
6556 let mut published = report.clone();
6557 published.accumulate(direct_report);
6558 self.record_formula_ingest_report(published);
6559 }
6560
6561 fn finish_compressed_formula_sources(
6562 &mut self,
6563 batches: Vec<(
6564 FormulaIngestBatch,
6565 crate::engine::FormulaCompressedSourceReport,
6566 crate::engine::FormulaCompressedPreparation,
6567 )>,
6568 ) -> Result<FormulaIngestReport, ExcelError> {
6569 self.observe_function_semantic_epoch()?;
6570 if batches.iter().any(|(_, _, preparation)| {
6571 !Arc::ptr_eq(&preparation.engine_token, &self.source_formula_token)
6572 }) {
6573 return Err(ExcelError::new(ExcelErrorKind::Value)
6574 .with_message("compressed source preparation belongs to another engine"));
6575 }
6576 if batches.iter().any(|(fallback, _, preparation)| {
6577 preparation.sheet_name.as_ref() != fallback.sheet_name
6578 }) {
6579 return Err(ExcelError::new(ExcelErrorKind::Value)
6580 .with_message("compressed source preparation sheet mismatch"));
6581 }
6582 let initial_guard = crate::function_registry::semantic_epoch_read_guard();
6583 let initial_provider_revision = self.resolver.planning_semantic_revision();
6584 let mut fallback_batches = Vec::with_capacity(batches.len());
6585 let mut stale_fallback_batches = Vec::new();
6586 let mut pending_preparations = Vec::new();
6587 for (mut fallback, mut source, mut preparation) in batches {
6588 for formula in fallback.formulas.drain(..) {
6589 let source_order = formula.source_order.ok_or_else(|| {
6590 ExcelError::new(ExcelErrorKind::Value).with_message(
6591 "compressed source supplied formulas without source-order proof",
6592 )
6593 })?;
6594 let text = formula.formula_text.ok_or_else(|| {
6595 ExcelError::new(ExcelErrorKind::Value).with_message(
6596 "ordered compressed fallback formula has no exact source text",
6597 )
6598 })?;
6599 preparation
6600 .eager_replay
6601 .push(crate::engine::DeferredReplayFormula {
6602 source_order,
6603 row: formula.row,
6604 col: formula.col,
6605 text: text.to_string(),
6606 family: formula.source_family,
6607 partition_owner: formula.partition_owner,
6608 });
6609 }
6610 preparation
6611 .eager_replay
6612 .sort_by_key(|record| record.source_order);
6613 source.source_spool_replays = source
6614 .source_spool_replays
6615 .saturating_add(preparation.preparation_spool_replays);
6616 let stale_reason = preparation
6617 .function_semantics_used
6618 .then(|| {
6619 if preparation.function_provider_revision != initial_provider_revision {
6620 Some("FunctionProviderRevisionChanged")
6621 } else if self.prepared_function_semantics_changed(&preparation, &initial_guard)
6622 {
6623 Some("FunctionSemanticEpochChanged")
6624 } else {
6625 None
6626 }
6627 })
6628 .flatten();
6629 let stale_semantics = stale_reason.is_some();
6630 for reason in preparation.rejected.values() {
6631 *source.fallback_reasons.entry(reason.clone()).or_default() += 1;
6632 }
6633 source.replay_families = source.families_seen;
6635 source.replay_cells = source.family_cells_seen;
6636 let compressed = crate::engine::FormulaCompressedSourceBatch::new(
6637 fallback.sheet_name.clone(),
6638 source,
6639 );
6640 if stale_semantics {
6641 stale_fallback_batches.push((fallback, compressed));
6642 } else {
6643 fallback_batches.push((fallback, compressed));
6644 }
6645 pending_preparations.push((preparation, stale_semantics));
6646 }
6647 drop(initial_guard);
6648
6649 let configured_mode = self.config.formula_plane_mode;
6651 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6652 let stale_result =
6653 self.ingest_compressed_formula_source_batches_inner(stale_fallback_batches, false);
6654 self.config.formula_plane_mode = configured_mode;
6655 let mut report = stale_result?;
6656 report.mode = configured_mode;
6657
6658 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6659 let fallback_result =
6660 self.ingest_compressed_formula_source_batches_inner(fallback_batches, false);
6661 self.config.formula_plane_mode = configured_mode;
6662 match fallback_result {
6663 Ok(fallback_report) => report.accumulate(&fallback_report),
6664 Err(error) => {
6665 self.record_formula_ingest_report(report);
6666 return Err(error);
6667 }
6668 }
6669
6670 let mut direct_report =
6671 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
6672 for (preparation, _) in &pending_preparations {
6673 direct_report.source_anchor_parses = direct_report.source_anchor_parses.saturating_add(
6674 preparation.clean_rejected_anchor_counts[0]
6675 .saturating_add(preparation.fragmented_rejected_anchor_counts[0]),
6676 );
6677 direct_report.source_anchor_asts = direct_report.source_anchor_asts.saturating_add(
6678 preparation.clean_rejected_anchor_counts[1]
6679 .saturating_add(preparation.fragmented_rejected_anchor_counts[1]),
6680 );
6681 direct_report.source_anchor_analyses =
6682 direct_report.source_anchor_analyses.saturating_add(
6683 preparation.clean_rejected_anchor_counts[2]
6684 .saturating_add(preparation.fragmented_rejected_anchor_counts[2]),
6685 );
6686 }
6687 loop {
6688 #[cfg(any(test, feature = "test-support"))]
6689 if let Some(hook) = self.before_prepared_span_commit_hook.take() {
6690 hook();
6691 }
6692 let commit_guard = crate::function_registry::semantic_epoch_read_guard();
6693 let commit_provider_revision = self.resolver.planning_semantic_revision();
6694 let mut newly_stale = Vec::new();
6695 let mut current = Vec::new();
6696 for pending in pending_preparations.drain(..) {
6697 let stale_reason = pending
6698 .0
6699 .function_semantics_used
6700 .then(|| {
6701 if pending.0.function_provider_revision != commit_provider_revision {
6702 Some("FunctionProviderRevisionChanged")
6703 } else if self
6704 .prepared_function_semantics_changed(&pending.0, &commit_guard)
6705 {
6706 Some("FunctionSemanticEpochChanged")
6707 } else {
6708 None
6709 }
6710 })
6711 .flatten();
6712 if let Some(reason) = stale_reason {
6713 newly_stale.push((pending, reason));
6714 } else {
6715 current.push(pending);
6716 }
6717 }
6718 if !newly_stale.is_empty() {
6719 drop(commit_guard);
6720 for ((mut preparation, was_initially_stale), reason) in newly_stale {
6721 preparation
6722 .eager_replay
6723 .sort_by_key(|record| record.source_order);
6724 direct_report.source_spool_replays =
6725 direct_report.source_spool_replays.saturating_add(1);
6726 if !was_initially_stale {
6727 direct_report
6728 .fallback_reasons
6729 .entry(reason.to_string())
6730 .or_default();
6731 }
6732 preparation.function_semantics_used = false;
6733 if !preparation.eager_replay.is_empty() {
6734 current.push((preparation, false));
6735 }
6736 }
6737 pending_preparations = current;
6738 continue;
6739 }
6740 drop(commit_guard);
6741
6742 enum SourceProposal {
6744 KnownFallback {
6745 source_order: crate::engine::SourceFormulaOrder,
6746 records: Vec<crate::engine::DeferredReplayFormula>,
6747 },
6748 }
6749
6750 impl SourceProposal {
6751 fn source_order(&self) -> crate::engine::SourceFormulaOrder {
6752 match self {
6753 Self::KnownFallback { source_order, .. } => *source_order,
6754 }
6755 }
6756 }
6757
6758 for (mut preparation, _) in current {
6759 let mut proposals = Vec::with_capacity(preparation.eager_replay.len());
6760 let mut family_fallbacks: BTreeMap<_, Vec<_>> = BTreeMap::new();
6761 for record in preparation.eager_replay.drain(..) {
6762 if let Some(owner) = record.partition_owner.or(record.family) {
6763 family_fallbacks.entry(owner).or_default().push(record);
6764 } else {
6765 proposals.push(SourceProposal::KnownFallback {
6766 source_order: record.source_order,
6767 records: vec![record],
6768 });
6769 }
6770 }
6771 for (_, mut records) in family_fallbacks {
6772 records.sort_by_key(|record| record.source_order);
6773 if records
6774 .windows(2)
6775 .any(|window| window[0].source_order == window[1].source_order)
6776 {
6777 self.publish_compressed_partial_report(&report, &direct_report);
6778 return Err(ExcelError::new(ExcelErrorKind::Value)
6779 .with_message("duplicate exact-replay source-order proof"));
6780 }
6781 let Some(source_order) = records.first().map(|record| record.source_order)
6782 else {
6783 self.publish_compressed_partial_report(&report, &direct_report);
6784 return Err(ExcelError::new(ExcelErrorKind::Value)
6785 .with_message("empty exact-replay fallback family"));
6786 };
6787 proposals.push(SourceProposal::KnownFallback {
6788 source_order,
6789 records,
6790 });
6791 }
6792 proposals.sort_by_key(SourceProposal::source_order);
6793 if proposals
6794 .windows(2)
6795 .any(|window| window[0].source_order() == window[1].source_order())
6796 {
6797 self.publish_compressed_partial_report(&report, &direct_report);
6798 return Err(ExcelError::new(ExcelErrorKind::Value)
6799 .with_message("ambiguous compressed source-order proof"));
6800 }
6801
6802 for proposal in proposals {
6803 match proposal {
6804 SourceProposal::KnownFallback { records, .. } => {
6805 let batch = match self.formula_batch_from_exact_replay(
6806 preparation.sheet_name.as_ref(),
6807 records,
6808 ) {
6809 Ok(batch) => batch,
6810 Err(error) => {
6811 self.publish_compressed_partial_report(&report, &direct_report);
6812 return Err(error);
6813 }
6814 };
6815 if batch.is_empty() {
6816 continue;
6817 }
6818 let snapshot = match self.fallback_planning_snapshot(&batch) {
6819 Ok(snapshot) => snapshot,
6820 Err(error) => {
6821 self.publish_compressed_partial_report(&report, &direct_report);
6822 return Err(error);
6823 }
6824 };
6825 let commit_guard =
6826 crate::function_registry::semantic_epoch_read_guard();
6827 let provider_revision_initial =
6828 self.resolver.planning_semantic_revision();
6829 if (commit_guard.epoch() != snapshot.epoch()
6830 && snapshot.semantic_changes_affect_requests_since_guarded(
6831 &commit_guard,
6832 snapshot.epoch(),
6833 ))
6834 || snapshot.provider_revision().is_some_and(|revision| {
6835 Some(revision) != provider_revision_initial
6836 })
6837 {
6838 drop(commit_guard);
6839 self.publish_compressed_partial_report(&report, &direct_report);
6840 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6841 "ordered fallback planning snapshot became stale",
6842 ));
6843 }
6844 let (plan, formula_count) = match self
6845 .prepare_legacy_batch_fallback(batch, &snapshot)
6846 {
6847 Ok(plan) => plan,
6848 Err(error) => {
6849 drop(commit_guard);
6850 self.publish_compressed_partial_report(&report, &direct_report);
6851 return Err(error);
6852 }
6853 };
6854 let provider_revision_after =
6855 self.resolver.planning_semantic_revision();
6856 if provider_revision_after != provider_revision_initial {
6857 drop(commit_guard);
6858 self.publish_compressed_partial_report(&report, &direct_report);
6859 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6860 "function provider changed while preparing ordered fallback",
6861 ));
6862 }
6863 let graph_vertices = plan.new_vertex_count();
6864 let Some(graph_edges) = plan.planned_edge_count() else {
6865 drop(commit_guard);
6866 self.publish_compressed_partial_report(&report, &direct_report);
6867 return Err(ExcelError::new(ExcelErrorKind::Value)
6868 .with_message("prepared ordered fallback size overflow"));
6869 };
6870 if let Err(error) = self.prepared_legacy_admission(&plan, formula_count)
6871 {
6872 drop(commit_guard);
6873 self.publish_compressed_partial_report(&report, &direct_report);
6874 return Err(error);
6875 }
6876 if let Err(error) =
6877 self.graph.validate_prepared_legacy_graph_plan(&plan)
6878 {
6879 drop(commit_guard);
6880 self.publish_compressed_partial_report(&report, &direct_report);
6881 return Err(ExcelError::new(ExcelErrorKind::Value)
6882 .with_message(error.to_string()));
6883 }
6884 #[cfg(test)]
6885 if let Some(hook) = self
6886 .before_legacy_fallback_final_provider_sample_hook
6887 .take()
6888 {
6889 hook();
6890 }
6891 let provider_revision_final =
6892 self.resolver.planning_semantic_revision();
6893 if provider_revision_final != provider_revision_initial {
6894 drop(commit_guard);
6895 self.publish_compressed_partial_report(&report, &direct_report);
6896 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6897 "function provider changed after ordered fallback validation",
6898 ));
6899 }
6900 let graph_formulas =
6901 self.graph.apply_prevalidated_legacy_graph_plan(plan);
6902 direct_report.formula_cells_seen = direct_report
6903 .formula_cells_seen
6904 .saturating_add(formula_count);
6905 direct_report.graph_formula_cells_materialized = direct_report
6906 .graph_formula_cells_materialized
6907 .saturating_add(graph_formulas as u64);
6908 direct_report.graph_vertices_created = direct_report
6909 .graph_vertices_created
6910 .saturating_add(graph_vertices as u64);
6911 direct_report.graph_edges_created = direct_report
6912 .graph_edges_created
6913 .saturating_add(graph_edges as u64);
6914 }
6915 }
6916 #[cfg(test)]
6917 if let Some(hook) = self.after_eager_proposal_commit_hook.take() {
6918 hook();
6919 }
6920 }
6921 }
6922 break;
6923 }
6924 report.accumulate(&direct_report);
6925 self.record_formula_ingest_report(report.clone());
6926 Ok(report)
6927 }
6928 pub(crate) fn ingest_compressed_formula_source_batches(
6930 &mut self,
6931 batches: Vec<(
6932 FormulaIngestBatch,
6933 crate::engine::FormulaCompressedSourceBatch,
6934 )>,
6935 ) -> Result<FormulaIngestReport, ExcelError> {
6936 self.ingest_compressed_formula_source_batches_inner(batches, true)
6937 }
6938
6939 fn ingest_compressed_formula_source_batches_inner(
6940 &mut self,
6941 batches: Vec<(
6942 FormulaIngestBatch,
6943 crate::engine::FormulaCompressedSourceBatch,
6944 )>,
6945 publish_report: bool,
6946 ) -> Result<FormulaIngestReport, ExcelError> {
6947 let mut source_counts = [0_u64; 11];
6948 let mut source_report = crate::engine::FormulaCompressedSourceReport::default();
6949 let mut formula_batches = Vec::with_capacity(batches.len());
6950 let mut compressed_families = Vec::new();
6951 let mut partitioned_families = Vec::new();
6952 for (batch, compressed_batch) in batches {
6953 let (sheet_name, compressed, families, partitions) = compressed_batch.into_parts();
6954 if sheet_name.as_ref() != batch.sheet_name {
6955 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
6956 "compressed formula source sheet does not match its replay batch",
6957 ));
6958 }
6959 compressed_families.push((batch.sheet_name.clone(), families));
6960 partitioned_families.push((batch.sheet_name.clone(), partitions));
6961 source_counts[0] = source_counts[0].saturating_add(compressed.source_formula_events);
6962 source_counts[1] = source_counts[1].saturating_add(compressed.source_ordinary_events);
6963 source_counts[2] =
6964 source_counts[2].saturating_add(compressed.source_shared_anchor_events);
6965 source_counts[3] =
6966 source_counts[3].saturating_add(compressed.source_shared_descendant_events);
6967 source_counts[4] = source_counts[4].saturating_add(compressed.source_unknown_events);
6968 source_counts[5] =
6969 source_counts[5].saturating_add(compressed.source_formula_records_spooled);
6970 source_counts[6] =
6971 source_counts[6].saturating_add(compressed.source_spool_encoded_bytes);
6972 source_counts[7] = source_counts[7].max(compressed.source_spool_peak_memory_bytes);
6973 source_counts[8] =
6974 source_counts[8].saturating_add(compressed.source_spool_spilled_bytes);
6975 source_counts[9] = source_counts[9].saturating_add(compressed.source_spool_spill_files);
6976 source_counts[10] = source_counts[10].saturating_add(compressed.source_spool_replays);
6977 source_report.families_seen = source_report
6978 .families_seen
6979 .saturating_add(compressed.families_seen);
6980 source_report.family_cells_seen = source_report
6981 .family_cells_seen
6982 .saturating_add(compressed.family_cells_seen);
6983 source_report.source_clean_families = source_report
6984 .source_clean_families
6985 .saturating_add(compressed.source_clean_families);
6986 source_report.source_clean_cells = source_report
6987 .source_clean_cells
6988 .saturating_add(compressed.source_clean_cells);
6989 source_report.source_fragmentable_families = source_report
6990 .source_fragmentable_families
6991 .saturating_add(compressed.source_fragmentable_families);
6992 source_report.source_fragmentable_cells = source_report
6993 .source_fragmentable_cells
6994 .saturating_add(compressed.source_fragmentable_cells);
6995 source_report.source_fragment_count = source_report
6996 .source_fragment_count
6997 .saturating_add(compressed.source_fragment_count);
6998 source_report.source_isolated_fallback_cells = source_report
6999 .source_isolated_fallback_cells
7000 .saturating_add(compressed.source_isolated_fallback_cells);
7001 source_report.source_hole_exclusions = source_report
7002 .source_hole_exclusions
7003 .saturating_add(compressed.source_hole_exclusions);
7004 source_report.source_ordinary_exclusions = source_report
7005 .source_ordinary_exclusions
7006 .saturating_add(compressed.source_ordinary_exclusions);
7007 source_report.source_partition_failures = source_report
7008 .source_partition_failures
7009 .saturating_add(compressed.source_partition_failures);
7010 source_report.replay_families = source_report
7011 .replay_families
7012 .saturating_add(compressed.replay_families);
7013 source_report.replay_cells = source_report
7014 .replay_cells
7015 .saturating_add(compressed.replay_cells);
7016 source_report.forward_descendants = source_report
7017 .forward_descendants
7018 .saturating_add(compressed.forward_descendants);
7019 source_report.evidence_limit_fallbacks = source_report
7020 .evidence_limit_fallbacks
7021 .saturating_add(compressed.evidence_limit_fallbacks);
7022 source_report.evidence_peak_bytes = source_report
7023 .evidence_peak_bytes
7024 .max(compressed.evidence_peak_bytes);
7025 for (reason, count) in compressed.fallback_reasons {
7026 *source_report.fallback_reasons.entry(reason).or_default() += count;
7027 }
7028 formula_batches.push(batch);
7029 }
7030 self.ingest_formula_batches_inner(
7031 formula_batches,
7032 source_counts,
7033 Some(source_report),
7034 compressed_families,
7035 partitioned_families,
7036 publish_report,
7037 )
7038 }
7039
7040 pub fn ingest_formula_batches(
7041 &mut self,
7042 batches: Vec<FormulaIngestBatch>,
7043 ) -> Result<FormulaIngestReport, ExcelError> {
7044 let has_formulas = batches.iter().any(|batch| !batch.formulas.is_empty());
7045 let report = self.ingest_formula_batches_inner(
7046 batches,
7047 [0; 11],
7048 None,
7049 Vec::new(),
7050 Vec::new(),
7051 true,
7052 )?;
7053 if has_formulas {
7054 self.mark_topology_edited();
7055 }
7056 Ok(report)
7057 }
7058
7059 fn ingest_formula_batches_unpublished(
7060 &mut self,
7061 batches: Vec<FormulaIngestBatch>,
7062 ) -> Result<FormulaIngestReport, ExcelError> {
7063 self.ingest_formula_batches_inner(batches, [0; 11], None, Vec::new(), Vec::new(), false)
7064 }
7065
7066 fn ingest_formula_batches_inner(
7067 &mut self,
7068 batches: Vec<FormulaIngestBatch>,
7069 source_counts: [u64; 11],
7070 source_report: Option<crate::engine::FormulaCompressedSourceReport>,
7071 compressed_families: Vec<(String, Vec<crate::engine::SourceFormulaFamily>)>,
7072 partitioned_families: Vec<(String, Vec<crate::engine::PartitionedSourceFormulaFamily>)>,
7073 publish_report: bool,
7074 ) -> Result<FormulaIngestReport, ExcelError> {
7075 self.observe_function_semantic_epoch()?;
7076 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
7077 #[cfg(feature = "tracing")]
7078 let arena_nodes_before = self.graph.data_store().memory_usage().total_ast_nodes;
7079 #[cfg(feature = "tracing")]
7080 let route = if !partitioned_families.is_empty() {
7081 "partitioned"
7082 } else if !compressed_families.is_empty() {
7083 "compressed_source"
7084 } else if source_report.is_some() {
7085 "replay"
7086 } else {
7087 "ordinary"
7088 };
7089 let _ingest_span = crate::engine::trace::fz_span!(
7090 tracing::Level::INFO,
7091 "ingest",
7092 "ingest.batch",
7093 mode = ?FormulaPlaneMode::Off,
7094 route,
7095 formula_cells = formula_cells_seen
7096 );
7097 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
7098 let materialize_batches = batches;
7099 report.formula_cells_seen = formula_cells_seen;
7100 report.source_formula_events = source_counts[0];
7101 report.source_ordinary_events = source_counts[1];
7102 report.source_shared_anchor_events = source_counts[2];
7103 report.source_shared_descendant_events = source_counts[3];
7104 report.source_unknown_events = source_counts[4];
7105 report.source_formula_records_spooled = source_counts[5];
7106 report.source_spool_encoded_bytes = source_counts[6];
7107 report.source_spool_peak_memory_bytes = source_counts[7];
7108 report.source_spool_spilled_bytes = source_counts[8];
7109 report.source_spool_spill_files = source_counts[9];
7110 report.source_spool_replays = source_counts[10];
7111 if let Some(source) = source_report {
7112 report.source_families_seen = source.families_seen;
7113 report.source_family_cells_seen = source.family_cells_seen;
7114 report.source_family_shadow_eligible = source.source_clean_families;
7115 report.source_family_shadow_eligible_cells = source.source_clean_cells;
7116 report.source_partitioned_families_seen = source.source_fragmentable_families;
7117 report.source_partition_holes = source.source_hole_exclusions;
7118 report.source_partition_ordinary_exceptions = source.source_ordinary_exclusions;
7119 report.source_partition_failures = source.source_partition_failures;
7120 report.source_partition_surviving_cells = source.source_fragmentable_cells;
7121 report.source_family_fallback = report
7122 .source_family_fallback
7123 .saturating_add(source.replay_families);
7124 report.source_family_fallback_cells = report
7125 .source_family_fallback_cells
7126 .saturating_add(source.replay_cells);
7127 report.source_forward_descendants = source.forward_descendants;
7128 report.source_evidence_limit_fallbacks = source.evidence_limit_fallbacks;
7129 report.source_evidence_peak_bytes = source.evidence_peak_bytes;
7130 for (reason, count) in source.fallback_reasons {
7131 let total = report.fallback_reasons.entry(reason).or_default();
7132 *total = total.saturating_add(count);
7133 }
7134 }
7135
7136 if self.graph.first_load_assume_new()
7140 && !self.graph_admission_enabled()
7141 && !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
7142 {
7143 let mut builder =
7144 crate::engine::ingest_builder::BulkIngestBuilder::new(&mut self.graph);
7145 for batch in materialize_batches {
7146 if batch.is_empty() {
7147 continue;
7148 }
7149 let sheet_id = builder
7150 .add_sheet_checked(&batch.sheet_name)
7151 .ok_or_else(|| {
7152 ExcelError::new(ExcelErrorKind::Ref)
7153 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7154 })?;
7155 builder.add_formula_refs(
7156 sheet_id,
7157 batch.formulas.into_iter().map(|record| {
7158 (
7159 record.row,
7160 record.col,
7161 crate::engine::graph::FormulaRef::of_ingested(
7162 record.ast_id,
7163 record.member_anchor,
7164 ),
7165 )
7166 }),
7167 );
7168 }
7169 let summary = builder.finish_with_provider(&self.resolver)?;
7170 report.graph_formula_cells_materialized = summary.formulas as u64;
7171 report.graph_vertices_created = summary.vertices as u64;
7172 report.graph_edges_created = summary.edges as u64;
7173 } else if !materialize_batches.iter().all(FormulaIngestBatch::is_empty) {
7174 let mut prepared_by_sheet: BTreeMap<String, Vec<_>> = BTreeMap::new();
7175 for batch in materialize_batches {
7176 if batch.is_empty() {
7177 continue;
7178 }
7179 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
7180 ExcelError::new(ExcelErrorKind::Ref)
7181 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7182 })?;
7183 let mut pipeline = self.ingest_pipeline();
7184 let prepared = prepared_by_sheet.entry(batch.sheet_name).or_default();
7187 prepared.reserve(batch.formulas.len());
7188 for record in batch.formulas {
7189 let placement = CellRef::new(
7190 sheet_id,
7191 Coord::from_excel(record.row, record.col, true, true),
7192 );
7193 let input = match record.member_anchor {
7194 Some(anchor) => FormulaAstInput::Member {
7195 template: record.ast_id,
7196 anchor,
7197 },
7198 None => FormulaAstInput::RawArena(record.ast_id),
7199 };
7200 let formula = pipeline.ingest_formula(input, placement, None)?;
7201 prepared.push((
7202 record.row,
7203 record.col,
7204 crate::engine::graph::FormulaRef::of_ingested(
7205 formula.ast_id,
7206 formula.member_anchor,
7207 ),
7208 formula.dep_plan,
7209 ));
7210 }
7211 }
7212 let admission_preflighted = self.graph_admission_enabled();
7213 if admission_preflighted {
7214 let mut preview = Vec::new();
7215 for (sheet_name, formulas) in &prepared_by_sheet {
7216 let sheet_id = self.graph.sheet_id(sheet_name).ok_or_else(|| {
7217 ExcelError::new(ExcelErrorKind::Ref)
7218 .with_message(format!("unknown ingest sheet: {sheet_name}"))
7219 })?;
7220 preview.extend(
7221 formulas
7222 .iter()
7223 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone())),
7224 );
7225 }
7226 let admission = self.graph.preview_formula_mutations(&preview)?;
7227 self.preflight_graph_admission(admission)?;
7228 }
7229
7230 let mut builder = self.begin_bulk_ingest();
7231 if admission_preflighted {
7232 builder.mark_admission_preflighted();
7233 }
7234 for (sheet_name, formulas) in prepared_by_sheet {
7235 if formulas.is_empty() {
7236 continue;
7237 }
7238 let sheet_id = builder.add_sheet(&sheet_name);
7239 builder.add_formula_plans(sheet_id, formulas);
7240 }
7241 let summary = builder.finish()?;
7242 report.graph_formula_cells_materialized = summary.formulas as u64;
7243 report.graph_vertices_created = summary.vertices as u64;
7244 report.graph_edges_created = summary.edges as u64;
7245 }
7246
7247 crate::engine::trace::fz_event!(
7248 tracing::Level::INFO,
7249 "ingest",
7250 "ingest.summary",
7251 candidate_cells = report.shadow_candidate_cells,
7252 accepted_span_cells = report.shadow_accepted_span_cells,
7253 fallback_cells = report.shadow_fallback_cells,
7254 spans_created = report.shadow_spans_created,
7255 templates_interned = report.shadow_templates_interned,
7256 graph_vertices_created = report.graph_vertices_created,
7257 graph_edges_created = report.graph_edges_created,
7258 arena_nodes_delta = self
7259 .graph
7260 .data_store()
7261 .memory_usage()
7262 .total_ast_nodes
7263 .saturating_sub(arena_nodes_before)
7264 );
7265 if publish_report {
7266 self.record_formula_ingest_report(report.clone());
7267 }
7268 Ok(report)
7269 }
7270
7271 fn dedup_formula_parse_diagnostics_since(&mut self, start: usize) {
7272 let mut unique = Vec::new();
7273 for diagnostic in self.formula_parse_diagnostics.drain(start..) {
7274 let duplicate = unique.iter().any(|prior: &FormulaParseDiagnostic| {
7275 prior.sheet == diagnostic.sheet
7276 && prior.row == diagnostic.row
7277 && prior.col == diagnostic.col
7278 && prior.formula == diagnostic.formula
7279 && prior.policy == diagnostic.policy
7280 });
7281 if !duplicate {
7282 unique.push(diagnostic);
7283 }
7284 }
7285 self.formula_parse_diagnostics.extend(unique);
7286 }
7287
7288 pub fn handle_formula_parse_error(
7289 &mut self,
7290 sheet: &str,
7291 row: u32,
7292 col: u32,
7293 formula: &str,
7294 message: String,
7295 ) -> Result<Option<ASTNode>, ExcelError> {
7296 let policy = self.config.formula_parse_policy;
7297
7298 if policy == FormulaParsePolicy::Strict {
7299 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
7300 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
7301 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
7302 )));
7303 }
7304
7305 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
7306 sheet: sheet.to_string(),
7307 row,
7308 col,
7309 formula: formula.to_string(),
7310 message: message.clone(),
7311 policy,
7312 });
7313
7314 match policy {
7315 FormulaParsePolicy::Strict => unreachable!(),
7316 FormulaParsePolicy::KeepCachedValue => Ok(None),
7317 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
7318 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
7319 None,
7320 ))),
7321 FormulaParsePolicy::CoerceToError => {
7322 let err = ExcelError::new(ExcelErrorKind::Error)
7323 .with_message(format!("Malformed formula: {message}"));
7324 Ok(Some(ASTNode::new(
7325 ASTNodeType::Literal(LiteralValue::Error(err)),
7326 None,
7327 )))
7328 }
7329 }
7330 }
7331
7332 #[cfg(test)]
7333 fn target_preparation_fault(
7334 &mut self,
7335 seam: crate::engine::target_preparation::TargetPreparationFault,
7336 ) -> Result<(), ExcelError> {
7337 if self.target_preparation_fault_for_test == Some(seam) {
7338 self.target_preparation_fault_for_test = None;
7339 Err(ExcelError::new(ExcelErrorKind::Value)
7340 .with_message(format!("injected target preparation fault: {seam:?}")))
7341 } else {
7342 Ok(())
7343 }
7344 }
7345
7346 fn preparation_stale(
7347 reason: formualizer_common::PreparationStaleReason,
7348 message: impl Into<String>,
7349 ) -> ExcelError {
7350 ExcelError::new(ExcelErrorKind::Value)
7351 .with_message(message)
7352 .with_extra(formualizer_common::ExcelErrorExtra::PreparationStale { reason })
7353 }
7354
7355 fn preparation_revision_stale_reason(
7356 assumptions: &crate::engine::PreparationRevision,
7357 current: &crate::engine::PreparationRevision,
7358 planning_requests: &BTreeSet<(String, String, usize)>,
7359 staged_leases_match: bool,
7360 ) -> Option<formualizer_common::PreparationStaleReason> {
7361 if assumptions.graph != current.graph {
7362 Some(formualizer_common::PreparationStaleReason::Graph)
7363 } else if assumptions.staged != current.staged || !staged_leases_match {
7364 Some(formualizer_common::PreparationStaleReason::Staged)
7365 } else if assumptions.symbols != current.symbols {
7366 Some(formualizer_common::PreparationStaleReason::Symbols)
7367 } else if assumptions.provider != current.provider {
7368 Some(formualizer_common::PreparationStaleReason::Provider)
7369 } else if assumptions.semantic != current.semantic
7370 && crate::function_registry::semantic_changes_affect_requests_since(
7371 assumptions.semantic,
7372 planning_requests.iter().cloned(),
7373 )
7374 {
7375 Some(formualizer_common::PreparationStaleReason::Semantic)
7376 } else {
7377 None
7378 }
7379 }
7380
7381 fn preparation_revisions(&self) -> crate::engine::PreparationRevision {
7382 crate::engine::PreparationRevision {
7383 graph: self.graph.topology_revision(),
7384 authority: 0,
7385 authority_indexes: 0,
7386 authority_indexed_plane: 0,
7387 staged: self.staged_formula_index.revision(),
7388 symbols: self.graph.symbol_revision(),
7389 semantic: crate::function_registry::semantic_epoch(),
7390 provider: self.resolver.planning_semantic_revision(),
7391 }
7392 }
7393
7394 fn planning_revision_snapshot(&self) -> PlanningRevisionSnapshot {
7395 let registry_guard = crate::function_registry::semantic_epoch_read_guard();
7396 let provider = self.resolver.planning_semantic_revision();
7397 let semantic = registry_guard.epoch();
7398 PlanningRevisionSnapshot {
7399 engine_topology_epoch: self.topology_epoch,
7400 graph_topology_revision: self.graph.topology_revision(),
7401 staged: self.staged_formula_index.revision(),
7402 symbols: self.graph.symbol_revision(),
7403 semantic,
7404 provider,
7405 deterministic_mode: self.config.deterministic_mode.clone(),
7406 budgets: self.evaluation_resource_budgets.clone(),
7407 }
7408 }
7409
7410 fn recalc_plan_key(&self) -> RecalcPlanKey {
7411 RecalcPlanKey {
7412 engine_token: Arc::clone(&self.recalc_plan_token),
7413 revisions: self.planning_revision_snapshot(),
7414 }
7415 }
7416
7417 fn plan_stale(reason: formualizer_common::PlanStaleReason) -> ExcelError {
7418 ExcelError::new(ExcelErrorKind::Value)
7419 .with_message(format!("recalculation plan is stale: {}", reason.as_str()))
7420 .with_extra(formualizer_common::ExcelErrorExtra::PlanStale { reason })
7421 }
7422
7423 fn validate_recalc_plan_key(&self, key: &RecalcPlanKey) -> Result<(), ExcelError> {
7424 use formualizer_common::PlanStaleReason;
7425
7426 if !Arc::ptr_eq(&key.engine_token, &self.recalc_plan_token) {
7427 return Err(Self::plan_stale(PlanStaleReason::Engine));
7428 }
7429
7430 let current = self.planning_revision_snapshot();
7431 let expected = &key.revisions;
7432 let stale = if expected.provider != current.provider {
7433 Some(PlanStaleReason::Provider)
7434 } else if expected.semantic != current.semantic {
7435 Some(PlanStaleReason::Semantic)
7436 } else if expected.budgets != current.budgets
7437 || expected.deterministic_mode != current.deterministic_mode
7438 {
7439 Some(PlanStaleReason::Budget)
7440 } else if expected.staged != current.staged {
7441 Some(PlanStaleReason::Staged)
7442 } else if expected.symbols != current.symbols {
7443 Some(PlanStaleReason::Symbols)
7444 } else if expected.graph_topology_revision != current.graph_topology_revision
7445 || expected.engine_topology_epoch != current.engine_topology_epoch
7446 {
7447 Some(PlanStaleReason::Graph)
7448 } else {
7449 None
7450 };
7451 stale.map_or(Ok(()), |reason| Err(Self::plan_stale(reason)))
7452 }
7453
7454 fn target_preparation_checkpoint(
7455 &mut self,
7456 deadline: Option<std::time::Instant>,
7457 work: u64,
7458 ) -> Result<(), ExcelError> {
7459 if self
7460 .active_cancel_flag
7461 .as_ref()
7462 .is_some_and(|cancel| cancel.is_cancelled())
7463 {
7464 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
7465 .with_message("target graph preparation cancelled"));
7466 }
7467 if deadline.is_some_and(|deadline| std::time::Instant::now() >= deadline) {
7468 return Err(crate::engine::ResourceLedgerError::Exhausted(
7469 formualizer_common::ResourceExhaustionDetail {
7470 reason: formualizer_common::ResourceExhaustionReason::Deadline,
7471 limit: 0,
7472 observed: 1,
7473 request_id: self
7474 .active_evaluation_resource_request
7475 .as_ref()
7476 .map(|stats| stats.request_id),
7477 },
7478 )
7479 .into_excel_error());
7480 }
7481 self.charge_bounded_work(work)
7482 }
7483
7484 fn opaque_reason_in_ast(
7485 &self,
7486 ast: &ASTNode,
7487 provider: &dyn crate::traits::FunctionProvider,
7488 ) -> Option<crate::engine::OpaqueReason> {
7489 match &ast.node_type {
7490 ASTNodeType::Function { name, args } => {
7491 let canonical = name.rsplit('.').next().unwrap_or(name).to_ascii_uppercase();
7492 if canonical == "INDIRECT" {
7493 return Some(crate::engine::OpaqueReason::RuntimeTextReference);
7494 }
7495 let Some(function) = provider.get_function_for_planning("", name) else {
7496 return Some(crate::engine::OpaqueReason::UnknownFunction);
7497 };
7498 let caps = function.caps();
7499 if caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7500 || caps.contains(FnCaps::RETURNS_REFERENCE)
7501 {
7502 return Some(crate::engine::OpaqueReason::DynamicReference);
7503 }
7504 args.iter()
7505 .find_map(|arg| self.opaque_reason_in_ast(arg, provider))
7506 }
7507 ASTNodeType::Call { .. } => Some(crate::engine::OpaqueReason::UnknownCustomFunction),
7508 ASTNodeType::UnaryOp { expr, .. } => self.opaque_reason_in_ast(expr, provider),
7509 ASTNodeType::BinaryOp { left, right, .. } => self
7510 .opaque_reason_in_ast(left, provider)
7511 .or_else(|| self.opaque_reason_in_ast(right, provider)),
7512 ASTNodeType::Array(rows) => rows
7513 .iter()
7514 .flat_map(|row| row.iter())
7515 .find_map(|item| self.opaque_reason_in_ast(item, provider)),
7516 ASTNodeType::Reference {
7517 reference:
7518 ReferenceType::Cell {
7519 sheet: Some(sheet), ..
7520 }
7521 | ReferenceType::Range {
7522 sheet: Some(sheet), ..
7523 },
7524 ..
7525 } if self.graph.sheet_id(sheet).is_none() => {
7526 Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding)
7527 }
7528 ASTNodeType::Reference {
7529 reference:
7530 ReferenceType::External(_)
7531 | ReferenceType::Cell3D { .. }
7532 | ReferenceType::Range3D { .. },
7533 ..
7534 } => Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding),
7535 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => None,
7536 }
7537 }
7538
7539 fn target_planning_snapshot(
7540 &mut self,
7541 ast: &ASTNode,
7542 planning_requests: &mut BTreeSet<(String, String, usize)>,
7543 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
7544 #[cfg(test)]
7545 if std::mem::take(&mut self.inject_target_semantic_stale_once_for_test) {
7546 return Err(Self::preparation_stale(
7547 formualizer_common::PreparationStaleReason::Semantic,
7548 "injected target semantic preparation movement",
7549 ));
7550 }
7551 #[cfg(test)]
7552 if let Some(hook) = self.before_target_planning_snapshot_hook.take() {
7553 hook();
7554 }
7555 let mut requests = Vec::new();
7556 Self::collect_planning_function_requests(ast, &mut requests);
7557 requests.sort();
7558 requests.dedup();
7559 planning_requests.extend(requests.iter().cloned());
7560 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
7561 &self.resolver,
7562 requests,
7563 )
7564 .map_err(|error| {
7565 ExcelError::new(ExcelErrorKind::Value)
7566 .with_message(format!("target planning snapshot unavailable: {error:?}"))
7567 })
7568 }
7569
7570 fn target_planning_snapshot_stale_reason(
7571 snapshot: &crate::function_registry::RegistryPlanningSnapshot,
7572 assumptions: &crate::engine::PreparationRevision,
7573 ) -> Option<formualizer_common::PreparationStaleReason> {
7574 if snapshot
7575 .provider_revision()
7576 .is_some_and(|revision| Some(revision) != assumptions.provider)
7577 {
7578 Some(formualizer_common::PreparationStaleReason::Provider)
7579 } else if snapshot.epoch() != assumptions.semantic
7580 && snapshot.semantic_changes_affect_requests_since(assumptions.semantic)
7581 {
7582 Some(formualizer_common::PreparationStaleReason::Semantic)
7583 } else {
7584 None
7585 }
7586 }
7587
7588 fn widen_target_preparation(
7589 policy: crate::engine::OpaquePreparePolicy,
7590 scope: &mut crate::engine::PrepareScope,
7591 reasons: &mut Vec<crate::engine::OpaqueReason>,
7592 reason: crate::engine::OpaqueReason,
7593 ) -> Result<bool, ExcelError> {
7594 if policy == crate::engine::OpaquePreparePolicy::Error {
7595 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7596 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7597 }
7598 if !reasons.contains(&reason) {
7599 reasons.push(reason);
7600 }
7601 if !matches!(scope, crate::engine::PrepareScope::Workbook) {
7602 *scope = crate::engine::PrepareScope::Workbook;
7603 Ok(true)
7604 } else {
7605 Ok(false)
7606 }
7607 }
7608
7609 fn widen_target_preparation_to_sheet(
7610 policy: crate::engine::OpaquePreparePolicy,
7611 scope: &mut crate::engine::PrepareScope,
7612 reasons: &mut Vec<crate::engine::OpaqueReason>,
7613 reason: crate::engine::OpaqueReason,
7614 sheet: &str,
7615 ) -> Result<bool, ExcelError> {
7616 if policy == crate::engine::OpaquePreparePolicy::Error {
7617 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7618 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7619 }
7620 if !reasons.contains(&reason) {
7621 reasons.push(reason);
7622 }
7623 match scope {
7624 crate::engine::PrepareScope::Exact => {
7625 *scope = crate::engine::PrepareScope::Sheets(vec![sheet.to_string()]);
7626 Ok(true)
7627 }
7628 crate::engine::PrepareScope::Sheets(sheets) => {
7629 if sheets.iter().any(|candidate| candidate == sheet) {
7630 Ok(false)
7631 } else {
7632 sheets.push(sheet.to_string());
7633 sheets.sort();
7634 Ok(true)
7635 }
7636 }
7637 crate::engine::PrepareScope::Workbook => Ok(false),
7638 }
7639 }
7640
7641 fn ast_has_proven_sheet_local_dynamic(
7642 ast: &ASTNode,
7643 provider: &dyn crate::traits::FunctionProvider,
7644 ) -> bool {
7645 fn classify(ast: &ASTNode, provider: &dyn crate::traits::FunctionProvider) -> (bool, bool) {
7646 match &ast.node_type {
7647 ASTNodeType::Function { name, args } => {
7648 let Some(function) = provider.get_function_for_planning("", name) else {
7649 return (false, false);
7650 };
7651 let caps = function.caps();
7652 let dynamic = caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7653 || caps.contains(FnCaps::RETURNS_REFERENCE);
7654 let canonical = name.rsplit('.').next().unwrap_or(name);
7655 if dynamic
7656 && !canonical.eq_ignore_ascii_case("OFFSET")
7657 && !canonical.eq_ignore_ascii_case("INDEX")
7658 {
7659 return (false, true);
7660 }
7661 let mut has_dynamic = dynamic;
7662 for arg in args {
7663 let (safe, child_dynamic) = classify(arg, provider);
7664 if !safe {
7665 return (false, has_dynamic || child_dynamic);
7666 }
7667 has_dynamic |= child_dynamic;
7668 }
7669 (true, has_dynamic)
7670 }
7671 ASTNodeType::UnaryOp { expr, .. } => classify(expr, provider),
7672 ASTNodeType::BinaryOp { left, right, .. } => {
7673 let (left_safe, left_dynamic) = classify(left, provider);
7674 let (right_safe, right_dynamic) = classify(right, provider);
7675 (left_safe && right_safe, left_dynamic || right_dynamic)
7676 }
7677 ASTNodeType::Array(rows) => {
7678 let mut has_dynamic = false;
7679 for item in rows.iter().flatten() {
7680 let (safe, child_dynamic) = classify(item, provider);
7681 if !safe {
7682 return (false, has_dynamic || child_dynamic);
7683 }
7684 has_dynamic |= child_dynamic;
7685 }
7686 (true, has_dynamic)
7687 }
7688 ASTNodeType::Reference {
7689 reference:
7690 ReferenceType::Cell { sheet: None, .. }
7691 | ReferenceType::Range { sheet: None, .. },
7692 ..
7693 }
7694 | ASTNodeType::Literal(_)
7695 | ASTNodeType::Omitted => (true, false),
7696 ASTNodeType::Call { .. } | ASTNodeType::Reference { .. } => (false, false),
7697 }
7698 }
7699
7700 let (safe, dynamic) = classify(ast, provider);
7701 safe && dynamic
7702 }
7703
7704 fn table_selection_region(
7705 &self,
7706 entry: &crate::engine::graph::TableEntry,
7707 selection: &crate::engine::TableSelection,
7708 ) -> Result<PreparationRegion, ExcelError> {
7709 let mut start_row = entry.range.start.coord.row() + 1;
7710 let mut end_row = entry.range.end.coord.row() + 1;
7711 let mut start_col = entry.range.start.coord.col() + 1;
7712 let mut end_col = entry.range.end.coord.col() + 1;
7713 match selection {
7714 crate::engine::TableSelection::Whole => {}
7715 crate::engine::TableSelection::Headers => {
7716 if !entry.header_row {
7717 return Err(ExcelError::new(ExcelErrorKind::Value)
7718 .with_message(format!("table {} has no header row", entry.name)));
7719 }
7720 end_row = start_row;
7721 }
7722 crate::engine::TableSelection::Data => {
7723 if entry.header_row {
7724 start_row = start_row.saturating_add(1);
7725 }
7726 if entry.totals_row {
7727 end_row = end_row.saturating_sub(1);
7728 }
7729 }
7730 crate::engine::TableSelection::Totals => {
7731 if !entry.totals_row {
7732 return Err(ExcelError::new(ExcelErrorKind::Value)
7733 .with_message(format!("table {} has no totals row", entry.name)));
7734 }
7735 start_row = end_row;
7736 }
7737 crate::engine::TableSelection::Column(column) => {
7738 let index = entry.col_index(column).ok_or_else(|| {
7739 ExcelError::new(ExcelErrorKind::Name)
7740 .with_message(format!("unknown table column: {column}"))
7741 })?;
7742 start_col = start_col.saturating_add(index as u32);
7743 end_col = start_col;
7744 }
7745 crate::engine::TableSelection::Columns { start, end } => {
7746 let first = entry.col_index(start).ok_or_else(|| {
7747 ExcelError::new(ExcelErrorKind::Name)
7748 .with_message(format!("unknown table column: {start}"))
7749 })?;
7750 let last = entry.col_index(end).ok_or_else(|| {
7751 ExcelError::new(ExcelErrorKind::Name)
7752 .with_message(format!("unknown table column: {end}"))
7753 })?;
7754 if first > last {
7755 return Err(ExcelError::new(ExcelErrorKind::Value)
7756 .with_message("table column selection is reversed"));
7757 }
7758 start_col = start_col.saturating_add(first as u32);
7759 end_col = entry
7760 .range
7761 .start
7762 .coord
7763 .col()
7764 .saturating_add(last as u32)
7765 .saturating_add(1);
7766 }
7767 }
7768 if start_row > end_row && matches!(selection, crate::engine::TableSelection::Data) {
7769 start_row = entry.range.start.coord.row() + 1;
7770 end_row = start_row;
7771 }
7772 if start_row > end_row || start_col > end_col {
7773 return Err(
7774 ExcelError::new(ExcelErrorKind::Value).with_message("table selection is empty")
7775 );
7776 }
7777 Ok(PreparationRegion {
7778 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
7779 sheet_id: entry.sheet_id(),
7780 start_row,
7781 start_col,
7782 end_row,
7783 end_col,
7784 })
7785 }
7786
7787 #[cfg(test)]
7788 pub(crate) fn reset_target_root_dedup_probes_for_test() {
7789 TARGET_ROOT_DEDUP_PROBES.with(|probes| probes.set(0));
7790 }
7791
7792 #[cfg(test)]
7793 pub(crate) fn target_root_dedup_probes_for_test() -> usize {
7794 TARGET_ROOT_DEDUP_PROBES.with(std::cell::Cell::get)
7795 }
7796
7797 pub(crate) fn resolve_target_producers(
7798 &mut self,
7799 targets: &[crate::engine::EvaluationTarget],
7800 ) -> Result<Vec<crate::engine::target_preparation::TargetProducer>, ExcelError> {
7801 use crate::engine::target_preparation::TargetProducer;
7802
7803 let request_id = self
7804 .active_evaluation_resource_request
7805 .as_ref()
7806 .map(|request| request.request_id);
7807 let mut roots = OrderedTargetProducers::with_capacity(targets.len())
7808 .map_err(|_| target_root_allocation_error(targets.len(), request_id))?;
7809 let resolve_region = |engine: &mut Self,
7810 region: Region,
7811 value_only: Option<CellRef>,
7812 roots: &mut OrderedTargetProducers|
7813 -> Result<(), ExcelError> {
7814 let before = roots.len();
7815 for anchor in engine.graph.spill_anchors_in_region(
7816 region.sheet_id(),
7817 region.axis_ranges().0.query_bounds().0,
7818 region.axis_ranges().1.query_bounds().0,
7819 region.axis_ranges().0.query_bounds().1,
7820 region.axis_ranges().1.query_bounds().1,
7821 ) {
7822 roots
7823 .push(TargetProducer::Legacy(anchor))
7824 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7825 }
7826 for vertex in engine.graph.vertices_in_region(
7827 region.sheet_id(),
7828 region.axis_ranges().0.query_bounds().0,
7829 region.axis_ranges().0.query_bounds().1,
7830 region.axis_ranges().1.query_bounds().0,
7831 region.axis_ranges().1.query_bounds().1,
7832 ) {
7833 let vertex = engine
7834 .graph
7835 .get_cell_ref(vertex)
7836 .and_then(|cell| engine.graph.spill_registry_anchor_for_cell(cell))
7837 .unwrap_or(vertex);
7838 if matches!(
7843 engine.graph.get_vertex_kind(vertex),
7844 VertexKind::FormulaScalar | VertexKind::FormulaArray
7845 ) {
7846 roots
7847 .push(TargetProducer::Legacy(vertex))
7848 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7849 }
7850 }
7851 if roots.len() == before
7852 && let Some(cell) = value_only
7853 {
7854 roots
7855 .push(TargetProducer::ValueOnly(cell))
7856 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
7857 }
7858 Ok(())
7859 };
7860
7861 for target in targets {
7862 match target {
7863 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
7864 if *row == 0 || *col == 0 {
7865 return Err(ExcelError::new(ExcelErrorKind::Ref)
7866 .with_message("target cell coordinates are one-based"));
7867 }
7868 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
7869 ExcelError::new(ExcelErrorKind::Ref)
7870 .with_message(format!("target sheet not found: {sheet}"))
7871 })?;
7872 let cell = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
7873 resolve_region(
7874 self,
7875 Region::point(sheet_id, *row - 1, *col - 1),
7876 Some(cell),
7877 &mut roots,
7878 )?;
7879 }
7880 crate::engine::EvaluationTarget::Range(range) => {
7881 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
7882 ExcelError::new(ExcelErrorKind::Ref)
7883 .with_message(format!("target sheet not found: {}", range.sheet))
7884 })?;
7885 resolve_region(
7886 self,
7887 Region::rect(
7888 sheet_id,
7889 range.start_row - 1,
7890 range.end_row - 1,
7891 range.start_col - 1,
7892 range.end_col - 1,
7893 ),
7894 None,
7895 &mut roots,
7896 )?;
7897 }
7898 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
7899 let scope = self.name_query_scope(scope_sheet.as_deref())?;
7900 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, scope) {
7901 roots
7902 .push(TargetProducer::Symbol(entry.vertex))
7903 .map_err(|_| {
7904 target_root_allocation_error(roots.len() + 1, request_id)
7905 })?;
7906 }
7907 }
7908 crate::engine::EvaluationTarget::Table { name, .. } => {
7909 if let Some(entry) = self.graph.resolve_table_entry(name) {
7910 roots
7911 .push(TargetProducer::Symbol(entry.vertex))
7912 .map_err(|_| {
7913 target_root_allocation_error(roots.len() + 1, request_id)
7914 })?;
7915 }
7916 }
7917 }
7918 }
7919 Ok(roots.into_vec())
7920 }
7921
7922 pub fn prepare_graph_for_targets(
7926 &mut self,
7927 targets: &[crate::engine::EvaluationTarget],
7928 options: crate::engine::TargetEvalOptions<'_>,
7929 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7930 let previous_budgets = self.evaluation_resource_budgets.clone();
7931 let diagnostics_len = self.formula_parse_diagnostics.len();
7932 let previous_report = self.last_formula_ingest_report.clone();
7933 if let Some(budgets) = options.budgets {
7934 self.evaluation_resource_budgets = budgets.clone();
7935 }
7936 let previous_graph_budget_override = self
7937 .graph
7938 .set_admission_budget_override(Some(self.evaluation_resource_budgets.clone()));
7939 let previous_cancel = self.active_cancel_flag.take();
7943 self.active_cancel_flag = options.cancel.clone();
7944 let result = self.observe_evaluation_resource_request(
7945 EvaluationRequestKind::TargetPreparation,
7946 |engine| engine.prepare_graph_for_targets_unobserved(targets, &options),
7947 );
7948 self.active_cancel_flag = previous_cancel;
7949 self.graph
7950 .set_admission_budget_override(previous_graph_budget_override);
7951 self.evaluation_resource_budgets = previous_budgets;
7952 if result.is_err() {
7953 self.formula_parse_diagnostics.truncate(diagnostics_len);
7954 self.last_formula_ingest_report = previous_report;
7955 }
7956 result
7957 }
7958
7959 fn prepare_graph_for_targets_unobserved(
7960 &mut self,
7961 targets: &[crate::engine::EvaluationTarget],
7962 options: &crate::engine::TargetEvalOptions<'_>,
7963 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7964 let scratch_checkpoint = self
7965 .active_resource_ledger
7966 .as_ref()
7967 .map_or(0, crate::engine::ResourceLedger::scratch_checkpoint);
7968 let result = self.prepare_graph_for_targets_transaction(targets, options);
7969 let release = self
7970 .active_resource_ledger
7971 .as_mut()
7972 .map_or(Ok(()), |ledger| {
7973 ledger.release_scratch_to(scratch_checkpoint)
7974 });
7975 match (result, release) {
7976 (result, Ok(())) => result,
7977 (Ok(_), Err(error)) | (Err(_), Err(error)) => Err(error.into_excel_error()),
7978 }
7979 }
7980
7981 fn prepare_graph_for_targets_transaction(
7982 &mut self,
7983 targets: &[crate::engine::EvaluationTarget],
7984 options: &crate::engine::TargetEvalOptions<'_>,
7985 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
7986 use crate::engine::{
7987 OpaqueReason, PreparationOutcome, PrepareScope, PreparedTargetGraphReport,
7988 TableSelection,
7989 };
7990
7991 self.target_preparation_checkpoint(options.deadline, 0)?;
7992 self.observe_function_semantic_epoch()?;
7993 let assumptions = self.preparation_revisions();
7994 let ledger_at_start = self
7995 .active_resource_ledger
7996 .as_ref()
7997 .map(|ledger| ledger.snapshot());
7998 let diagnostics_len = self.formula_parse_diagnostics.len();
7999 let report_len = self.last_formula_ingest_report.clone();
8000 let request_id = options.request_id.or_else(|| {
8001 self.active_evaluation_resource_request
8002 .as_ref()
8003 .map(|stats| stats.request_id)
8004 });
8005
8006 let mut scope = PrepareScope::Exact;
8007 let mut reasons = Vec::new();
8008 let mut regions = VecDeque::new();
8009 let mut deferred_shared_regions = VecDeque::new();
8010 let mut normalized = Vec::with_capacity(targets.len());
8011 let mut symbol_vertices = VecDeque::new();
8012 for target in targets {
8013 self.target_preparation_checkpoint(options.deadline, 1)?;
8014 match target {
8015 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
8016 if *row == 0 || *col == 0 {
8017 return Err(ExcelError::new(ExcelErrorKind::Ref)
8018 .with_message("target cell coordinates are one-based"));
8019 }
8020 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
8021 ExcelError::new(ExcelErrorKind::Ref)
8022 .with_message(format!("target sheet not found: {sheet}"))
8023 })?;
8024 regions.push_back(PreparationRegion {
8025 sheet: sheet.clone(),
8026 sheet_id,
8027 start_row: *row,
8028 start_col: *col,
8029 end_row: *row,
8030 end_col: *col,
8031 });
8032 normalized.push(target.clone());
8033 }
8034 crate::engine::EvaluationTarget::Range(range) => {
8035 if range.start_row == 0
8036 || range.start_col == 0
8037 || range.end_row < range.start_row
8038 || range.end_col < range.start_col
8039 {
8040 return Err(ExcelError::new(ExcelErrorKind::Ref)
8041 .with_message("invalid target range"));
8042 }
8043 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
8044 ExcelError::new(ExcelErrorKind::Ref)
8045 .with_message(format!("target sheet not found: {}", range.sheet))
8046 })?;
8047 regions.push_back(PreparationRegion {
8048 sheet: range.sheet.clone(),
8049 sheet_id,
8050 start_row: range.start_row,
8051 start_col: range.start_col,
8052 end_row: range.end_row,
8053 end_col: range.end_col,
8054 });
8055 normalized.push(target.clone());
8056 }
8057 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
8058 let name_scope = self.name_query_scope(scope_sheet.as_deref())?;
8059 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, name_scope) {
8060 symbol_vertices.push_back(entry.vertex);
8061 } else {
8062 Self::widen_target_preparation(
8063 options.opaque_policy,
8064 &mut scope,
8065 &mut reasons,
8066 OpaqueReason::UnresolvedName,
8067 )?;
8068 }
8069 normalized.push(target.clone());
8070 }
8071 crate::engine::EvaluationTarget::Table { name, selection } => {
8072 if let Some(entry) = self.graph.resolve_table_entry(name) {
8073 let region = self.table_selection_region(entry, selection)?;
8074 symbol_vertices.push_back(entry.vertex);
8075 regions.push_back(region);
8076 } else {
8077 Self::widen_target_preparation(
8078 options.opaque_policy,
8079 &mut scope,
8080 &mut reasons,
8081 OpaqueReason::UnresolvedTable,
8082 )?;
8083 }
8084 normalized.push(target.clone());
8085 }
8086 }
8087 }
8088
8089 let mut visited_regions = FxHashSet::default();
8090 let mut visited_vertices = FxHashSet::default();
8091 let mut selected = FxHashSet::default();
8092 let mut prepared = Vec::new();
8093 let mut pending_diagnostics = Vec::new();
8094 let mut planning_requests = BTreeSet::new();
8095 let mut selected_cells = Vec::new();
8096 let mut workbook_seeded = false;
8097 let mut sheet_scope_seeded = BTreeSet::new();
8098 let mut indexed_query_sheets = FxHashSet::default();
8099 let mut discovery_scratch_reserved = 0u64;
8100 let mut package_encountered = false;
8101 let mut selected_package_sheets = FxHashSet::default();
8102 let mut selected_package_points: BTreeMap<String, BTreeSet<(u32, u32)>> = BTreeMap::new();
8103 let mut prepared_packages: Vec<PreparedTargetSourcePackage> = Vec::new();
8104 let authoritative_with_ordinary = false;
8105 let has_unknown_package_sheet = self
8106 .staged_formula_index
8107 .package_sheets()
8108 .any(|sheet| self.graph.sheet_id(sheet).is_none());
8109
8110 loop {
8111 if let PrepareScope::Sheets(sheets) = &scope {
8112 for sheet in sheets.clone() {
8113 if !sheet_scope_seeded.insert(sheet.clone()) {
8114 continue;
8115 }
8116 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8117 package_encountered = true;
8118 Self::widen_target_preparation(
8119 options.opaque_policy,
8120 &mut scope,
8121 &mut reasons,
8122 OpaqueReason::UnsupportedSourceSemantics,
8123 )?;
8124 continue;
8125 };
8126 for lease in self.staged_formula_index.leases_for_sheet(&sheet) {
8127 self.target_preparation_checkpoint(options.deadline, 1)?;
8128 regions.push_back(PreparationRegion {
8129 sheet: sheet.clone(),
8130 sheet_id,
8131 start_row: lease.row,
8132 start_col: lease.col,
8133 end_row: lease.row,
8134 end_col: lease.col,
8135 });
8136 }
8137 if self
8138 .staged_formula_index
8139 .package_lease_for_sheet(&sheet)
8140 .is_some()
8141 {
8142 regions.push_back(PreparationRegion {
8143 sheet: sheet.clone(),
8144 sheet_id,
8145 start_row: 1,
8146 start_col: 1,
8147 end_row: self.workbook_load_limits.max_sheet_rows,
8148 end_col: self.workbook_load_limits.max_sheet_cols,
8149 });
8150 }
8151 }
8152 }
8153 if matches!(scope, PrepareScope::Workbook) && !workbook_seeded {
8154 workbook_seeded = true;
8155 for (sheet, lease) in self.staged_formula_index.all_leases() {
8156 self.target_preparation_checkpoint(options.deadline, 1)?;
8157 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8158 package_encountered = true;
8159 Self::widen_target_preparation(
8160 options.opaque_policy,
8161 &mut scope,
8162 &mut reasons,
8163 OpaqueReason::UnsupportedSourceSemantics,
8164 )?;
8165 continue;
8166 };
8167 regions.push_back(PreparationRegion {
8168 sheet,
8169 sheet_id,
8170 start_row: lease.row,
8171 start_col: lease.col,
8172 end_row: lease.row,
8173 end_col: lease.col,
8174 });
8175 }
8176 let package_sheets = self
8177 .staged_formula_index
8178 .package_sheets()
8179 .map(str::to_string)
8180 .collect::<Vec<_>>();
8181 for sheet in package_sheets {
8182 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8183 package_encountered = true;
8184 Self::widen_target_preparation(
8185 options.opaque_policy,
8186 &mut scope,
8187 &mut reasons,
8188 OpaqueReason::UnsupportedSourceSemantics,
8189 )?;
8190 continue;
8191 };
8192 regions.push_back(PreparationRegion {
8193 sheet,
8194 sheet_id,
8195 start_row: 1,
8196 start_col: 1,
8197 end_row: self.workbook_load_limits.max_sheet_rows,
8198 end_col: self.workbook_load_limits.max_sheet_cols,
8199 });
8200 }
8201 }
8202
8203 let allow_partial_shared = regions.is_empty() && symbol_vertices.is_empty();
8206 let next_region = if allow_partial_shared {
8207 deferred_shared_regions.pop_front()
8208 } else {
8209 regions.pop_front()
8210 };
8211 let Some(region) = next_region else {
8212 if let Some(vertex) = symbol_vertices.pop_front() {
8213 self.target_preparation_checkpoint(options.deadline, 1)?;
8214 if !visited_vertices.insert(vertex) || !self.graph.vertex_exists(vertex) {
8215 continue;
8216 }
8217 let vertex_is_dynamic = self.graph.is_dynamic(vertex);
8218 if let Some(ast) = self.graph.get_formula(vertex) {
8219 let snapshot =
8220 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8221 if let Some(reason) =
8222 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8223 {
8224 return Err(Self::preparation_stale(
8225 reason,
8226 "target planning snapshot became stale during discovery",
8227 ));
8228 }
8229 let opaque = self.opaque_reason_in_ast(&ast, &snapshot);
8230 if let Some(reason) =
8231 opaque.or(vertex_is_dynamic.then_some(OpaqueReason::DynamicReference))
8232 {
8233 if reason == OpaqueReason::DynamicReference
8234 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
8235 {
8236 let sheet = self.graph.get_vertex_sheet_id(vertex);
8237 let sheet = self.graph.sheet_name(sheet).to_string();
8238 Self::widen_target_preparation_to_sheet(
8239 options.opaque_policy,
8240 &mut scope,
8241 &mut reasons,
8242 reason,
8243 &sheet,
8244 )?;
8245 } else {
8246 Self::widen_target_preparation(
8247 options.opaque_policy,
8248 &mut scope,
8249 &mut reasons,
8250 reason,
8251 )?;
8252 }
8253 }
8254 } else if vertex_is_dynamic {
8255 Self::widen_target_preparation(
8256 options.opaque_policy,
8257 &mut scope,
8258 &mut reasons,
8259 OpaqueReason::DynamicReference,
8260 )?;
8261 }
8262 if let Some(anchor) = self
8263 .graph
8264 .get_cell_ref(vertex)
8265 .and_then(|cell| self.graph.spill_registry_anchor_for_cell(cell))
8266 {
8267 symbol_vertices.push_back(anchor);
8268 }
8269 if let Some(cell) = self.graph.get_cell_ref(vertex) {
8270 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
8271 regions.push_back(PreparationRegion {
8272 sheet,
8273 sheet_id: cell.sheet_id,
8274 start_row: cell.coord.row() + 1,
8275 start_col: cell.coord.col() + 1,
8276 end_row: cell.coord.row() + 1,
8277 end_col: cell.coord.col() + 1,
8278 });
8279 }
8280 match self.graph.authority_vertex_precedents(vertex) {
8284 Some(precedents) => {
8285 for (sheet_id, rect) in precedents {
8286 self.target_preparation_checkpoint(options.deadline, 1)?;
8287 if sheet_id == crate::engine::authority::geom::SYMBOL_SHEET {
8288 for slot in rect.r0..=rect.r1 {
8289 if let Some(symbol) =
8290 self.graph.authority_host().symbols().vertex(slot)
8291 {
8292 symbol_vertices.push_back(symbol);
8293 }
8294 }
8295 continue;
8296 }
8297 let sheet = self.graph.sheet_name(sheet_id).to_string();
8298 regions.push_back(PreparationRegion {
8299 sheet,
8300 sheet_id,
8301 start_row: rect.r0 + 1,
8302 start_col: rect.c0 + 1,
8303 end_row: (rect.r1 + 1)
8304 .min(self.workbook_load_limits.max_sheet_rows),
8305 end_col: (rect.c1 + 1)
8306 .min(self.workbook_load_limits.max_sheet_cols),
8307 });
8308 }
8309 }
8310 None => {
8312 if Self::widen_target_preparation(
8313 options.opaque_policy,
8314 &mut scope,
8315 &mut reasons,
8316 OpaqueReason::UnresolvedCrossSheetBinding,
8317 )? {
8318 continue;
8319 }
8320 }
8321 }
8322 if let Some(name) = self.graph.named_range_by_vertex(vertex).cloned() {
8323 match &name.definition {
8324 NamedDefinition::Cell(cell) => regions.push_back(PreparationRegion {
8325 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
8326 sheet_id: cell.sheet_id,
8327 start_row: cell.coord.row() + 1,
8328 start_col: cell.coord.col() + 1,
8329 end_row: cell.coord.row() + 1,
8330 end_col: cell.coord.col() + 1,
8331 }),
8332 NamedDefinition::Range(range) => regions.push_back(PreparationRegion {
8333 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
8334 sheet_id: range.start.sheet_id,
8335 start_row: range.start.coord.row() + 1,
8336 start_col: range.start.coord.col() + 1,
8337 end_row: range.end.coord.row() + 1,
8338 end_col: range.end.coord.col() + 1,
8339 }),
8340 NamedDefinition::Formula {
8341 ast,
8342 dependencies,
8343 range_deps,
8344 } => {
8345 let snapshot =
8346 self.target_planning_snapshot(ast, &mut planning_requests)?;
8347 if let Some(reason) = Self::target_planning_snapshot_stale_reason(
8348 &snapshot,
8349 &assumptions,
8350 ) {
8351 return Err(Self::preparation_stale(
8352 reason,
8353 "target planning snapshot became stale during discovery",
8354 ));
8355 }
8356 if let Some(reason) = self.opaque_reason_in_ast(ast, &snapshot) {
8357 Self::widen_target_preparation(
8358 options.opaque_policy,
8359 &mut scope,
8360 &mut reasons,
8361 reason,
8362 )?;
8363 }
8364 for dependency in dependencies {
8365 self.target_preparation_checkpoint(options.deadline, 1)?;
8366 symbol_vertices.push_back(*dependency);
8367 }
8368 for range in range_deps {
8369 self.target_preparation_checkpoint(options.deadline, 1)?;
8370 let context_sheet = self.graph.get_vertex_sheet_id(vertex);
8378 let Ok(sheet_id) =
8379 self.resolve_sheet_locator(&range.sheet, context_sheet)
8380 else {
8381 if Self::widen_target_preparation(
8382 options.opaque_policy,
8383 &mut scope,
8384 &mut reasons,
8385 OpaqueReason::UnresolvedCrossSheetBinding,
8386 )? {
8387 break;
8388 }
8389 continue;
8390 };
8391 regions.push_back(PreparationRegion {
8392 sheet: self.graph.sheet_name(sheet_id).to_string(),
8393 sheet_id,
8394 start_row: range
8395 .start_row
8396 .map_or(1, |bound| bound.index + 1),
8397 start_col: range
8398 .start_col
8399 .map_or(1, |bound| bound.index + 1),
8400 end_row: range.end_row.map_or(
8401 self.workbook_load_limits.max_sheet_rows,
8402 |bound| bound.index + 1,
8403 ),
8404 end_col: range.end_col.map_or(
8405 self.workbook_load_limits.max_sheet_cols,
8406 |bound| bound.index + 1,
8407 ),
8408 });
8409 }
8410 }
8411 NamedDefinition::Literal(_) => {}
8412 }
8413 }
8414 if let Some(table) = self.graph.table_by_vertex(vertex) {
8415 regions
8416 .push_back(self.table_selection_region(table, &TableSelection::Whole)?);
8417 }
8418 continue;
8419 }
8420 break;
8421 };
8422
8423 self.target_preparation_checkpoint(options.deadline, 1)?;
8424 if !visited_regions.insert(region.clone()) && !allow_partial_shared {
8425 continue;
8426 }
8427 if let PrepareScope::Sheets(sheets) = &mut scope
8428 && !sheets.iter().any(|sheet| sheet == ®ion.sheet)
8429 {
8430 sheets.push(region.sheet.clone());
8431 sheets.sort();
8432 }
8433 let package_match = self.staged_formula_index.package_for_region(
8434 ®ion.sheet,
8435 region.start_row,
8436 region.start_col,
8437 region.end_row,
8438 region.end_col,
8439 );
8440 let package_lease = match package_match {
8441 Some(Ok(lease)) => Some(lease),
8442 Some(Err(()))
8443 if region.start_row == 1
8444 && region.start_col == 1
8445 && region.end_row == self.workbook_load_limits.max_sheet_rows
8446 && region.end_col == self.workbook_load_limits.max_sheet_cols =>
8447 {
8448 self.staged_formula_index
8449 .package_lease_for_sheet(®ion.sheet)
8450 }
8451 Some(Err(())) => {
8452 Self::widen_target_preparation(
8453 options.opaque_policy,
8454 &mut scope,
8455 &mut reasons,
8456 OpaqueReason::DeferredSourcePackage,
8457 )?;
8458 None
8459 }
8460 None => None,
8461 };
8462 let compatibility_before_package_replay = package_lease.is_some()
8463 && (authoritative_with_ordinary || has_unknown_package_sheet);
8464 let package_lease = if compatibility_before_package_replay {
8465 package_encountered = true;
8466 Self::widen_target_preparation(
8467 options.opaque_policy,
8468 &mut scope,
8469 &mut reasons,
8470 OpaqueReason::UnsupportedSourceSemantics,
8471 )?;
8472 None
8473 } else {
8474 package_lease
8475 };
8476 if let Some(package_lease) = package_lease
8477 && !selected_package_sheets.contains(®ion.sheet)
8478 {
8479 self.target_preparation_checkpoint(options.deadline, 1)?;
8480 let mut points = self.staged_formula_index.package_points_in_region(
8481 ®ion.sheet,
8482 region.start_row,
8483 region.start_col,
8484 region.end_row,
8485 region.end_col,
8486 );
8487 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8488 points.retain(|point| !selected.contains(point));
8489 }
8490
8491 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8492 points.retain(|point| !selected.contains(point));
8493 }
8494 let permit_partial = allow_partial_shared
8495 || (points.len() == 1
8496 && regions.is_empty()
8497 && symbol_vertices.is_empty()
8498 && deferred_shared_regions.is_empty());
8499 let partial = self.prepare_target_exact_source_selection(
8500 ®ion.sheet,
8501 package_lease,
8502 points,
8503 selected_package_points
8504 .get(®ion.sheet)
8505 .unwrap_or(&BTreeSet::new()),
8506 permit_partial,
8507 options.deadline,
8508 &mut discovery_scratch_reserved,
8509 )?;
8510 let mut package = if let Some(mut package) = partial {
8511 if package.deferred_shared {
8512 deferred_shared_regions.push_back(region.clone());
8513 }
8514 if !package.direct_domains.is_empty() {
8518 for prior in prepared_packages
8519 .iter_mut()
8520 .filter(|prior| prior.sheet == region.sheet)
8521 {
8522 prior
8523 .replay_records
8524 .retain(|record| !package.direct_contains(record.row, record.col));
8525 prior
8526 .legacy
8527 .retain(|(row, col, _, _)| !package.direct_contains(*row, *col));
8528 if let Some(points) = prior.selected_points.as_mut() {
8529 points.retain(|&(row, col)| !package.direct_contains(row, col));
8530 }
8531 }
8532 }
8533 if selected_package_points.contains_key(®ion.sheet) {
8534 package.source_report = Default::default();
8535 }
8536 let points = package.selected_points.as_ref().unwrap();
8537 if !points.is_empty() {
8538 selected_package_points
8539 .entry(region.sheet.clone())
8540 .or_default()
8541 .extend(points.iter().copied());
8542 }
8543 package
8544 } else {
8545 selected_package_sheets.insert(region.sheet.clone());
8546 self.prepare_target_source_package(
8547 ®ion.sheet,
8548 package_lease,
8549 options.deadline,
8550 )?
8551 };
8552 if package
8553 .selected_points
8554 .as_ref()
8555 .is_none_or(|points| !points.is_empty())
8556 {
8557 let mut final_fallback = BTreeMap::new();
8558 for record in package.fallback_records() {
8559 final_fallback.insert((record.row, record.col), record.clone());
8560 }
8561 let batch = self.formula_batch_from_exact_replay(
8562 ®ion.sheet,
8563 final_fallback.into_values(),
8564 )?;
8565 for record in batch.formulas {
8566 self.target_preparation_checkpoint(options.deadline, 1)?;
8567 let ast = self
8568 .graph
8569 .data_store()
8570 .retrieve_ast(record.ast_id, self.graph.sheet_reg())
8571 .ok_or_else(|| {
8572 ExcelError::new(ExcelErrorKind::Value)
8573 .with_message("target fallback AST is unavailable")
8574 })?;
8575 let snapshot =
8576 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8577 if let Some(reason) =
8578 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8579 {
8580 return Err(Self::preparation_stale(
8581 reason,
8582 "target fallback planning snapshot became stale during discovery",
8583 ));
8584 }
8585 let proven_sheet_local_dynamic =
8586 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
8587 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
8588 if reason == OpaqueReason::DynamicReference
8589 && proven_sheet_local_dynamic
8590 {
8591 Self::widen_target_preparation_to_sheet(
8592 options.opaque_policy,
8593 &mut scope,
8594 &mut reasons,
8595 reason,
8596 ®ion.sheet,
8597 )?;
8598 } else {
8599 Self::widen_target_preparation(
8600 options.opaque_policy,
8601 &mut scope,
8602 &mut reasons,
8603 reason,
8604 )?;
8605 }
8606 }
8607 let placement = CellRef::new(
8608 package.sheet_id,
8609 Coord::from_excel(record.row, record.col, true, true),
8610 );
8611 let ingested = self
8612 .graph
8613 .ingest_pipeline(&snapshot)
8614 .enable_function_semantics()
8615 .ingest_formula(
8616 FormulaAstInput::RawArena(record.ast_id),
8617 placement,
8618 record.formula_text,
8619 )?;
8620 if ingested.dep_plan.dynamic {
8621 if proven_sheet_local_dynamic {
8622 Self::widen_target_preparation_to_sheet(
8623 options.opaque_policy,
8624 &mut scope,
8625 &mut reasons,
8626 OpaqueReason::DynamicReference,
8627 ®ion.sheet,
8628 )?;
8629 } else {
8630 Self::widen_target_preparation(
8631 options.opaque_policy,
8632 &mut scope,
8633 &mut reasons,
8634 OpaqueReason::DynamicReference,
8635 )?;
8636 }
8637 }
8638 for dep in &ingested.dep_plan.direct_cell_deps {
8639 self.target_preparation_checkpoint(options.deadline, 1)?;
8640 regions.push_back(PreparationRegion {
8641 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
8642 sheet_id: dep.sheet_id,
8643 start_row: dep.coord.row().saturating_add(1),
8644 start_col: dep.coord.col().saturating_add(1),
8645 end_row: dep.coord.row().saturating_add(1),
8646 end_col: dep.coord.col().saturating_add(1),
8647 });
8648 }
8649 for range in &ingested.dep_plan.range_deps {
8650 self.target_preparation_checkpoint(options.deadline, 1)?;
8651 let Ok(dependency_sheet) =
8654 self.resolve_sheet_locator(&range.sheet, package.sheet_id)
8655 else {
8656 Self::widen_target_preparation(
8657 options.opaque_policy,
8658 &mut scope,
8659 &mut reasons,
8660 OpaqueReason::UnresolvedCrossSheetBinding,
8661 )?;
8662 continue;
8663 };
8664 regions.push_back(PreparationRegion {
8665 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
8666 sheet_id: dependency_sheet,
8667 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
8668 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
8669 end_row: range
8670 .end_row
8671 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
8672 bound.index + 1
8673 }),
8674 end_col: range
8675 .end_col
8676 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
8677 bound.index + 1
8678 }),
8679 });
8680 }
8681 for name in ingested
8682 .dep_plan
8683 .resolved_named_refs
8684 .iter()
8685 .chain(&ingested.dep_plan.named_refs)
8686 {
8687 self.target_preparation_checkpoint(options.deadline, 1)?;
8688 if let Some(entry) =
8689 self.graph.resolve_name_entry(name, package.sheet_id)
8690 {
8691 symbol_vertices.push_back(entry.vertex);
8692 } else if self.graph.resolve_source_scalar_entry(name).is_none()
8693 && self.graph.resolve_source_table_entry(name).is_none()
8694 {
8695 Self::widen_target_preparation(
8696 options.opaque_policy,
8697 &mut scope,
8698 &mut reasons,
8699 OpaqueReason::UnresolvedName,
8700 )?;
8701 }
8702 }
8703 for table in &ingested.dep_plan.table_refs {
8704 self.target_preparation_checkpoint(options.deadline, 1)?;
8705 if let Some(entry) = self.graph.resolve_table_entry(table) {
8706 symbol_vertices.push_back(entry.vertex);
8707 } else if self.graph.resolve_source_table_entry(table).is_none() {
8708 Self::widen_target_preparation(
8709 options.opaque_policy,
8710 &mut scope,
8711 &mut reasons,
8712 OpaqueReason::UnresolvedTable,
8713 )?;
8714 }
8715 }
8716 package.legacy.push((
8717 record.row,
8718 record.col,
8719 ingested.ast_id,
8720 ingested.dep_plan,
8721 ));
8722 }
8723 prepared_packages.push(package);
8724 }
8725 }
8726 let leases = self.staged_formula_index.leases_in_region(
8727 ®ion.sheet,
8728 region.start_row,
8729 region.start_col,
8730 region.end_row,
8731 region.end_col,
8732 );
8733 for lease in leases {
8734 self.target_preparation_checkpoint(options.deadline, 1)?;
8735 let sheet_id = self.graph.sheet_id(®ion.sheet).ok_or_else(|| {
8736 ExcelError::new(ExcelErrorKind::Ref)
8737 .with_message(format!("staged formula sheet not found: {}", region.sheet))
8738 })?;
8739 let key = (region.sheet.clone(), lease.row, lease.col, lease.generation);
8740 if !selected.insert(key) {
8741 continue;
8742 }
8743 let text = self
8744 .staged_formulas
8745 .get(®ion.sheet)
8746 .and_then(|sheet| sheet.get_ordinary(lease.row, lease.col))
8747 .ok_or_else(|| {
8748 ExcelError::new(ExcelErrorKind::Value)
8749 .with_message("staged formula index is stale")
8750 })?
8751 .to_string();
8752 let formula = if text.starts_with('=') {
8753 text.clone()
8754 } else {
8755 format!("={text}")
8756 };
8757 self.target_preparation_checkpoint(options.deadline, 1)?;
8758 let ast = match formualizer_parse::parser::parse(&formula) {
8759 Ok(ast) => ast,
8760 Err(error) => {
8761 if self.config.formula_parse_policy == FormulaParsePolicy::Strict {
8762 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
8763 format!(
8764 "Formula parse error at {}!{}{}: {error}",
8765 region.sheet,
8766 col_letters_from_1based(lease.col)
8767 .unwrap_or_else(|_| "?".to_string()),
8768 lease.row
8769 ),
8770 ));
8771 }
8772 pending_diagnostics.push(FormulaParseDiagnostic {
8773 sheet: region.sheet.clone(),
8774 row: lease.row,
8775 col: lease.col,
8776 formula: formula.clone(),
8777 message: error.to_string(),
8778 policy: self.config.formula_parse_policy,
8779 });
8780 match self.config.formula_parse_policy {
8781 FormulaParsePolicy::KeepCachedValue => {
8782 selected_cells.push(
8783 formualizer_common::RangeAddress::new(
8784 region.sheet.clone(),
8785 lease.row,
8786 lease.col,
8787 lease.row,
8788 lease.col,
8789 )
8790 .expect("selected staged coordinates are valid"),
8791 );
8792 prepared.push(PreparedOrdinaryStagedFormula {
8793 sheet: region.sheet.clone(),
8794 sheet_id,
8795 lease,
8796 ast_id: None,
8797 plan: None,
8798 });
8799 continue;
8800 }
8801 FormulaParsePolicy::AsText => ASTNode::new(
8802 ASTNodeType::Literal(LiteralValue::Text(formula.clone())),
8803 None,
8804 ),
8805 FormulaParsePolicy::CoerceToError => ASTNode::new(
8806 ASTNodeType::Literal(LiteralValue::Error(
8807 ExcelError::new(ExcelErrorKind::Error)
8808 .with_message(format!("Malformed formula: {error}")),
8809 )),
8810 None,
8811 ),
8812 FormulaParsePolicy::Strict => unreachable!(),
8813 }
8814 }
8815 };
8816 self.target_preparation_checkpoint(options.deadline, 1)?;
8817 let snapshot = self.target_planning_snapshot(&ast, &mut planning_requests)?;
8818 self.target_preparation_checkpoint(options.deadline, 1)?;
8819 if let Some(reason) =
8820 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8821 {
8822 return Err(Self::preparation_stale(
8823 reason,
8824 "target planning snapshot became stale during discovery",
8825 ));
8826 }
8827 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
8828 if reason == OpaqueReason::DynamicReference
8829 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
8830 {
8831 Self::widen_target_preparation_to_sheet(
8832 options.opaque_policy,
8833 &mut scope,
8834 &mut reasons,
8835 reason,
8836 ®ion.sheet,
8837 )?;
8838 } else {
8839 Self::widen_target_preparation(
8840 options.opaque_policy,
8841 &mut scope,
8842 &mut reasons,
8843 reason,
8844 )?;
8845 }
8846 }
8847 let proven_sheet_local_dynamic =
8848 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
8849 let placement = CellRef::new(
8850 sheet_id,
8851 Coord::from_excel(lease.row, lease.col, true, true),
8852 );
8853 let ingested = self.graph.ingest_pipeline(&snapshot).ingest_formula(
8854 FormulaAstInput::Tree(ast),
8855 placement,
8856 Some(Arc::from(formula)),
8857 )?;
8858 self.target_preparation_checkpoint(options.deadline, 1)?;
8859 if ingested.dep_plan.dynamic {
8860 if proven_sheet_local_dynamic {
8861 Self::widen_target_preparation_to_sheet(
8862 options.opaque_policy,
8863 &mut scope,
8864 &mut reasons,
8865 OpaqueReason::DynamicReference,
8866 ®ion.sheet,
8867 )?;
8868 } else {
8869 Self::widen_target_preparation(
8870 options.opaque_policy,
8871 &mut scope,
8872 &mut reasons,
8873 OpaqueReason::DynamicReference,
8874 )?;
8875 }
8876 }
8877 for dep in &ingested.dep_plan.direct_cell_deps {
8878 self.target_preparation_checkpoint(options.deadline, 1)?;
8879 regions.push_back(PreparationRegion {
8880 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
8881 sheet_id: dep.sheet_id,
8882 start_row: dep.coord.row() + 1,
8883 start_col: dep.coord.col() + 1,
8884 end_row: dep.coord.row() + 1,
8885 end_col: dep.coord.col() + 1,
8886 });
8887 }
8888 for range in &ingested.dep_plan.range_deps {
8889 self.target_preparation_checkpoint(options.deadline, 1)?;
8890 let Ok(dependency_sheet) = self.resolve_sheet_locator(&range.sheet, sheet_id)
8892 else {
8893 Self::widen_target_preparation(
8894 options.opaque_policy,
8895 &mut scope,
8896 &mut reasons,
8897 OpaqueReason::UnresolvedCrossSheetBinding,
8898 )?;
8899 continue;
8900 };
8901 regions.push_back(PreparationRegion {
8902 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
8903 sheet_id: dependency_sheet,
8904 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
8905 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
8906 end_row: range
8907 .end_row
8908 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
8909 bound.index + 1
8910 }),
8911 end_col: range
8912 .end_col
8913 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
8914 bound.index + 1
8915 }),
8916 });
8917 }
8918 for name in ingested
8919 .dep_plan
8920 .resolved_named_refs
8921 .iter()
8922 .chain(&ingested.dep_plan.named_refs)
8923 {
8924 self.target_preparation_checkpoint(options.deadline, 1)?;
8925 if let Some(entry) = self.graph.resolve_name_entry(name, sheet_id) {
8926 symbol_vertices.push_back(entry.vertex);
8927 } else if self.graph.resolve_source_scalar_entry(name).is_none()
8928 && self.graph.resolve_source_table_entry(name).is_none()
8929 {
8930 Self::widen_target_preparation(
8931 options.opaque_policy,
8932 &mut scope,
8933 &mut reasons,
8934 OpaqueReason::UnresolvedName,
8935 )?;
8936 }
8937 }
8938 for table in &ingested.dep_plan.table_refs {
8939 self.target_preparation_checkpoint(options.deadline, 1)?;
8940 if let Some(entry) = self.graph.resolve_table_entry(table) {
8941 symbol_vertices.push_back(entry.vertex);
8942 } else if self.graph.resolve_source_table_entry(table).is_none() {
8943 Self::widen_target_preparation(
8944 options.opaque_policy,
8945 &mut scope,
8946 &mut reasons,
8947 OpaqueReason::UnresolvedTable,
8948 )?;
8949 }
8950 }
8951 selected_cells.push(
8952 formualizer_common::RangeAddress::new(
8953 region.sheet.clone(),
8954 lease.row,
8955 lease.col,
8956 lease.row,
8957 lease.col,
8958 )
8959 .expect("selected staged coordinates are valid"),
8960 );
8961 prepared.push(PreparedOrdinaryStagedFormula {
8962 sheet: region.sheet.clone(),
8963 sheet_id,
8964 lease,
8965 ast_id: Some(ingested.ast_id),
8966 plan: Some(ingested.dep_plan),
8967 });
8968 }
8969
8970 if indexed_query_sheets.insert(region.sheet_id) {
8971 self.graph.prepare_sheet_index_for_query(region.sheet_id);
8972 let bytes = (self.graph.sheet_index_vertex_count(region.sheet_id) as u64)
8973 .saturating_mul(32);
8974 self.reserve_graph_source_scratch(bytes)?;
8975 discovery_scratch_reserved = discovery_scratch_reserved.saturating_add(bytes);
8976 }
8977 let spill_anchors = self.graph.spill_anchors_in_region(
8978 region.sheet_id,
8979 region.start_row - 1,
8980 region.start_col - 1,
8981 region.end_row - 1,
8982 region.end_col - 1,
8983 );
8984 for anchor in spill_anchors {
8985 self.target_preparation_checkpoint(options.deadline, 1)?;
8986 symbol_vertices.push_back(anchor);
8987 }
8988 let vertices = self.graph.vertices_in_region(
8989 region.sheet_id,
8990 region.start_row - 1,
8991 region.end_row - 1,
8992 region.start_col - 1,
8993 region.end_col - 1,
8994 );
8995 for vertex in vertices {
8996 self.target_preparation_checkpoint(options.deadline, 1)?;
8997 symbol_vertices.push_back(vertex);
8998 }
8999 }
9000
9001 #[cfg(test)]
9002 self.target_preparation_fault(
9003 crate::engine::target_preparation::TargetPreparationFault::AfterDiscovery,
9004 )?;
9005
9006 if package_encountered {
9007 Self::widen_target_preparation(
9008 options.opaque_policy,
9009 &mut scope,
9010 &mut reasons,
9011 OpaqueReason::UnsupportedSourceSemantics,
9012 )?;
9013 self.target_preparation_checkpoint(options.deadline, 0)?;
9014 let selected_count = self.staged_formula_count();
9015 let selected_packages = self
9016 .staged_formulas
9017 .values()
9018 .filter_map(|staged| staged.deferred_package.as_ref())
9019 .map(|package| package.families.len() + package.partitioned_families.len())
9020 .sum();
9021 self.formula_parse_diagnostics.truncate(diagnostics_len);
9022 self.last_formula_ingest_report = report_len;
9023 #[cfg(test)]
9024 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
9025 hook();
9026 }
9027 self.target_preparation_checkpoint(options.deadline, 0)?;
9028 #[cfg(test)]
9029 self.target_preparation_fault(
9030 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
9031 )?;
9032 let current_revisions = self.preparation_revisions();
9033 if let Some(reason) = Self::preparation_revision_stale_reason(
9034 &assumptions,
9035 ¤t_revisions,
9036 &planning_requests,
9037 true,
9038 ) {
9039 return Err(Self::preparation_stale(
9040 reason,
9041 "target compatibility preparation plan is stale",
9042 ));
9043 }
9044 #[cfg(test)]
9045 self.target_preparation_fault(
9046 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9047 )?;
9048 let commit_work_before = self
9049 .active_resource_ledger
9050 .as_ref()
9051 .map_or(0, |ledger| ledger.snapshot().work_charged);
9052 let commit_started = crate::instant::FzInstant::now();
9053 self.build_graph_all_unobserved()?;
9054 let commit_window = commit_started.elapsed();
9055 let ledger_after = self
9056 .active_resource_ledger
9057 .as_ref()
9058 .map(|ledger| ledger.snapshot());
9059 let actual_commit_work = ledger_after
9060 .map_or(0, |snapshot| snapshot.work_charged)
9061 .saturating_sub(commit_work_before);
9062 let observed_scratch_bytes = ledger_after
9063 .map_or(0, |snapshot| snapshot.scratch_peak)
9064 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9065 let revisions = assumptions.clone();
9066 let report = PreparedTargetGraphReport {
9067 request_id: request_id.unwrap_or_default(),
9068 requested_targets: targets.len(),
9069 normalized_regions: visited_regions.len(),
9070 normalized_target_list: normalized,
9071 selected_staged_cells: selected_count,
9072 selected_source_families: selected_packages,
9073 retained_staged_cells: self.staged_formula_count(),
9074 selected_cells,
9075 retained_cells: Vec::new(),
9076 widened_scope: PrepareScope::Workbook,
9077 widening_reasons: reasons,
9078 revisions,
9079 commit_window,
9080 estimated_scratch_bytes: discovery_scratch_reserved
9081 .saturating_add((selected_count as u64).saturating_mul(256)),
9082 observed_scratch_bytes,
9083 estimated_commit_work: selected_count as u64,
9084 actual_commit_work,
9085 outcome: PreparationOutcome::CompatibilityPrepared,
9086 };
9087 self.observe_target_preparation_report(&report);
9088 return Ok(report);
9089 }
9090
9091 prepared.sort_by_key(|formula| formula.lease.insertion_order);
9092 for package in &prepared_packages {
9093 if let Some(points) = &package.selected_points {
9094 selected_cells.extend(points.iter().filter_map(|&(row, col)| {
9095 formualizer_common::RangeAddress::new(&package.sheet, row, col, row, col).ok()
9096 }));
9097 continue;
9098 }
9099 selected_cells.extend(package.replay_records.iter().filter_map(|record| {
9100 formualizer_common::RangeAddress::new(
9101 package.sheet.clone(),
9102 record.row,
9103 record.col,
9104 record.row,
9105 record.col,
9106 )
9107 .ok()
9108 }));
9109 }
9110 let (legacy_graph, planned_formula_count) =
9111 self.prepare_target_combined_legacy_graph(&prepared_packages, &prepared)?;
9112 let new_vertices = legacy_graph.new_vertex_count();
9113 let new_edges = legacy_graph.planned_edge_count().ok_or_else(|| {
9114 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9115 })?;
9116 let removed_edges = legacy_graph.removed_edge_count().ok_or_else(|| {
9117 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9118 })?;
9119 let current = self.graph.baseline_stats();
9120 let final_vertices = current
9121 .graph_vertex_count
9122 .checked_add(new_vertices)
9123 .ok_or_else(|| {
9124 crate::engine::ResourceLedgerError::Exhausted(
9125 formualizer_common::ResourceExhaustionDetail {
9126 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9127 limit: u64::MAX,
9128 observed: u64::MAX,
9129 request_id,
9130 },
9131 )
9132 .into_excel_error()
9133 })?;
9134 let final_edges = current
9135 .graph_edge_count
9136 .checked_sub(removed_edges)
9137 .and_then(|count| count.checked_add(new_edges))
9138 .ok_or_else(|| {
9139 crate::engine::ResourceLedgerError::Exhausted(
9140 formualizer_common::ResourceExhaustionDetail {
9141 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9142 limit: u64::MAX,
9143 observed: u64::MAX,
9144 request_id,
9145 },
9146 )
9147 .into_excel_error()
9148 })?;
9149 #[cfg(test)]
9150 self.target_preparation_fault(
9151 crate::engine::target_preparation::TargetPreparationFault::Admission,
9152 )?;
9153 let resource = |reason, limit: u64, observed: u64| {
9154 crate::engine::ResourceLedgerError::Exhausted(
9155 formualizer_common::ResourceExhaustionDetail {
9156 reason,
9157 limit,
9158 observed,
9159 request_id,
9160 },
9161 )
9162 .into_excel_error()
9163 };
9164 let admission = crate::engine::resource_ledger::GraphAdmission {
9165 final_vertices,
9166 final_edges,
9167 materialization_cells: planned_formula_count as u64,
9168 added_vertices: new_vertices,
9169 added_edges: new_edges,
9170 };
9171 let materialized_bytes = admission
9172 .materialized_graph_bytes()
9173 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
9174 if let Err(error) = self.preflight_graph_admission(admission) {
9175 if let formualizer_common::ExcelErrorExtra::Resource { detail } = &error.extra {
9176 self.observe_target_admission_failure(detail.reason);
9177 }
9178 return Err(error);
9179 }
9180 let selected_package_records = prepared_packages
9181 .iter()
9182 .map(|package| package.replay_records.len() as u64)
9183 .sum::<u64>();
9184 let planned_working_bytes = (prepared.len() as u64)
9185 .saturating_add(selected_package_records)
9186 .saturating_mul(256)
9187 .saturating_add((visited_regions.len() as u64).saturating_mul(128))
9188 .saturating_add((visited_vertices.len() as u64).saturating_mul(32))
9189 .saturating_add((selected.len() as u64).saturating_mul(96))
9190 .saturating_add(materialized_bytes);
9191 let residual_scratch = selected_package_points
9192 .iter()
9193 .map(|(sheet, points)| {
9194 let source = self
9195 .staged_formulas
9196 .get(sheet)
9197 .unwrap()
9198 .deferred_package
9199 .as_ref()
9200 .unwrap();
9201 (source.families.len() as u64)
9202 .saturating_mul(256)
9203 .saturating_add(
9204 source
9205 .partitioned_families
9206 .iter()
9207 .map(|family| {
9208 256u64
9209 .saturating_add(family.fragments.len() as u64 * 32)
9210 .saturating_add(family.legacy_members.len() as u64 * 32)
9211 })
9212 .sum::<u64>(),
9213 )
9214 .saturating_add(points.len() as u64 * 32)
9217 .saturating_add(
9218 prepared_packages
9219 .iter()
9220 .filter(|package| &package.sheet == sheet)
9221 .map(|package| package.replay_records.len() as u64 * 480)
9222 .sum::<u64>(),
9223 )
9224 })
9225 .sum::<u64>();
9226 let scratch_bytes = discovery_scratch_reserved
9227 .saturating_add(planned_working_bytes)
9228 .saturating_add(residual_scratch);
9229 let remaining_scratch = scratch_bytes.saturating_sub(discovery_scratch_reserved);
9230 if let Err(error) = self.reserve_graph_source_scratch(remaining_scratch) {
9231 self.observe_target_admission_failure(
9232 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9233 );
9234 return Err(error);
9235 }
9236
9237 let mut residual_sources = BTreeMap::new();
9238 let mut residual_owners = BTreeMap::new();
9239 for (sheet, points) in &selected_package_points {
9240 let selected: BTreeMap<_, BTreeSet<_>> = prepared_packages
9241 .iter()
9242 .filter(|package| &package.sheet == sheet)
9243 .flat_map(|package| &package.replay_records)
9244 .filter_map(|record| {
9245 record.partition_owner.or(record.family).map(|owner| {
9246 (
9247 owner,
9248 crate::engine::SourceCoord {
9249 row: record.row - 1,
9250 col: record.col - 1,
9251 },
9252 )
9253 })
9254 })
9255 .fold(BTreeMap::new(), |mut map, (owner, coord)| {
9256 map.entry(owner).or_default().insert(coord);
9257 map
9258 });
9259 let metadata_work = self
9260 .staged_formulas
9261 .get(sheet)
9262 .unwrap()
9263 .deferred_package
9264 .as_ref()
9265 .map_or(0, |source| {
9266 source.families.len() as u64
9267 + source
9268 .partitioned_families
9269 .iter()
9270 .map(|family| {
9271 1 + family.fragments.len() as u64 * family.fragments.len() as u64
9272 + family.legacy_members.len() as u64
9273 })
9274 .sum::<u64>()
9275 });
9276 let split_work = selected
9277 .values()
9278 .map(|points| points.len() as u64 * 128)
9279 .sum::<u64>();
9280 self.target_preparation_checkpoint(
9281 options.deadline,
9282 metadata_work
9283 .saturating_add(split_work)
9284 .saturating_add(points.len() as u64),
9285 )?;
9286 let source = self
9287 .staged_formulas
9288 .get(sheet)
9289 .unwrap()
9290 .deferred_package
9291 .as_ref()
9292 .unwrap();
9293 let complete: BTreeSet<_> = prepared_packages
9294 .iter()
9295 .filter(|package| &package.sheet == sheet)
9296 .flat_map(|package| package.complete_selections.iter().copied())
9297 .collect();
9298 let residual = source
9299 .residual_sources(&selected, &complete, &self.workbook_load_limits)
9300 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
9301 residual_owners.insert(
9302 sheet.clone(),
9303 residual
9304 .1
9305 .iter()
9306 .map(|family| family.source_id)
9307 .collect::<BTreeSet<_>>(),
9308 );
9309 residual_sources.insert(sheet.clone(), residual);
9310 }
9311
9312 for (sheet, points) in &selected_package_points {
9315 let package = self
9316 .staged_formulas
9317 .get_mut(sheet)
9318 .unwrap()
9319 .deferred_package
9320 .as_mut()
9321 .unwrap();
9322 let owner_count = prepared_packages
9323 .iter()
9324 .filter(|p| &p.sheet == sheet)
9325 .flat_map(|p| &p.replay_records)
9326 .filter_map(|record| record.partition_owner.or(record.family))
9327 .filter(|owner| residual_owners[sheet].contains(owner))
9328 .count();
9329 package
9330 .consumed_members
9331 .try_reserve(owner_count)
9332 .map_err(|_| {
9333 resource(
9334 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9335 0,
9336 owner_count as u64 * 24,
9337 )
9338 })?;
9339 package.suppressed.try_reserve(points.len()).map_err(|_| {
9340 resource(
9341 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9342 0,
9343 points.len() as u64 * 16,
9344 )
9345 })?;
9346 }
9347
9348 let estimated_commit_duration = std::time::Duration::from_nanos(
9349 (new_vertices as u64)
9350 .saturating_add(new_edges as u64)
9351 .saturating_add(prepared.len() as u64)
9352 .saturating_add(selected_package_records)
9353 .max(1)
9354 .saturating_mul(100),
9355 );
9356 if options.deadline.is_some_and(|deadline| {
9357 std::time::Instant::now()
9358 .checked_add(estimated_commit_duration)
9359 .is_none_or(|finish| finish > deadline)
9360 }) {
9361 self.observe_target_admission_failure(
9362 formualizer_common::ResourceExhaustionReason::Deadline,
9363 );
9364 return Err(resource(
9365 formualizer_common::ResourceExhaustionReason::Deadline,
9366 0,
9367 1,
9368 ));
9369 }
9370 #[cfg(test)]
9371 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
9372 hook();
9373 }
9374 self.target_preparation_checkpoint(options.deadline, 0)?;
9375 #[cfg(test)]
9376 self.target_preparation_fault(
9377 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
9378 )?;
9379 let current_revisions = self.preparation_revisions();
9380 let staged_leases_match = prepared.iter().all(|formula| {
9381 self.staged_formula_index
9382 .lease_matches(&formula.sheet, formula.lease)
9383 }) && prepared_packages.iter().all(|package| {
9384 self.staged_formula_index
9385 .package_lease_matches(&package.sheet, package.lease)
9386 });
9387 let stale_reason = Self::preparation_revision_stale_reason(
9388 &assumptions,
9389 ¤t_revisions,
9390 &planning_requests,
9391 staged_leases_match,
9392 );
9393 if let Some(reason) = stale_reason {
9394 return Err(Self::preparation_stale(
9395 reason,
9396 "target graph preparation plan is stale",
9397 ));
9398 }
9399 #[cfg(test)]
9400 self.target_preparation_fault(
9401 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9402 )?;
9403 self.graph
9404 .validate_prepared_legacy_graph_plan(&legacy_graph)
9405 .map_err(|error| {
9406 Self::preparation_stale(
9407 formualizer_common::PreparationStaleReason::Graph,
9408 format!("target graph preparation plan is stale: {error}"),
9409 )
9410 })?;
9411 #[cfg(test)]
9412 self.target_preparation_fault(
9413 crate::engine::target_preparation::TargetPreparationFault::Reservation,
9414 )?;
9415 self.graph.reserve_prepared_legacy_graph_plan(&legacy_graph);
9416 self.formula_parse_diagnostics
9417 .try_reserve(pending_diagnostics.len())
9418 .map_err(|_| {
9419 resource(
9420 formualizer_common::ResourceExhaustionReason::Admission,
9421 pending_diagnostics.len() as u64,
9422 pending_diagnostics.len() as u64,
9423 )
9424 })?;
9425 self.target_preparation_checkpoint(options.deadline, 0)?;
9426 #[cfg(test)]
9427 self.target_preparation_fault(
9428 crate::engine::target_preparation::TargetPreparationFault::BeforeFirstMutation,
9429 )?;
9430
9431 let commit_started = crate::instant::FzInstant::now();
9432 let committed = self
9433 .graph
9434 .apply_prevalidated_legacy_graph_plan(legacy_graph);
9435 for formula in &prepared {
9436 let removed = self
9437 .staged_formulas
9438 .get_mut(&formula.sheet)
9439 .and_then(|sheet| sheet.remove_ordinary(formula.lease.row, formula.lease.col));
9440 debug_assert!(removed.is_some());
9441 let index_removed = self.staged_formula_index.remove(
9442 &formula.sheet,
9443 formula.lease.row,
9444 formula.lease.col,
9445 );
9446 debug_assert!(index_removed);
9447 }
9448 for package in &prepared_packages {
9449 if let Some(points) = &package.selected_points {
9450 let staged = self.staged_formulas.get_mut(&package.sheet).unwrap();
9451 let source = staged.deferred_package.as_mut().unwrap();
9452 source.consumed_engine = Some(Arc::clone(&self.source_formula_token));
9453 source
9456 .consumed_members
9457 .extend(package.replay_records.iter().filter_map(|record| {
9458 record
9459 .partition_owner
9460 .or(record.family)
9461 .filter(|owner| residual_owners[&package.sheet].contains(owner))
9462 .map(|owner| {
9463 (
9464 owner,
9465 crate::engine::SourceCoord {
9466 row: record.row - 1,
9467 col: record.col - 1,
9468 },
9469 )
9470 })
9471 }));
9472 source.suppressed.extend(points.iter().copied());
9473 source.source_accounted = true;
9474 self.staged_formula_index
9475 .consume_package_points(&package.sheet, points);
9476 if source.suppressed.len() >= source.source_coordinates.len() {
9477 staged.deferred_package = None;
9478 self.staged_formula_index.set_package(&package.sheet, None);
9479 }
9480 } else {
9481 let removed = self
9482 .staged_formulas
9483 .get_mut(&package.sheet)
9484 .and_then(|staged| staged.deferred_package.take());
9485 debug_assert!(removed.is_some());
9486 self.staged_formula_index.set_package(&package.sheet, None);
9487 }
9488 }
9489 for (sheet, (families, partitions)) in residual_sources {
9490 if let Some(source) = self
9491 .staged_formulas
9492 .get_mut(&sheet)
9493 .and_then(|staged| staged.deferred_package.as_mut())
9494 {
9495 source.families = families;
9496 source.partitioned_families = partitions;
9497 source
9498 .consumed_members
9499 .retain(|(owner, _)| residual_owners[&sheet].contains(owner));
9500 self.staged_formula_index.update_package_family_count(
9501 &sheet,
9502 source.families.len() + source.partitioned_families.len(),
9503 );
9504 }
9505 }
9506 let empty_sheets = self
9507 .staged_formulas
9508 .iter()
9509 .filter_map(|(sheet, staged)| staged.is_empty().then_some(sheet.clone()))
9510 .collect::<Vec<_>>();
9511 for sheet in empty_sheets {
9512 self.staged_formulas.remove(&sheet);
9513 }
9514 if committed > 0 {
9515 self.mark_topology_edited();
9516 }
9517 self.formula_parse_diagnostics.extend(pending_diagnostics);
9518 if !prepared.is_empty() || !prepared_packages.is_empty() {
9519 let mut ingest_delta = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
9520 ingest_delta.formula_cells_seen = (prepared.len() as u64).saturating_add(
9521 prepared_packages
9522 .iter()
9523 .map(|package| {
9524 (package.replay_records.len() as u64).saturating_add(
9525 if package.selected_points.is_some() {
9526 package.direct_cells
9527 } else {
9528 0
9529 },
9530 )
9531 })
9532 .sum::<u64>(),
9533 );
9534 ingest_delta.graph_formula_cells_materialized = committed as u64;
9535 ingest_delta.graph_vertices_created = new_vertices as u64;
9536 ingest_delta.graph_edges_created = new_edges as u64;
9537 for package in &prepared_packages {
9538 let source = &package.source_report;
9539 ingest_delta.source_formula_events = ingest_delta
9540 .source_formula_events
9541 .saturating_add(source.source_formula_events);
9542 ingest_delta.source_formula_records_spooled = ingest_delta
9543 .source_formula_records_spooled
9544 .saturating_add(source.source_formula_records_spooled);
9545 ingest_delta.source_spool_encoded_bytes = ingest_delta
9546 .source_spool_encoded_bytes
9547 .saturating_add(source.source_spool_encoded_bytes);
9548 ingest_delta.source_spool_peak_memory_bytes = ingest_delta
9549 .source_spool_peak_memory_bytes
9550 .max(source.source_spool_peak_memory_bytes);
9551 ingest_delta.source_spool_spilled_bytes = ingest_delta
9552 .source_spool_spilled_bytes
9553 .saturating_add(source.source_spool_spilled_bytes);
9554 ingest_delta.source_spool_spill_files = ingest_delta
9555 .source_spool_spill_files
9556 .saturating_add(source.source_spool_spill_files);
9557 ingest_delta.source_spool_replays = ingest_delta
9558 .source_spool_replays
9559 .saturating_add(source.source_spool_replays)
9560 .saturating_add(package.spool_replays);
9561 ingest_delta.source_families_seen = ingest_delta
9562 .source_families_seen
9563 .saturating_add(source.families_seen);
9564 ingest_delta.source_family_cells_seen = ingest_delta
9565 .source_family_cells_seen
9566 .saturating_add(source.family_cells_seen);
9567 ingest_delta.source_family_shadow_eligible = ingest_delta
9568 .source_family_shadow_eligible
9569 .saturating_add(source.source_clean_families);
9570 ingest_delta.source_family_shadow_eligible_cells = ingest_delta
9571 .source_family_shadow_eligible_cells
9572 .saturating_add(source.source_clean_cells);
9573 ingest_delta.source_partitioned_families_seen = ingest_delta
9574 .source_partitioned_families_seen
9575 .saturating_add(source.source_fragmentable_families);
9576 ingest_delta.source_partition_holes = ingest_delta
9577 .source_partition_holes
9578 .saturating_add(source.source_hole_exclusions);
9579 ingest_delta.source_partition_ordinary_exceptions = ingest_delta
9580 .source_partition_ordinary_exceptions
9581 .saturating_add(source.source_ordinary_exclusions);
9582 ingest_delta.source_partition_surviving_cells = ingest_delta
9583 .source_partition_surviving_cells
9584 .saturating_add(source.source_fragmentable_cells);
9585 for (reason, count) in &source.fallback_reasons {
9586 let total = ingest_delta
9587 .fallback_reasons
9588 .entry(reason.clone())
9589 .or_default();
9590 *total = total.saturating_add(*count);
9591 }
9592
9593 ingest_delta.source_family_fallback = ingest_delta
9594 .source_family_fallback
9595 .saturating_add(source.families_seen);
9596 ingest_delta.source_family_fallback_cells = ingest_delta
9597 .source_family_fallback_cells
9598 .saturating_add(source.family_cells_seen);
9599 }
9600 self.record_formula_ingest_report(ingest_delta);
9601 }
9602 let commit_window = commit_started.elapsed();
9603 let retained_cells = self
9604 .staged_formula_index
9605 .all_leases()
9606 .into_iter()
9607 .filter_map(|(sheet, lease)| {
9608 formualizer_common::RangeAddress::new(
9609 sheet, lease.row, lease.col, lease.row, lease.col,
9610 )
9611 .ok()
9612 })
9613 .collect::<Vec<_>>();
9614 let observed_scratch_bytes = self
9615 .active_resource_ledger
9616 .as_ref()
9617 .map_or(0, |ledger| ledger.snapshot().scratch_peak)
9618 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9619 let committed_spans = 0u64;
9620 let selected_source_families = prepared_packages
9621 .iter()
9622 .filter(|package| package.selected_points.is_none())
9623 .map(|package| package.lease.family_count)
9624 .sum::<usize>()
9625 + prepared_packages
9626 .iter()
9627 .filter(|package| package.selected_points.is_some())
9628 .flat_map(|package| package.replay_records.iter())
9629 .filter_map(|record| record.partition_owner.or(record.family))
9630 .chain(
9631 prepared_packages
9632 .iter()
9633 .flat_map(|package| package.complete_selections.iter().copied()),
9634 )
9635 .collect::<BTreeSet<_>>()
9636 .len();
9637 let selected_staged_cells = prepared.len().saturating_add(
9638 prepared_packages
9639 .iter()
9640 .map(|package| {
9641 package
9642 .selected_points
9643 .as_ref()
9644 .map_or(package.replay_records.len(), BTreeSet::len)
9645 })
9646 .sum::<usize>(),
9647 );
9648 let actual_commit_work = (new_vertices as u64)
9649 .saturating_add(new_edges as u64)
9650 .saturating_add(committed as u64)
9651 .saturating_add(committed_spans)
9652 .saturating_add(prepared.len() as u64)
9653 .saturating_add(
9654 prepared_packages
9655 .iter()
9656 .map(|package| {
9657 package
9658 .selected_points
9659 .as_ref()
9660 .map_or(1, |points| points.len() as u64)
9661 })
9662 .sum::<u64>(),
9663 );
9664 let report = PreparedTargetGraphReport {
9665 request_id: request_id.unwrap_or_default(),
9666 requested_targets: targets.len(),
9667 normalized_regions: visited_regions.len(),
9668 normalized_target_list: normalized,
9669 selected_staged_cells,
9670 selected_source_families,
9671 retained_staged_cells: self.staged_formula_count(),
9672 selected_cells,
9673 retained_cells,
9674 widened_scope: scope,
9675 widening_reasons: reasons,
9676 revisions: assumptions,
9677 commit_window,
9678 estimated_scratch_bytes: scratch_bytes,
9679 observed_scratch_bytes,
9680 estimated_commit_work: (new_vertices as u64)
9681 .saturating_add(new_edges as u64)
9682 .saturating_add(prepared.len() as u64)
9683 .saturating_add(selected_package_records),
9684 actual_commit_work,
9685 outcome: PreparationOutcome::Prepared,
9686 };
9687 self.observe_target_preparation_report(&report);
9688 Ok(report)
9689 }
9690
9691 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9693 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
9694 engine.build_graph_all_unobserved()
9695 })
9696 }
9697
9698 fn build_graph_all_unobserved(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9699 let selected = self.staged_formula_count();
9700 let started = crate::instant::FzInstant::now();
9701 self.resource_checkpoint(selected as u64)?;
9702 let scratch_bytes = (selected as u64).saturating_mul(256);
9703 let result = self.with_request_scratch(scratch_bytes, |engine| {
9704 let index_snapshot = engine.staged_formula_index.clone();
9705 let collected = std::mem::take(&mut engine.staged_formulas)
9706 .into_iter()
9707 .collect();
9708 engine.staged_formula_index.clear_all();
9709 engine.build_graph_from_staged_batches(collected, false, index_snapshot)
9710 });
9711 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9712 result
9713 }
9714
9715 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
9717 &mut self,
9718 sheets: I,
9719 ) -> Result<(), formualizer_parse::ExcelError> {
9720 let mut sheets = sheets.into_iter();
9721 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, move |engine| {
9722 let name_scratch = (sheets.size_hint().0 as u64).saturating_mul(64);
9723 engine.with_request_scratch(name_scratch, |engine| {
9724 let names = sheets.by_ref().map(str::to_string).collect::<Vec<_>>();
9727 engine.charge_bounded_work(names.len() as u64)?;
9728 engine.build_graph_for_sheet_names_unobserved(names)
9729 })
9730 })
9731 }
9732
9733 fn build_graph_for_sheet_names_unobserved(
9734 &mut self,
9735 sheets: Vec<String>,
9736 ) -> Result<(), formualizer_parse::ExcelError> {
9737 let started = crate::instant::FzInstant::now();
9738 let selected = sheets
9739 .iter()
9740 .filter_map(|sheet| self.staged_formulas.get(sheet))
9741 .map(StagedSheet::len)
9742 .sum::<usize>();
9743 self.resource_checkpoint(selected as u64)?;
9744 let scratch_bytes = (selected as u64).saturating_mul(256);
9745 self.reserve_request_scratch(scratch_bytes)?;
9746 let index_snapshot = self.staged_formula_index.clone();
9747 let mut collected = Vec::new();
9748 for sheet in sheets {
9749 if let Some(staged) = self.staged_formulas.remove(&sheet) {
9750 self.index_removed_staged_sheet(&sheet, &staged);
9751 collected.push((sheet, staged));
9752 }
9753 }
9754 let result = self.build_graph_from_staged_batches(collected, true, index_snapshot);
9755 self.release_request_scratch(scratch_bytes);
9756 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9757 result
9758 }
9759
9760 fn build_graph_from_staged_batches(
9761 &mut self,
9762 collected: StagedFormulaBatches,
9763 share_parse_cache_across_sheets: bool,
9764 staged_index_snapshot: StagedFormulaIndex,
9765 ) -> Result<(), formualizer_parse::ExcelError> {
9766 if collected.is_empty() {
9767 return Ok(());
9768 }
9769 for (sheet, _) in &collected {
9770 let _ = self.add_sheet(sheet);
9771 }
9772
9773 let diagnostics_len = self.formula_parse_diagnostics.len();
9774 let mut collected = collected;
9775 let prepared = match self
9776 .prepare_staged_formula_batches(&mut collected, share_parse_cache_across_sheets)
9777 {
9778 Ok(prepared) => prepared,
9779 Err(error) => {
9780 self.formula_parse_diagnostics.truncate(diagnostics_len);
9781 for (sheet, staged) in collected {
9782 self.restore_staged_sheet(sheet, staged);
9783 }
9784 self.staged_formula_index = staged_index_snapshot;
9785 return Err(error);
9786 }
9787 };
9788 let (ordinary, compressed, direct, may_fail) = prepared;
9789
9790 let first_build = if self.deferred_build_can_be_first_load() {
9800 if let Err(error) = self.check_staged_formula_plans(
9801 ordinary
9802 .iter()
9803 .chain(compressed.iter().map(|(batch, _)| batch)),
9804 &may_fail,
9805 ) {
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.deferred_build_as_first_load()
9814 } else {
9815 None
9816 };
9817 let built_sheets: Vec<String> = if first_build.is_some() {
9818 collected.iter().map(|(sheet, _)| sheet.clone()).collect()
9819 } else {
9820 Vec::new()
9821 };
9822
9823 let result = (|| {
9827 if !ordinary.is_empty() {
9828 self.ingest_formula_batches(ordinary)?;
9829 }
9830 if !compressed.is_empty() {
9831 self.ingest_compressed_formula_source_batches(compressed)?;
9832 }
9833 if !direct.is_empty() {
9834 self.finish_compressed_formula_sources(direct)?;
9835 }
9836 Ok(())
9837 })();
9838 if let Some(saved) = first_build {
9839 self.leave_deferred_first_load(saved, &built_sheets);
9840 }
9841 if let Err(error) = result {
9842 self.formula_parse_diagnostics.truncate(diagnostics_len);
9843 for (sheet, staged) in collected {
9844 self.restore_staged_sheet(sheet, staged);
9845 }
9846 self.staged_formula_index = staged_index_snapshot;
9847 return Err(error);
9848 }
9849 self.dedup_formula_parse_diagnostics_since(diagnostics_len);
9850 Ok(())
9851 }
9852
9853 fn deferred_build_can_be_first_load(&self) -> bool {
9859 !self.graph.first_load_assume_new()
9860 && !self.graph_admission_enabled()
9861 && self.graph.formula_vertex_count() == 0
9862 }
9863
9864 fn check_staged_formula_plans<'b>(
9869 &mut self,
9870 batches: impl Iterator<Item = &'b FormulaIngestBatch>,
9871 may_fail: &FxHashSet<crate::engine::arena::AstNodeId>,
9872 ) -> Result<(), ExcelError> {
9873 if may_fail.is_empty() {
9874 return Ok(());
9875 }
9876 let mut seen: FxHashSet<(SheetId, crate::engine::arena::AstNodeId)> = FxHashSet::default();
9877 for batch in batches {
9878 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
9879 ExcelError::new(ExcelErrorKind::Ref)
9880 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
9881 })?;
9882 let mut pipeline = self.ingest_pipeline();
9883 for record in &batch.formulas {
9884 if record.member_anchor.is_some()
9885 || !may_fail.contains(&record.ast_id)
9886 || !seen.insert((sheet_id, record.ast_id))
9887 {
9888 continue;
9889 }
9890 let placement = CellRef::new(
9891 sheet_id,
9892 Coord::from_excel(record.row, record.col, true, true),
9893 );
9894 pipeline.ingest_formula(
9895 FormulaAstInput::RawArena(record.ast_id),
9896 placement,
9897 None,
9898 )?;
9899 }
9900 }
9901 Ok(())
9902 }
9903
9904 fn deferred_build_as_first_load(&mut self) -> Option<(crate::engine::SheetIndexMode, usize)> {
9905 if !self.deferred_build_can_be_first_load() {
9906 return None;
9907 }
9908 let saved = (
9909 self.graph.get_config().sheet_index_mode,
9910 self.config.range_expansion_limit,
9911 );
9912 self.graph
9913 .set_sheet_index_mode(crate::engine::SheetIndexMode::Lazy);
9914 self.config.range_expansion_limit = 0;
9915 self.graph.set_first_load_assume_new(true);
9916 self.graph.reset_ensure_touched();
9917 Some(saved)
9918 }
9919
9920 fn leave_deferred_first_load(
9923 &mut self,
9924 (index_mode, range_limit): (crate::engine::SheetIndexMode, usize),
9925 sheets: &[String],
9926 ) {
9927 self.graph.set_first_load_assume_new(false);
9928 self.graph.reset_ensure_touched();
9929 self.graph.set_sheet_index_mode(index_mode);
9930 self.config.range_expansion_limit = range_limit;
9931 for sheet in sheets {
9932 self.graph.finalize_sheet_index(sheet);
9933 }
9934 }
9935
9936 fn prepare_staged_formula_batches(
9937 &mut self,
9938 collected: &mut StagedFormulaBatches,
9939 share_parse_cache_across_sheets: bool,
9940 ) -> Result<PreparedStagedFormulaBatches, formualizer_parse::ExcelError> {
9941 let mut ordinary = Vec::new();
9942 let mut compressed = Vec::new();
9943 let mut direct = Vec::new();
9944 let mut may_fail = FxHashSet::default();
9945 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
9946 rustc_hash::FxHashMap::default();
9947 cache.reserve(4096);
9948
9949 for (sheet, staged) in collected {
9950 if !share_parse_cache_across_sheets {
9951 cache.clear();
9952 }
9953 let mut grouper = crate::engine::FormulaFamilyGrouper::new();
9957 let mut replayed_records = Vec::new();
9961 let deferred_source = None;
9962 let mut deferred_fallback = None;
9963 if let Some(package) = staged.deferred_package.as_mut() {
9964 if package.sheet_name != *sheet {
9965 return Err(ExcelError::new(ExcelErrorKind::Value)
9966 .with_message("deferred formula package sheet mismatch"));
9967 }
9968 let eligible: Vec<_> = package
9969 .families
9970 .iter()
9971 .filter(|family| !package.invalidated.contains(&family.source_id))
9972 .cloned()
9973 .collect();
9974 let eligible_partitions: Vec<_> = package
9975 .partitioned_families
9976 .iter()
9977 .filter(|family| !package.invalidated.contains(&family.source_id))
9978 .cloned()
9979 .collect();
9980
9981 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
9982 for partition in &eligible_partitions {
9983 replay_disposition
9984 .register_partition(partition, false)
9985 .map_err(|reason| {
9986 ExcelError::new(ExcelErrorKind::Value).with_message(reason)
9987 })?;
9988 }
9989 replay_disposition
9990 .extend_suppressed_excel_coords(package.suppressed.iter().copied());
9991 let replayed = package
9992 .replay
9993 .lock()
9994 .map_err(|_| {
9995 ExcelError::new(ExcelErrorKind::Value)
9996 .with_message("deferred formula spool lock poisoned")
9997 })?
9998 .replay(&replay_disposition)
9999 .map_err(|message| {
10000 ExcelError::new(ExcelErrorKind::Value).with_message(message)
10001 })?;
10002 replayed_records = replayed;
10003 let mut report = package.accounting_report();
10004 report.source_spool_replays = report.source_spool_replays.saturating_add(1);
10005 deferred_fallback = Some((report, package.families.clone(), eligible_partitions));
10006 }
10007
10008 let n_entries = staged.entries.len() + replayed_records.len();
10009 let entries = staged
10010 .entries
10011 .iter()
10012 .cloned()
10013 .map(|(row, col, text)| (row, col, text, None))
10014 .chain(replayed_records.into_iter().map(|record| {
10015 (
10016 record.row,
10017 record.col,
10018 record.text,
10019 Some((record.source_order, record.family, record.partition_owner)),
10020 )
10021 }));
10022 let mut formulas = Vec::with_capacity(n_entries);
10023 let staged_order_base = u64::MAX.saturating_sub(n_entries as u64);
10024 for (entry_index, (row, col, txt, source_proof)) in entries.enumerate() {
10025 let key = if txt.starts_with('=') {
10026 txt
10027 } else {
10028 format!("={txt}")
10029 };
10030 let staged_record = if let Some(cached) = cache.get(&key) {
10031 cached.map(|ast_id| {
10032 self.note_staged_formula(&mut grouper, row, col, ast_id);
10033 FormulaIngestRecord::new(row, col, ast_id, Some(Arc::<str>::from(key)))
10034 })
10035 } else {
10036 let parsed = match formualizer_parse::parser::parse(&key) {
10037 Ok(parsed) => Some(parsed),
10038 Err(error) => self.handle_formula_parse_error(
10039 sheet,
10040 row,
10041 col,
10042 &key,
10043 error.to_string(),
10044 )?,
10045 };
10046 match parsed {
10047 Some(ast) => {
10048 let record = self.stage_formula_ast(&mut grouper, row, col, &ast, None);
10049 if !record.is_family_member() && self.formula_may_fail_planning(&ast) {
10050 may_fail.insert(record.ast_id);
10051 }
10052 if record.is_family_member() {
10055 Some(record)
10056 } else {
10057 let ast_id = record.ast_id;
10058 cache.insert(key.clone(), Some(ast_id));
10059 Some(FormulaIngestRecord::new(
10060 row,
10061 col,
10062 ast_id,
10063 Some(Arc::<str>::from(key)),
10064 ))
10065 }
10066 }
10067 None => {
10068 cache.insert(key, None);
10069 None
10070 }
10071 }
10072 };
10073
10074 if let Some(mut formula) = staged_record {
10075 if let Some((order, family, owner)) = source_proof {
10076 formula = formula.with_source_proof(order, family, owner);
10077 } else if deferred_source.is_some() {
10078 formula = formula.with_source_proof(
10079 crate::engine::SourceFormulaOrder::new(
10080 staged_order_base.saturating_add(entry_index as u64),
10081 ),
10082 None,
10083 None,
10084 );
10085 }
10086 formulas.push(formula);
10087 }
10088 }
10089
10090 let batch = FormulaIngestBatch::new(sheet.clone(), formulas);
10091 if let Some((report, preparation)) = deferred_source {
10092 direct.push((batch, report, preparation));
10093 } else if let Some((report, families, partitions)) = deferred_fallback {
10094 let source_batch = crate::engine::FormulaCompressedSourceBatch::with_proposals(
10095 batch.sheet_name.clone(),
10096 report,
10097 families,
10098 partitions,
10099 );
10100 compressed.push((batch, source_batch));
10101 } else if !batch.is_empty() {
10102 ordinary.push(batch);
10103 }
10104 }
10105 Ok((ordinary, compressed, direct, may_fail))
10106 }
10107
10108 fn formula_may_fail_planning(&self, ast: &formualizer_parse::parser::ASTNode) -> bool {
10113 use formualizer_parse::parser::{ASTNodeType, ReferenceType};
10114 let sheet_missing = |sheet: &Option<String>| {
10115 sheet
10116 .as_deref()
10117 .is_some_and(|s| self.graph.sheet_id(s).is_none())
10118 };
10119 match &ast.node_type {
10120 ASTNodeType::Literal(_) | ASTNodeType::Omitted => false,
10121 ASTNodeType::Reference { reference, .. } => match reference {
10122 ReferenceType::Cell { sheet, .. } => sheet_missing(sheet),
10123 ReferenceType::Range {
10124 sheet,
10125 start_row,
10126 start_col,
10127 end_row,
10128 end_col,
10129 ..
10130 } => {
10131 sheet_missing(sheet)
10132 || matches!((start_row, end_row), (Some(a), Some(b)) if a > b)
10133 || matches!((start_col, end_col), (Some(a), Some(b)) if a > b)
10134 }
10135 ReferenceType::NamedRange(_) => false,
10136 _ => true,
10137 },
10138 ASTNodeType::UnaryOp { expr, .. } => self.formula_may_fail_planning(expr),
10139 ASTNodeType::BinaryOp { left, right, .. } => {
10140 self.formula_may_fail_planning(left) || self.formula_may_fail_planning(right)
10141 }
10142 ASTNodeType::Function { args, .. } => {
10143 args.iter().any(|a| self.formula_may_fail_planning(a))
10144 }
10145 ASTNodeType::Call { callee, args } => {
10146 self.formula_may_fail_planning(callee)
10147 || args.iter().any(|a| self.formula_may_fail_planning(a))
10148 }
10149 ASTNodeType::Array(rows) => rows
10150 .iter()
10151 .flatten()
10152 .any(|a| self.formula_may_fail_planning(a)),
10153 }
10154 }
10155
10156 pub fn begin_bulk_ingest_arrow(
10158 &mut self,
10159 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
10160 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
10161 }
10162
10163 pub fn begin_bulk_update_arrow(
10165 &mut self,
10166 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
10167 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
10168 }
10169
10170 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
10171 self.graph.sheet_id(sheet).ok_or(
10172 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10173 name: sheet.to_string(),
10174 reason: "Unknown sheet".to_string(),
10175 },
10176 )
10177 }
10178
10179 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
10180 if row_1based == 0 {
10181 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10182 }
10183 Ok(row_1based - 1)
10184 }
10185
10186 fn normalize_row_range_1based(
10187 start_row_1based: u32,
10188 end_row_1based: u32,
10189 ) -> Result<(u32, u32), crate::engine::EditorError> {
10190 if start_row_1based == 0 || end_row_1based == 0 {
10191 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10192 }
10193 if start_row_1based > end_row_1based {
10194 return Err(crate::engine::EditorError::TransactionFailed {
10195 reason: "Row range start is greater than end".to_string(),
10196 });
10197 }
10198 Ok((start_row_1based - 1, end_row_1based - 1))
10199 }
10200
10201 fn invalidate_row_visibility_mask_cache(&self) {
10202 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10203 cache.clear();
10204 }
10205 }
10206
10207 fn set_row_hidden_by_sheet_id(
10208 &mut self,
10209 sheet_id: SheetId,
10210 row0: u32,
10211 hidden: bool,
10212 source: RowVisibilitySource,
10213 ) -> bool {
10214 let changed = {
10215 let state = self.row_visibility.entry(sheet_id).or_default();
10216 state.set_row_hidden(row0, hidden, source)
10217 };
10218
10219 let remove_entry = self
10220 .row_visibility
10221 .get(&sheet_id)
10222 .map(|state| state.is_empty())
10223 .unwrap_or(false);
10224 if remove_entry {
10225 self.row_visibility.remove(&sheet_id);
10226 }
10227
10228 if changed {
10229 self.invalidate_row_visibility_mask_cache();
10230 }
10231
10232 changed
10233 }
10234
10235 fn set_rows_hidden_by_sheet_id(
10236 &mut self,
10237 sheet_id: SheetId,
10238 start_row0: u32,
10239 end_row0: u32,
10240 hidden: bool,
10241 source: RowVisibilitySource,
10242 ) -> bool {
10243 let changed = {
10244 let state = self.row_visibility.entry(sheet_id).or_default();
10245 state.set_rows_hidden(start_row0, end_row0, hidden, source)
10246 };
10247
10248 let remove_entry = self
10249 .row_visibility
10250 .get(&sheet_id)
10251 .map(|state| state.is_empty())
10252 .unwrap_or(false);
10253 if remove_entry {
10254 self.row_visibility.remove(&sheet_id);
10255 }
10256
10257 if changed {
10258 self.invalidate_row_visibility_mask_cache();
10259 }
10260
10261 changed
10262 }
10263
10264 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
10265 if count == 0 {
10266 return;
10267 }
10268 let mut changed = false;
10269 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10270 changed = state.insert_rows(before0, count);
10271 state.is_empty()
10272 } else {
10273 false
10274 };
10275 if remove_entry {
10276 self.row_visibility.remove(&sheet_id);
10277 }
10278 if changed {
10279 self.invalidate_row_visibility_mask_cache();
10280 }
10281 }
10282
10283 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
10284 if count == 0 {
10285 return;
10286 }
10287 let mut changed = false;
10288 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10289 changed = state.delete_rows(start0, count);
10290 state.is_empty()
10291 } else {
10292 false
10293 };
10294 if remove_entry {
10295 self.row_visibility.remove(&sheet_id);
10296 }
10297 if changed {
10298 self.invalidate_row_visibility_mask_cache();
10299 }
10300 }
10301
10302 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10303 if let crate::engine::ChangeEvent::SetRowVisibility {
10304 sheet_id,
10305 row0,
10306 source,
10307 old_hidden,
10308 ..
10309 } = event
10310 {
10311 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
10312 }
10313 }
10314
10315 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10316 if let crate::engine::ChangeEvent::SetRowVisibility {
10317 sheet_id,
10318 row0,
10319 source,
10320 new_hidden,
10321 ..
10322 } = event
10323 {
10324 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
10325 }
10326 }
10327
10328 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10329 for event in events.iter().rev() {
10330 self.apply_inverse_row_visibility_event(event);
10331 }
10332 }
10333
10334 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10335 for event in events {
10336 self.apply_forward_row_visibility_event(event);
10337 }
10338 }
10339
10340 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10341 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10342 sheet,
10343 row,
10344 col,
10345 old,
10346 ..
10347 } = event
10348 {
10349 self.apply_staged_formula_cell(sheet, *row, *col, old.as_deref());
10350 }
10351 }
10352
10353 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10354 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10355 sheet,
10356 row,
10357 col,
10358 new,
10359 ..
10360 } = event
10361 {
10362 self.apply_staged_formula_cell(sheet, *row, *col, new.as_deref());
10363 }
10364 }
10365
10366 fn apply_staged_formula_cell(&mut self, sheet: &str, row: u32, col: u32, target: Option<&str>) {
10369 match target {
10370 Some(text) => self.stage_formula_text(sheet, row, col, text.to_string()),
10371 None => {
10372 self.clear_staged_formula_text(sheet, row, col);
10373 }
10374 }
10375 }
10376
10377 pub fn set_row_hidden(
10378 &mut self,
10379 sheet: &str,
10380 row_1based: u32,
10381 hidden: bool,
10382 source: RowVisibilitySource,
10383 ) -> Result<(), crate::engine::EditorError> {
10384 self.observe_function_semantic_epoch()
10385 .map_err(crate::engine::EditorError::Excel)?;
10386 self.observe_function_semantic_epoch()
10387 .map_err(crate::engine::EditorError::Excel)?;
10388 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10389 let row0 = Self::normalize_row_1based(row_1based)?;
10390 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
10391 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10392 sheet_id, row0,
10393 )));
10394 self.mark_data_edited();
10395 }
10396 Ok(())
10397 }
10398
10399 pub fn set_rows_hidden(
10400 &mut self,
10401 sheet: &str,
10402 start_row_1based: u32,
10403 end_row_1based: u32,
10404 hidden: bool,
10405 source: RowVisibilitySource,
10406 ) -> Result<(), crate::engine::EditorError> {
10407 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10408 let (start_row0, end_row0) =
10409 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
10410 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
10411 if start_row0 == end_row0 {
10412 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10413 sheet_id, start_row0,
10414 )));
10415 } else {
10416 self.record_structural_change(StructuralScope::Sheet(sheet_id));
10417 }
10418 self.mark_data_edited();
10419 }
10420 Ok(())
10421 }
10422
10423 pub fn is_row_hidden(
10424 &self,
10425 sheet: &str,
10426 row_1based: u32,
10427 source: Option<RowVisibilitySource>,
10428 ) -> Option<bool> {
10429 let sheet_id = self.graph.sheet_id(sheet)?;
10430 let row0 = row_1based.checked_sub(1)?;
10431 Some(
10432 self.row_visibility
10433 .get(&sheet_id)
10434 .map(|state| state.is_row_hidden(row0, source))
10435 .unwrap_or(false),
10436 )
10437 }
10438
10439 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
10440 let sheet_id = self.graph.sheet_id(sheet)?;
10441 Some(
10442 self.row_visibility
10443 .get(&sheet_id)
10444 .map(|state| state.version())
10445 .unwrap_or(0),
10446 )
10447 }
10448
10449 fn build_row_visibility_mask_for_view(
10450 &self,
10451 view: &RangeView<'_>,
10452 mode: VisibilityMaskMode,
10453 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
10454 let sheet_rows = view.sheet().nrows as usize;
10455 if sheet_rows == 0 || view.start_row() >= sheet_rows {
10456 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
10457 }
10458
10459 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
10460 let start_row0 = view.start_row() as u32;
10461 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
10462 let version = self
10463 .row_visibility
10464 .get(&sheet_id)
10465 .map(|state| state.version())
10466 .unwrap_or(0);
10467 let key = VisibilityMaskCacheKey {
10468 sheet_id,
10469 start_row0,
10470 end_row0,
10471 mode,
10472 version,
10473 };
10474
10475 if let Ok(cache) = self.row_visibility_mask_cache.read()
10476 && let Some(mask) = cache.get(&key)
10477 {
10478 #[cfg(test)]
10479 visibility_mask_test_hooks::inc_hit();
10480 return Some(mask.clone());
10481 }
10482
10483 #[cfg(test)]
10484 visibility_mask_test_hooks::inc_miss();
10485
10486 let state = self.row_visibility.get(&sheet_id);
10487 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
10488 for row0 in start_row0..=end_row0 {
10489 let manual_hidden = state
10490 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
10491 .unwrap_or(false);
10492 let filter_hidden = state
10493 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
10494 .unwrap_or(false);
10495
10496 let include = match mode {
10497 VisibilityMaskMode::IncludeAll => true,
10498 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
10499 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
10500 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
10501 !(manual_hidden || filter_hidden)
10502 }
10503 };
10504 out.push(include);
10505 }
10506
10507 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
10508 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10509 const MAX_CACHE_ENTRIES: usize = 4096;
10510 if cache.len() >= MAX_CACHE_ENTRIES {
10511 cache.clear();
10512 #[cfg(test)]
10513 visibility_mask_test_hooks::inc_eviction();
10514 }
10515 cache.insert(key, mask.clone());
10516 }
10517
10518 Some(mask)
10519 }
10520
10521 fn observe_function_semantic_epoch(&mut self) -> Result<bool, ExcelError> {
10522 let changes =
10523 crate::function_registry::semantic_changes_since(self.function_semantic_epoch_seen);
10524 let global_changed = changes.epoch != self.function_semantic_epoch_seen;
10525 let provider_revision = self.resolver.planning_semantic_revision();
10526 let provider_changed = provider_revision != self.function_provider_revision_seen;
10527 if !global_changed && !provider_changed {
10528 return Ok(false);
10529 }
10530
10531 let changed = !changes.keys.is_empty();
10532 if global_changed && changed || provider_changed {
10533 self.cached_static_schedule = None;
10534 self.recent_schedules.clear();
10535 self.base_schedule = None;
10536 }
10537 self.function_semantic_epoch_seen = changes.epoch;
10538 self.function_provider_revision_seen = provider_revision;
10539 Ok(false)
10540 }
10541
10542 pub(crate) fn ast_uses_changed_function(
10543 ast: &ASTNode,
10544 changed: &BTreeSet<(String, String)>,
10545 ) -> bool {
10546 match &ast.node_type {
10547 ASTNodeType::Function { name, args } => {
10548 let normalized = name.to_uppercase();
10549 let mut spellings = vec![(String::new(), normalized.clone())];
10550 let mut stripped = normalized.as_str();
10551 loop {
10552 let Some(rest) = ["_XLFN.", "_XLL.", "_XLWS."]
10553 .iter()
10554 .find_map(|prefix| stripped.strip_prefix(prefix))
10555 else {
10556 break;
10557 };
10558 stripped = rest;
10559 spellings.push((String::new(), stripped.to_string()));
10560 }
10561 let resolved = crate::function_registry::resolve("", name);
10562 let directly_changed = spellings.iter().any(|spelling| changed.contains(spelling))
10563 || resolved.as_ref().is_some_and(|resolved| {
10564 changed.contains(&(
10565 resolved.namespace.clone(),
10566 resolved.canonical_name.clone(),
10567 ))
10568 });
10569 directly_changed
10570 || resolved.is_none()
10571 || args
10572 .iter()
10573 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10574 }
10575 ASTNodeType::Call { callee, args } => {
10576 Self::ast_uses_changed_function(callee, changed)
10577 || args
10578 .iter()
10579 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10580 }
10581 ASTNodeType::UnaryOp { expr, .. } => Self::ast_uses_changed_function(expr, changed),
10582 ASTNodeType::BinaryOp { left, right, .. } => {
10583 Self::ast_uses_changed_function(left, changed)
10584 || Self::ast_uses_changed_function(right, changed)
10585 }
10586 ASTNodeType::Array(rows) => rows
10587 .iter()
10588 .flatten()
10589 .any(|node| Self::ast_uses_changed_function(node, changed)),
10590 _ => false,
10591 }
10592 }
10593
10594 fn ast_contains_function(ast: &ASTNode) -> bool {
10595 match &ast.node_type {
10596 ASTNodeType::Function { .. } => true,
10597 ASTNodeType::Call { callee, args } => {
10598 Self::ast_contains_function(callee) || args.iter().any(Self::ast_contains_function)
10599 }
10600 ASTNodeType::UnaryOp { expr, .. } => Self::ast_contains_function(expr),
10601 ASTNodeType::BinaryOp { left, right, .. } => {
10602 Self::ast_contains_function(left) || Self::ast_contains_function(right)
10603 }
10604 ASTNodeType::Array(rows) => rows.iter().flatten().any(Self::ast_contains_function),
10605 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => false,
10606 }
10607 }
10608
10609 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
10610 Region::rows_from(sheet_id, start_row0)
10611 }
10612
10613 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
10614 Region::cols_from(sheet_id, start_col0)
10615 }
10616
10617 #[cfg(test)]
10618 pub(crate) fn force_non_cycle_schedule_fallback_for_test(&mut self) {
10619 self.force_non_cycle_schedule_fallback_for_test = true;
10620 }
10621
10622 fn materialize_deferred_sheet_before_structural_edit(
10623 &mut self,
10624 sheet: &str,
10625 ) -> Result<(), crate::engine::EditorError> {
10626 if self.staged_formulas.contains_key(sheet) {
10627 self.build_graph_for_sheets([sheet])?;
10628 }
10629 Ok(())
10630 }
10631
10632 fn structural_row_occupancy(
10633 &self,
10634 sheet: &str,
10635 sheet_id: SheetId,
10636 ) -> crate::engine::graph::StructuralOccupancy {
10637 if !self.graph.has_compressed_range_readers() {
10638 return crate::engine::graph::StructuralOccupancy::default();
10639 }
10640 let mut occupancy = self.graph.structural_occupancy(sheet_id);
10641 if let Some(arrow_sheet) = self.arrow_sheets.sheet(sheet) {
10642 occupancy.include_arrow_sheet(arrow_sheet);
10643 occupancy
10644 } else {
10645 crate::engine::graph::StructuralOccupancy::conservative()
10647 }
10648 }
10649
10650 fn structural_column_occupancy(&self) -> crate::engine::graph::StructuralOccupancy {
10651 crate::engine::graph::StructuralOccupancy::conservative()
10655 }
10656
10657 pub fn insert_rows(
10659 &mut self,
10660 sheet: &str,
10661 before: u32,
10662 count: u32,
10663 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10664 {
10665 if count == 0 {
10666 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10667 }
10668 self.observe_function_semantic_epoch()
10669 .map_err(crate::engine::EditorError::Excel)?;
10670 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10671 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10672 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10673 let before0 = before.saturating_sub(1);
10674 let affected_region = Self::structural_row_region(sheet_id, before0);
10675 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10676 let summary = {
10677 let mut editor =
10678 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10679 editor.insert_rows(sheet_id, before0, count)?
10680 };
10681 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10682 let before0 = before0 as usize;
10683 asheet.insert_rows(before0, count as usize);
10684 }
10685 self.purge_derived_formats_after_row(sheet_id, before0);
10686 self.mark_moved_formula_vertices_dirty(&summary);
10687 self.clear_computed_overlay_after_row(sheet, before0 as usize);
10688 self.shift_row_visibility_insert(sheet_id, before0, count);
10689 self.record_structural_change(StructuralScope::Region(affected_region));
10690 self.mark_topology_edited();
10691 Ok(summary)
10692 }
10693
10694 pub fn delete_rows(
10696 &mut self,
10697 sheet: &str,
10698 start: u32,
10699 count: u32,
10700 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10701 {
10702 if count == 0 {
10703 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10704 }
10705 self.observe_function_semantic_epoch()
10706 .map_err(crate::engine::EditorError::Excel)?;
10707 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10708 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10709 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10710 let start0 = start.saturating_sub(1);
10711 let affected_region = Self::structural_row_region(sheet_id, start0);
10712 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10713 let summary = {
10714 let mut editor =
10715 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10716 editor.delete_rows(sheet_id, start0, count)?
10717 };
10718 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10719 let start0 = start0 as usize;
10720 asheet.delete_rows(start0, count as usize);
10721 }
10722 self.purge_derived_formats_after_row(sheet_id, start0);
10723 self.mark_moved_formula_vertices_dirty(&summary);
10724 self.clear_computed_overlay_after_row(sheet, start0 as usize);
10725 self.shift_row_visibility_delete(sheet_id, start0, count);
10726 self.record_structural_change(StructuralScope::Region(affected_region));
10727 self.mark_topology_edited();
10728 Ok(summary)
10729 }
10730
10731 pub fn insert_columns(
10733 &mut self,
10734 sheet: &str,
10735 before: u32,
10736 count: u32,
10737 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10738 {
10739 if count == 0 {
10740 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10741 }
10742 self.observe_function_semantic_epoch()
10743 .map_err(crate::engine::EditorError::Excel)?;
10744 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10745 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10746 let sheet_id = self.graph.sheet_id(sheet).ok_or(
10747 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10748 name: sheet.to_string(),
10749 reason: "Unknown sheet".to_string(),
10750 },
10751 )?;
10752 let before0 = before.saturating_sub(1);
10753 let affected_region = Self::structural_col_region(sheet_id, before0);
10754 let occupancy = self.structural_column_occupancy();
10755 let summary = {
10756 let mut editor =
10757 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10758 editor.insert_columns(sheet_id, before0, count)?
10759 };
10760 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10761 let before0 = before0 as usize;
10762 asheet.insert_columns(before0, count as usize);
10763 }
10764 self.purge_derived_formats_after_col(sheet_id, before0);
10765 self.mark_moved_formula_vertices_dirty(&summary);
10766 self.clear_computed_overlay_after_col(sheet, before0 as usize);
10767 self.record_structural_change(StructuralScope::Region(affected_region));
10768 self.mark_topology_edited();
10769 Ok(summary)
10770 }
10771
10772 pub fn delete_columns(
10774 &mut self,
10775 sheet: &str,
10776 start: u32,
10777 count: u32,
10778 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10779 {
10780 if count == 0 {
10781 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10782 }
10783 self.observe_function_semantic_epoch()
10784 .map_err(crate::engine::EditorError::Excel)?;
10785 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10786 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10787 let sheet_id = self.graph.sheet_id(sheet).ok_or(
10788 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10789 name: sheet.to_string(),
10790 reason: "Unknown sheet".to_string(),
10791 },
10792 )?;
10793 let start0 = start.saturating_sub(1);
10794 let affected_region = Self::structural_col_region(sheet_id, start0);
10795 let occupancy = self.structural_column_occupancy();
10796 let summary = {
10797 let mut editor =
10798 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10799 editor.delete_columns(sheet_id, start0, count)?
10800 };
10801 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10802 let start0 = start0 as usize;
10803 asheet.delete_columns(start0, count as usize);
10804 }
10805 self.purge_derived_formats_after_col(sheet_id, start0);
10806 self.mark_moved_formula_vertices_dirty(&summary);
10807 self.clear_computed_overlay_after_col(sheet, start0 as usize);
10808 self.record_structural_change(StructuralScope::Region(affected_region));
10809 self.mark_topology_edited();
10810 Ok(summary)
10811 }
10812 fn arrow_used_row_bounds(
10814 &self,
10815 sheet: &str,
10816 start_col: u32,
10817 end_col: u32,
10818 ) -> Option<(u32, u32)> {
10819 let a = self.sheet_store().sheet(sheet)?;
10820 if a.columns.is_empty() {
10821 return None;
10822 }
10823 let sc0 = start_col.saturating_sub(1) as usize;
10824 let ec0 = end_col.saturating_sub(1) as usize;
10825 let col_hi = a.columns.len().saturating_sub(1);
10826 if sc0 > col_hi {
10827 return None;
10828 }
10829 let ec0 = ec0.min(col_hi);
10830 let snap = self.data_snapshot_id();
10832 let mut min_r0: Option<usize> = None;
10833 for ci in sc0..=ec0 {
10834 let sheet_id = self.graph.sheet_id(sheet)?;
10835 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
10836 g.as_ref()
10837 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
10838 }) {
10839 let mv = mv as usize;
10840 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
10841 continue;
10842 }
10843 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
10845 if let Ok(mut g) = self.row_bounds_cache.write() {
10846 g.get_or_insert_with(|| RowBoundsCache::new(snap))
10847 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
10848 }
10849 if let Some(m) = min_c {
10850 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
10851 }
10852 }
10853 min_r0?;
10854 let mut max_r0: Option<usize> = None;
10855 for ci in sc0..=ec0 {
10856 let sheet_id = self.graph.sheet_id(sheet)?;
10857 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
10858 g.as_ref()
10859 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
10860 }) {
10861 let mv = mv as usize;
10862 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
10863 continue;
10864 }
10865 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
10866 if let Ok(mut g) = self.row_bounds_cache.write() {
10867 g.get_or_insert_with(|| RowBoundsCache::new(snap))
10868 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
10869 }
10870 if let Some(m) = max_c {
10871 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
10872 }
10873 }
10874 match (min_r0, max_r0) {
10875 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
10876 _ => None,
10877 }
10878 }
10879
10880 fn scan_column_used_bounds(
10881 a: &crate::arrow_store::ArrowSheet,
10882 ci: usize,
10883 ) -> (Option<u32>, Option<u32>) {
10884 let col = &a.columns[ci];
10885
10886 let mut min_r0: Option<u32> = None;
10888 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
10889 let tags = chunk.type_tag.values();
10890 for (off, &t) in tags.iter().enumerate() {
10891 let overlay_non_empty = chunk
10892 .overlay
10893 .get(off)
10894 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10895 .unwrap_or(false)
10896 || chunk
10897 .computed_overlay
10898 .get(off)
10899 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10900 .unwrap_or(false);
10901 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10902 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10903 break;
10904 };
10905 let row0 = chunk_start + off;
10906 min_r0 = Some(row0 as u32);
10907 break;
10908 }
10909 }
10910 if min_r0.is_some() {
10911 break;
10912 }
10913 }
10914 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
10915 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
10916 sparse_idxs.sort_unstable();
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 (off, &t) in tags.iter().enumerate() {
10926 let overlay_non_empty = chunk
10927 .overlay
10928 .get(off)
10929 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10930 .unwrap_or(false)
10931 || chunk
10932 .computed_overlay
10933 .get(off)
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 + off;
10938 min_r0 = Some(row0 as u32);
10939 break;
10940 }
10941 }
10942 if min_r0.is_some() {
10943 break;
10944 }
10945 }
10946 }
10947
10948 let mut max_r0: Option<u32> = None;
10950 if !col.sparse_chunks.is_empty() {
10951 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
10952 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
10953 for chunk_idx in sparse_idxs {
10954 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
10955 continue;
10956 };
10957 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10958 continue;
10959 };
10960 let tags = chunk.type_tag.values();
10961 for (rev_idx, &t) in tags.iter().enumerate().rev() {
10962 let overlay_non_empty = chunk
10963 .overlay
10964 .get(rev_idx)
10965 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10966 .unwrap_or(false)
10967 || chunk
10968 .computed_overlay
10969 .get(rev_idx)
10970 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10971 .unwrap_or(false);
10972 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10973 let row0 = chunk_start + rev_idx;
10974 max_r0 = Some(row0 as u32);
10975 break;
10976 }
10977 }
10978 if max_r0.is_some() {
10979 break;
10980 }
10981 }
10982 }
10983 if max_r0.is_none() {
10984 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
10985 let tags = chunk.type_tag.values();
10986 for (rev_idx, &t) in tags.iter().enumerate().rev() {
10987 let overlay_non_empty = chunk
10988 .overlay
10989 .get(rev_idx)
10990 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10991 .unwrap_or(false)
10992 || chunk
10993 .computed_overlay
10994 .get(rev_idx)
10995 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
10996 .unwrap_or(false);
10997 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
10998 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
10999 break;
11000 };
11001 let row0 = chunk_start + rev_idx;
11002 max_r0 = Some(row0 as u32);
11003 break;
11004 }
11005 }
11006 if max_r0.is_some() {
11007 break;
11008 }
11009 }
11010 }
11011
11012 (min_r0, max_r0)
11013 }
11014
11015 fn arrow_used_col_bounds(
11017 &self,
11018 sheet: &str,
11019 start_row: u32,
11020 end_row: u32,
11021 ) -> Option<(u32, u32)> {
11022 let a = self.sheet_store().sheet(sheet)?;
11023 if a.columns.is_empty() {
11024 return None;
11025 }
11026 let sr0 = start_row.saturating_sub(1) as usize;
11027 let er0 = end_row.saturating_sub(1) as usize;
11028 if sr0 > er0 {
11029 return None;
11030 }
11031 let mut min_c0: Option<usize> = None;
11034 let mut max_c0: Option<usize> = None;
11035 for (ci, col) in a.columns.iter().enumerate() {
11037 let mut any_in_range = false;
11038
11039 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
11040 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11041 return false;
11042 };
11043 let chunk_len = chunk.type_tag.len();
11044 if chunk_len == 0 {
11045 return false;
11046 }
11047 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
11048 if sr0 > chunk_end || er0 < chunk_start {
11050 return false;
11051 }
11052 let start_off = sr0.max(chunk_start) - chunk_start;
11053 let end_off = er0.min(chunk_end) - chunk_start;
11054 let tags = chunk.type_tag.values();
11055 for off in start_off..=end_off {
11056 let overlay_non_empty = chunk
11057 .overlay
11058 .get(off)
11059 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11060 .unwrap_or(false)
11061 || chunk
11062 .computed_overlay
11063 .get(off)
11064 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11065 .unwrap_or(false);
11066 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
11067 return true;
11068 }
11069 }
11070 false
11071 };
11072
11073 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
11074 if scan_chunk(chunk_idx, chunk) {
11075 any_in_range = true;
11076 break;
11077 }
11078 }
11079
11080 if !any_in_range && !col.sparse_chunks.is_empty() {
11081 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
11082 if scan_chunk(chunk_idx, chunk) {
11083 any_in_range = true;
11084 break;
11085 }
11086 }
11087 }
11088
11089 if any_in_range {
11090 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
11091 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
11092 }
11093 }
11094 match (min_c0, max_c0) {
11095 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
11096 _ => None,
11097 }
11098 }
11099
11100 fn formula_row_bounds_for_columns(
11101 &self,
11102 sheet: &str,
11103 start_col: u32,
11104 end_col: u32,
11105 ) -> Option<(u32, u32)> {
11106 let sheet_id = self.graph.sheet_id(sheet)?;
11107 let sc0 = start_col.saturating_sub(1);
11108 let ec0 = end_col.saturating_sub(1);
11109 let mut min_r0: Option<u32> = None;
11110 let mut max_r0: Option<u32> = None;
11111
11112 if self.graph.sheet_index(sheet_id).is_some() {
11113 for vid in self.graph.vertices_in_cols(sheet_id, sc0, ec0) {
11114 if !matches!(
11115 self.graph.get_vertex_kind(vid),
11116 VertexKind::FormulaScalar | VertexKind::FormulaArray
11117 ) {
11118 continue;
11119 }
11120 let Some(row0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.row()) else {
11121 continue;
11122 };
11123 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11124 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11125 }
11126 } else {
11127 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11128 if !matches!(
11129 self.graph.get_vertex_kind(vid),
11130 VertexKind::FormulaScalar | VertexKind::FormulaArray
11131 ) {
11132 continue;
11133 }
11134 let col0 = coord.col();
11135 if col0 < sc0 || col0 > ec0 {
11136 continue;
11137 }
11138 let row0 = coord.row();
11139 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11140 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11141 }
11142 }
11143
11144 match (min_r0, max_r0) {
11145 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11146 _ => None,
11147 }
11148 }
11149
11150 fn formula_col_bounds_for_rows(
11151 &self,
11152 sheet: &str,
11153 start_row: u32,
11154 end_row: u32,
11155 ) -> Option<(u32, u32)> {
11156 let sheet_id = self.graph.sheet_id(sheet)?;
11157 let sr0 = start_row.saturating_sub(1);
11158 let er0 = end_row.saturating_sub(1);
11159 let mut min_c0: Option<u32> = None;
11160 let mut max_c0: Option<u32> = None;
11161
11162 if self.graph.sheet_index(sheet_id).is_some() {
11163 for vid in self.graph.vertices_in_rows(sheet_id, sr0, er0) {
11164 if !matches!(
11165 self.graph.get_vertex_kind(vid),
11166 VertexKind::FormulaScalar | VertexKind::FormulaArray
11167 ) {
11168 continue;
11169 }
11170 let Some(col0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.col()) else {
11171 continue;
11172 };
11173 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11174 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11175 }
11176 } else {
11177 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11178 if !matches!(
11179 self.graph.get_vertex_kind(vid),
11180 VertexKind::FormulaScalar | VertexKind::FormulaArray
11181 ) {
11182 continue;
11183 }
11184 let row0 = coord.row();
11185 if row0 < sr0 || row0 > er0 {
11186 continue;
11187 }
11188 let col0 = coord.col();
11189 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11190 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11191 }
11192 }
11193
11194 match (min_c0, max_c0) {
11195 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11196 _ => None,
11197 }
11198 }
11199
11200 fn union_used_bounds(
11201 first: Option<(u32, u32)>,
11202 second: Option<(u32, u32)>,
11203 ) -> Option<(u32, u32)> {
11204 match (first, second) {
11205 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
11206 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
11207 (None, None) => None,
11208 }
11209 }
11210
11211 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
11214 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11215 return;
11216 }
11217 if self.arrow_sheets.sheet(sheet).is_none() {
11218 self.arrow_sheets
11219 .sheets
11220 .push(crate::arrow_store::ArrowSheet {
11221 name: std::sync::Arc::<str>::from(sheet),
11222 date_system: self.config.date_system,
11223 columns: Vec::new(),
11224 nrows: 0,
11225 chunk_starts: Vec::new(),
11226 chunk_rows: 32 * 1024,
11227 });
11228 }
11229
11230 let row0 = row.saturating_sub(1) as usize;
11231 let col0 = col.saturating_sub(1) as usize;
11232
11233 let asheet = self
11234 .arrow_sheets
11235 .sheet_mut(sheet)
11236 .expect("ArrowSheet must exist");
11237
11238 let cur_cols = asheet.columns.len();
11239 if col0 >= cur_cols {
11240 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11241 }
11242
11243 if row0 >= asheet.nrows as usize {
11244 if asheet.columns.is_empty() {
11245 asheet.insert_columns(0, 1);
11246 }
11247 asheet.ensure_row_capacity(row0 + 1);
11248 }
11249 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
11250 let ov =
11251 crate::arrow_store::OverlayValue::from_literal_value(value, asheet.date_system);
11252 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
11253 let _ = ch.overlay.set(in_off, ov);
11254 let format = match value {
11255 LiteralValue::Date(_) => Some(crate::format::FormatId::DATE),
11256 LiteralValue::DateTime(_) => Some(crate::format::FormatId::DATETIME),
11257 LiteralValue::Time(_) => Some(crate::format::FormatId::TIME),
11258 LiteralValue::Duration(_) => Some(crate::format::FormatId::DURATION),
11259 _ => None,
11260 };
11261 ch.overlay.set_format(in_off, format);
11262 ch.computed_overlay.remove(in_off)
11267 } else {
11268 return;
11269 };
11270 let abs_threshold = 1024usize;
11272 let frac_den = 50usize;
11273 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
11274 if freed > 0 {
11275 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
11276 }
11277 self.adjust_computed_overlay_bytes(computed_delta);
11278 }
11279 }
11280
11281 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
11286 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11287 return;
11288 }
11289 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11290 return;
11291 };
11292 let row0 = row.saturating_sub(1) as usize;
11293 let col0 = col.saturating_sub(1) as usize;
11294 if row0 >= asheet.nrows as usize {
11295 return;
11296 }
11297 if col0 >= asheet.columns.len() {
11298 return;
11299 }
11300 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11301 return;
11302 };
11303 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
11304 let _ = ch.overlay.remove(in_off);
11305 }
11306 }
11307
11308 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
11309 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11310 return;
11311 }
11312
11313 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11314 return;
11315 };
11316 if start_row0 >= asheet.nrows as usize {
11317 return;
11318 }
11319
11320 let starts = asheet.chunk_starts.clone();
11321 let nrows = asheet.nrows as usize;
11322 let mut delta = 0isize;
11323 for col in &mut asheet.columns {
11324 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
11325 let Some(&chunk_start) = starts.get(chunk_idx) else {
11326 continue;
11327 };
11328 let chunk_end = starts
11329 .get(chunk_idx + 1)
11330 .copied()
11331 .unwrap_or(nrows)
11332 .min(chunk_start.saturating_add(ch.len()));
11333 if chunk_end <= start_row0 {
11334 continue;
11335 }
11336 if chunk_start >= start_row0 {
11337 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11338 } else {
11339 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11340 delta = delta
11341 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11342 }
11343 }
11344
11345 for (chunk_idx, ch) in &mut col.sparse_chunks {
11346 let Some(&chunk_start) = starts.get(*chunk_idx) else {
11347 continue;
11348 };
11349 let chunk_end = starts
11350 .get(*chunk_idx + 1)
11351 .copied()
11352 .unwrap_or(nrows)
11353 .min(chunk_start.saturating_add(ch.len()));
11354 if chunk_end <= start_row0 {
11355 continue;
11356 }
11357 if chunk_start >= start_row0 {
11358 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11359 } else {
11360 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11361 delta = delta
11362 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11363 }
11364 }
11365 }
11366 self.adjust_computed_overlay_bytes(delta);
11367 }
11368
11369 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
11370 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11371 return;
11372 }
11373
11374 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11375 return;
11376 };
11377 if start_col0 >= asheet.columns.len() {
11378 return;
11379 }
11380
11381 let mut delta = 0isize;
11382 for col in asheet.columns.iter_mut().skip(start_col0) {
11383 for ch in &mut col.chunks {
11384 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11385 }
11386 for ch in col.sparse_chunks.values_mut() {
11387 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11388 }
11389 }
11390 self.adjust_computed_overlay_bytes(delta);
11391 }
11392
11393 #[inline]
11394 fn literal_to_overlay_value(
11395 value: &LiteralValue,
11396 date_system: crate::engine::DateSystem,
11397 ) -> crate::arrow_store::OverlayValue {
11398 crate::arrow_store::OverlayValue::from_literal_value(value, date_system)
11399 }
11400
11401 fn arrow_sheet_date_system(&self, sheet: &str) -> crate::engine::DateSystem {
11402 self.arrow_sheets
11403 .sheet(sheet)
11404 .map(|sheet| sheet.date_system)
11405 .unwrap_or(self.config.date_system)
11406 }
11407
11408 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11411 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11412 return None;
11413 }
11414 let asheet = self.arrow_sheets.sheet(sheet)?;
11415 let row0 = row.saturating_sub(1) as usize;
11416 let col0 = col.saturating_sub(1) as usize;
11417 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11418 return None;
11419 }
11420 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11421 let ch = asheet.columns[col0].chunk(ch_idx)?;
11422 ch.overlay
11423 .get_scalar(in_off)
11424 .map(|ov| ov.to_literal_for(asheet.date_system))
11425 }
11426
11427 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11430 if !(self.config.arrow_storage_enabled
11431 && self.config.delta_overlay_enabled
11432 && self.config.write_formula_overlay_enabled)
11433 {
11434 return None;
11435 }
11436 let asheet = self.arrow_sheets.sheet(sheet)?;
11437 let row0 = row.saturating_sub(1) as usize;
11438 let col0 = col.saturating_sub(1) as usize;
11439 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11440 return None;
11441 }
11442 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11443 let ch = asheet.columns[col0].chunk(ch_idx)?;
11444 ch.computed_overlay
11445 .get_scalar(in_off)
11446 .map(|ov| ov.to_literal_for(asheet.date_system))
11447 }
11448
11449 fn set_delta_overlay_cell_raw(
11450 &mut self,
11451 sheet: &str,
11452 row: u32,
11453 col: u32,
11454 value: Option<LiteralValue>,
11455 ) {
11456 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11457 return;
11458 }
11459
11460 self.ensure_arrow_sheet(sheet);
11461 let date_system = self.arrow_sheet_date_system(sheet);
11462 let ov_opt = value
11463 .as_ref()
11464 .map(|value| Self::literal_to_overlay_value(value, date_system));
11465 let row0 = row.saturating_sub(1) as usize;
11466 let col0 = col.saturating_sub(1) as usize;
11467 let asheet = self
11468 .arrow_sheets
11469 .sheet_mut(sheet)
11470 .expect("ArrowSheet must exist");
11471
11472 let cur_cols = asheet.columns.len();
11473 if col0 >= cur_cols {
11474 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11475 }
11476 if row0 >= asheet.nrows as usize {
11477 if asheet.columns.is_empty() {
11478 asheet.insert_columns(0, 1);
11479 }
11480 asheet.ensure_row_capacity(row0 + 1);
11481 }
11482
11483 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11484 return;
11485 };
11486 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11487 return;
11488 };
11489
11490 if let Some(ov) = ov_opt {
11491 let _ = ch.overlay.set(in_off, ov);
11492 } else {
11493 let _ = ch.overlay.remove(in_off);
11494 }
11495 }
11496
11497 fn set_computed_overlay_cell_raw(
11498 &mut self,
11499 sheet: &str,
11500 row: u32,
11501 col: u32,
11502 value: Option<LiteralValue>,
11503 ) {
11504 if !(self.config.arrow_storage_enabled
11505 && self.config.delta_overlay_enabled
11506 && self.config.write_formula_overlay_enabled)
11507 {
11508 return;
11509 }
11510
11511 self.ensure_arrow_sheet(sheet);
11512 let date_system = self.arrow_sheet_date_system(sheet);
11513 let ov_opt = value
11514 .as_ref()
11515 .map(|value| Self::literal_to_overlay_value(value, date_system));
11516 let row0 = row.saturating_sub(1) as usize;
11517 let col0 = col.saturating_sub(1) as usize;
11518 let asheet = self
11519 .arrow_sheets
11520 .sheet_mut(sheet)
11521 .expect("ArrowSheet must exist");
11522
11523 let cur_cols = asheet.columns.len();
11524 if col0 >= cur_cols {
11525 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11526 }
11527 if row0 >= asheet.nrows as usize {
11528 if asheet.columns.is_empty() {
11529 asheet.insert_columns(0, 1);
11530 }
11531 asheet.ensure_row_capacity(row0 + 1);
11532 }
11533
11534 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11535 return;
11536 };
11537 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11538 return;
11539 };
11540
11541 let delta = if let Some(ov) = ov_opt {
11542 ch.computed_overlay.set(in_off, ov)
11543 } else {
11544 ch.computed_overlay.remove(in_off)
11545 };
11546 self.adjust_computed_overlay_bytes(delta);
11547 }
11548
11549 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
11550 use crate::engine::ArrowOp;
11551
11552 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
11553 Box::new(batch.ops.iter().rev())
11554 } else {
11555 Box::new(batch.ops.iter())
11556 };
11557
11558 for op in iter {
11559 match op {
11560 ArrowOp::SetDeltaCell {
11561 sheet_id,
11562 row0,
11563 col0,
11564 old,
11565 new,
11566 } => {
11567 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11568 let v = if undo { old.clone() } else { new.clone() };
11569 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11570 }
11571 ArrowOp::SetComputedCell {
11572 sheet_id,
11573 row0,
11574 col0,
11575 old,
11576 new,
11577 } => {
11578 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11579 let v = if undo { old.clone() } else { new.clone() };
11580 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11581 }
11582 ArrowOp::RestoreComputedRect {
11583 sheet_id,
11584 sr0,
11585 sc0,
11586 er0,
11587 ec0,
11588 old,
11589 new,
11590 } => {
11591 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11592 let vals = if undo { old } else { new };
11593 let height = (*er0).saturating_sub(*sr0) as usize + 1;
11594 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
11595 for r in 0..height {
11596 for c in 0..width {
11597 let v = vals
11598 .get(r)
11599 .and_then(|row| row.get(c))
11600 .cloned()
11601 .unwrap_or(LiteralValue::Empty);
11602 self.set_computed_overlay_cell_raw(
11603 &sheet,
11604 *sr0 + 1 + r as u32,
11605 *sc0 + 1 + c as u32,
11606 Some(v),
11607 );
11608 }
11609 }
11610 }
11611 ArrowOp::InsertRows {
11612 sheet_id,
11613 before0,
11614 count,
11615 } => {
11616 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11617 self.ensure_arrow_sheet(&sheet);
11618 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11619 if undo {
11620 asheet.delete_rows(*before0 as usize, *count as usize);
11621 } else {
11622 asheet.insert_rows(*before0 as usize, *count as usize);
11623 }
11624 }
11625 self.purge_derived_formats_after_row(*sheet_id, *before0);
11626 }
11627 ArrowOp::InsertCols {
11628 sheet_id,
11629 before0,
11630 count,
11631 } => {
11632 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11633 self.ensure_arrow_sheet(&sheet);
11634 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11635 if undo {
11636 asheet.delete_columns(*before0 as usize, *count as usize);
11637 } else {
11638 asheet.insert_columns(*before0 as usize, *count as usize);
11639 }
11640 }
11641 self.purge_derived_formats_after_col(*sheet_id, *before0);
11642 }
11643 }
11644 }
11645 }
11646
11647 fn record_spill_ops_into_arrow_undo(
11648 &mut self,
11649 undo: &mut crate::engine::ArrowUndoBatch,
11650 events: &[crate::engine::ChangeEvent],
11651 ) {
11652 use crate::engine::ChangeEvent;
11653 use formualizer_common::LiteralValue;
11654
11655 #[allow(clippy::type_complexity)]
11656 let rect_from_snapshot =
11657 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
11658 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
11659 if snap.target_cells.is_empty() {
11660 return None;
11661 }
11662 let sheet_id = snap.target_cells[0].sheet_id;
11663 let sr0 = snap.target_cells[0].coord.row();
11664 let sc0 = snap.target_cells[0].coord.col();
11665 if snap.values.is_empty() || snap.values[0].is_empty() {
11666 return None;
11667 }
11668 let h = snap.values.len() as u32;
11669 let w = snap.values[0].len() as u32;
11670 let er0 = sr0.saturating_add(h.saturating_sub(1));
11671 let ec0 = sc0.saturating_add(w.saturating_sub(1));
11672 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
11673 };
11674
11675 for ev in events {
11676 match ev {
11677 ChangeEvent::SpillCommitted { old, new, .. } => {
11678 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
11679 let old_vals = if let Some(old_snap) = old {
11680 rect_from_snapshot(old_snap)
11681 .map(|(_, _, _, _, _, v)| v)
11682 .unwrap_or_else(|| {
11683 vec![
11684 vec![LiteralValue::Empty; new_vals[0].len()];
11685 new_vals.len()
11686 ]
11687 })
11688 } else {
11689 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
11690 };
11691 undo.record_restore_computed_rect(
11692 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11693 );
11694 }
11695 }
11696 ChangeEvent::SpillCleared { old, .. } => {
11697 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
11698 let new_vals =
11699 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
11700 undo.record_restore_computed_rect(
11701 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11702 );
11703 }
11704 }
11705 _ => {}
11706 }
11707 }
11708 }
11709
11710 fn mirror_value_to_computed_overlay(
11715 &mut self,
11716 sheet: &str,
11717 row: u32,
11718 col: u32,
11719 value: &LiteralValue,
11720 ) {
11721 if !(self.config.arrow_storage_enabled
11722 && self.config.delta_overlay_enabled
11723 && self.config.write_formula_overlay_enabled)
11724 {
11725 return;
11726 }
11727 if self.computed_overlay_mirroring_disabled {
11728 return;
11729 }
11730
11731 let date_system = self.arrow_sheet_date_system(sheet);
11732 let ov = Self::literal_to_overlay_value(value, date_system);
11733 self.write_computed_overlay_value_0based(
11734 sheet,
11735 row.saturating_sub(1),
11736 col.saturating_sub(1),
11737 ov,
11738 );
11739 }
11740
11741 fn record_derived_format(&self, vertex_id: VertexId, format: Option<crate::format::FormatId>) {
11742 if let Some(cell) = self.graph.get_cell_ref(vertex_id) {
11743 self.record_derived_format_at(cell, format);
11744 }
11745 }
11746
11747 fn record_derived_format_at(&self, cell: CellRef, format: Option<crate::format::FormatId>) {
11748 #[cfg(test)]
11749 self.derived_format_operations_for_test
11750 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
11751 let format = format.filter(|id| *id != crate::format::FormatId::GENERAL);
11752 self.derived_formats.set(cell, format);
11753 }
11754
11755 fn clear_cell_format_state(&mut self, sheet: &str, cell: CellRef) {
11756 self.derived_formats.set(cell, None);
11757 if let Some(arrow) = self.arrow_sheets.sheet_mut(sheet) {
11758 arrow.clear_format(cell.coord.row() as usize, cell.coord.col() as usize);
11759 }
11760 }
11761
11762 fn clear_logged_cell_format_states(&mut self, events: &[ChangeEvent]) {
11763 let cells = events
11764 .iter()
11765 .filter_map(|event| match event {
11766 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
11767 Some(*addr)
11768 }
11769 _ => None,
11770 })
11771 .collect::<FxHashSet<_>>();
11772 for cell in cells {
11773 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
11774 self.clear_cell_format_state(&sheet, cell);
11775 }
11776 }
11777
11778 fn purge_derived_formats_after_row(&mut self, sheet_id: SheetId, start0: u32) {
11779 self.derived_formats
11780 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.row() < start0);
11781 }
11782
11783 fn purge_derived_formats_after_col(&mut self, sheet_id: SheetId, start0: u32) {
11784 self.derived_formats
11785 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.col() < start0);
11786 }
11787
11788 fn purge_derived_formats_for_sheet(&mut self, sheet_id: SheetId) {
11789 self.derived_formats
11790 .retain(|cell| cell.sheet_id != sheet_id);
11791 }
11792
11793 #[cfg(test)]
11794 pub(crate) fn debug_computed_overlay_format_0based(
11795 &self,
11796 sheet: &str,
11797 row0: u32,
11798 col0: u32,
11799 ) -> Option<crate::format::FormatId> {
11800 let sheet = self.arrow_sheets.sheet(sheet)?;
11801 let (chunk_idx, row_in_chunk) = sheet.chunk_of_row(row0 as usize)?;
11802 sheet
11803 .columns
11804 .get(col0 as usize)?
11805 .chunk(chunk_idx)?
11806 .computed_overlay
11807 .get_format(row_in_chunk)
11808 }
11809
11810 #[cfg(test)]
11811 pub(crate) fn debug_computed_overlay_chunk_has_formats_0based(
11812 &self,
11813 sheet: &str,
11814 row0: u32,
11815 col0: u32,
11816 ) -> bool {
11817 let Some(sheet) = self.arrow_sheets.sheet(sheet) else {
11818 return false;
11819 };
11820 let Some((chunk_idx, _)) = sheet.chunk_of_row(row0 as usize) else {
11821 return false;
11822 };
11823 sheet
11824 .columns
11825 .get(col0 as usize)
11826 .and_then(|column| column.chunk(chunk_idx))
11827 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
11828 }
11829
11830 #[cfg(test)]
11831 pub(crate) fn debug_clear_derived_format_0based(&mut self, sheet: &str, row0: u32, col0: u32) {
11832 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
11833 self.derived_formats
11834 .set(CellRef::new_absolute(sheet_id, row0, col0), None);
11835 }
11836 }
11837
11838 #[cfg(test)]
11839 pub(crate) fn debug_record_derived_format_0based(
11840 &self,
11841 sheet: &str,
11842 row0: u32,
11843 col0: u32,
11844 format: Option<crate::format::FormatId>,
11845 ) {
11846 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
11847 self.record_derived_format_at(CellRef::new_absolute(sheet_id, row0, col0), format);
11848 }
11849 }
11850
11851 #[cfg(test)]
11852 pub(crate) fn debug_derived_format_0based(
11853 &self,
11854 sheet: &str,
11855 row0: u32,
11856 col0: u32,
11857 ) -> Option<crate::format::FormatId> {
11858 let sheet_id = self.graph.sheet_id(sheet)?;
11859 self.derived_formats
11860 .get(&CellRef::new_absolute(sheet_id, row0, col0))
11861 }
11862
11863 #[cfg(test)]
11864 pub(crate) fn debug_reset_format_write_operation_counts(&mut self) {
11865 self.derived_format_operations_for_test
11866 .store(0, std::sync::atomic::Ordering::Relaxed);
11867 self.computed_overlay_set_explicit_entry_operations_for_test = 0;
11868 self.computed_overlay_stale_clear_range_effects_for_test = 0;
11869 self.computed_overlay_stale_clear_offset_attempts_for_test = 0;
11870 self.computed_format_vector_allocations_for_test
11871 .store(0, std::sync::atomic::Ordering::Relaxed);
11872 }
11873
11874 #[cfg(test)]
11875 pub(crate) fn debug_format_write_operation_counts(&self) -> (u64, u64, u64, u64, u64) {
11876 (
11877 self.derived_format_operations_for_test
11878 .load(std::sync::atomic::Ordering::Relaxed),
11879 self.computed_overlay_set_explicit_entry_operations_for_test,
11880 self.computed_format_vector_allocations_for_test
11881 .load(std::sync::atomic::Ordering::Relaxed),
11882 self.computed_overlay_stale_clear_range_effects_for_test,
11883 self.computed_overlay_stale_clear_offset_attempts_for_test,
11884 )
11885 }
11886
11887 fn write_computed_overlay_format_0based(
11888 &mut self,
11889 sheet: &str,
11890 row0: u32,
11891 col0: u32,
11892 format: Option<crate::format::FormatId>,
11893 ) {
11894 self.ensure_arrow_sheet(sheet);
11895 let (row0, col0) = (row0 as usize, col0 as usize);
11896 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11897 return;
11898 };
11899 if col0 >= asheet.columns.len() {
11900 asheet.insert_columns(asheet.columns.len(), col0 + 1 - asheet.columns.len());
11901 }
11902 if row0 >= asheet.nrows as usize {
11903 asheet.ensure_row_capacity(row0 + 1);
11904 }
11905 let Some((chunk, offset)) = asheet.chunk_of_row(row0) else {
11906 return;
11907 };
11908 if let Some(chunk) = asheet.ensure_column_chunk_mut(col0, chunk) {
11909 chunk.computed_overlay.set_format(offset, format);
11910 }
11911 }
11912
11913 fn write_computed_cell_0based(
11918 &mut self,
11919 cell: CellRef,
11920 value: &LiteralValue,
11921 format: Option<crate::format::FormatId>,
11922 ) {
11923 if !(self.config.arrow_storage_enabled
11924 && self.config.delta_overlay_enabled
11925 && self.config.write_formula_overlay_enabled)
11926 || self.computed_overlay_mirroring_disabled
11927 {
11928 return;
11929 }
11930 let sheet = self.graph.sheet_name(cell.sheet_id);
11931 let index = match self
11932 .arrow_sheets
11933 .sheets
11934 .iter()
11935 .position(|s| s.name.as_ref() == sheet)
11936 {
11937 Some(index) => index,
11938 None => {
11939 let sheet = sheet.to_string();
11940 self.ensure_arrow_sheet(&sheet);
11941 self.arrow_sheets.sheets.len() - 1
11942 }
11943 };
11944 let (row0, col0) = (cell.coord.row() as usize, cell.coord.col() as usize);
11945 let asheet = &mut self.arrow_sheets.sheets[index];
11946 let ov = Self::literal_to_overlay_value(value, asheet.date_system);
11947 let cur_cols = asheet.columns.len();
11948 if col0 >= cur_cols {
11949 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11950 }
11951 if row0 >= asheet.nrows as usize {
11952 asheet.ensure_row_capacity(row0 + 1);
11953 }
11954 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11955 return;
11956 };
11957 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11958 return;
11959 };
11960 let delta = ch.computed_overlay.set_scalar(in_off, ov);
11961 ch.computed_overlay.set_format(in_off, format);
11962 self.adjust_computed_overlay_bytes(delta);
11963 if let Some(cap) = self.config.max_overlay_memory_bytes
11964 && self.computed_overlay_bytes_estimate > cap
11965 {
11966 self.disable_computed_overlay_mirroring_due_to_budget(cap);
11967 }
11968 }
11969
11970 fn write_computed_overlay_value_0based(
11971 &mut self,
11972 sheet: &str,
11973 row0: u32,
11974 col0: u32,
11975 value: OverlayValue,
11976 ) {
11977 if !(self.config.arrow_storage_enabled
11978 && self.config.delta_overlay_enabled
11979 && self.config.write_formula_overlay_enabled)
11980 {
11981 return;
11982 }
11983 if self.computed_overlay_mirroring_disabled {
11984 return;
11985 }
11986
11987 self.ensure_arrow_sheet(sheet);
11988
11989 let row0 = row0 as usize;
11990 let col0 = col0 as usize;
11991 let asheet = self
11992 .arrow_sheets
11993 .sheet_mut(sheet)
11994 .expect("ArrowSheet must exist");
11995
11996 let cur_cols = asheet.columns.len();
11997 if col0 >= cur_cols {
11998 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11999 }
12000
12001 if row0 >= asheet.nrows as usize {
12002 if asheet.columns.is_empty() {
12003 asheet.insert_columns(0, 1);
12004 }
12005 asheet.ensure_row_capacity(row0 + 1);
12006 }
12007
12008 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
12009 return;
12010 };
12011 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
12012 return;
12013 };
12014
12015 let delta = ch.computed_overlay.set_scalar(in_off, value);
12016 self.adjust_computed_overlay_bytes(delta);
12017
12018 if let Some(cap) = self.config.max_overlay_memory_bytes
12019 && self.computed_overlay_bytes_estimate > cap
12020 {
12021 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12022 }
12023 }
12024
12025 pub(crate) fn plan_computed_write_coalescing(
12026 &self,
12027 buffer: &ComputedWriteBuffer,
12028 ) -> ComputedWriteCoalescingPlan {
12029 self.plan_computed_write_coalescing_from_writes(
12030 buffer.writes().iter().cloned(),
12031 buffer.formats_present,
12032 )
12033 }
12034
12035 fn plan_owned_computed_write_coalescing(
12036 &self,
12037 writes: Vec<ComputedWrite>,
12038 formats_present: bool,
12039 ) -> ComputedWriteCoalescingPlan {
12040 self.plan_computed_write_coalescing_from_writes(writes, formats_present)
12041 }
12042
12043 fn plan_computed_write_coalescing_from_writes(
12044 &self,
12045 writes: impl IntoIterator<Item = ComputedWrite>,
12046 formats_present: bool,
12047 ) -> ComputedWriteCoalescingPlan {
12048 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
12049 BTreeMap::new();
12050 let mut input_cells = 0usize;
12051 let mut located: Option<(SheetId, Option<&crate::arrow_store::ArrowSheet>)> = None;
12053
12054 for write in writes {
12055 match write {
12056 ComputedWrite::Cell {
12057 seq,
12058 sheet_id,
12059 row0,
12060 col0,
12061 value,
12062 format_id,
12063 } => {
12064 input_cells = input_cells.saturating_add(1);
12065 let sheet = match located {
12066 Some((id, sheet)) if id == sheet_id => sheet,
12067 _ => {
12068 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12069 located = Some((sheet_id, sheet));
12070 sheet
12071 }
12072 };
12073 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12074 Some(sheet) => {
12075 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize)
12076 }
12077 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12078 row0 as usize,
12079 32 * 1024,
12080 ),
12081 };
12082 groups
12083 .entry(ComputedWriteChunkKey {
12084 sheet_id,
12085 col0,
12086 chunk_idx,
12087 chunk_start_row0,
12088 })
12089 .or_default()
12090 .push(ComputedWriteChunkEntryPlan {
12091 row_in_chunk,
12092 seq,
12093 value,
12094 format_id,
12095 });
12096 }
12097 ComputedWrite::Run {
12098 seq,
12099 sheet_id,
12100 row0,
12101 col0,
12102 entries,
12103 } => {
12104 input_cells = input_cells.saturating_add(entries.len());
12105 let sheet = match located {
12106 Some((id, sheet)) if id == sheet_id => sheet,
12107 _ => {
12108 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12109 located = Some((sheet_id, sheet));
12110 sheet
12111 }
12112 };
12113 let mut segment: Vec<ComputedWriteChunkEntryPlan> = Vec::new();
12117 let mut segment_key: Option<ComputedWriteChunkKey> = None;
12118 for (k, (value, format_id)) in entries.into_iter().enumerate() {
12119 let row = row0.saturating_add(k as u32) as usize;
12120 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12121 Some(sheet) => {
12122 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row)
12123 }
12124 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12125 row,
12126 32 * 1024,
12127 ),
12128 };
12129 let key = ComputedWriteChunkKey {
12130 sheet_id,
12131 col0,
12132 chunk_idx,
12133 chunk_start_row0,
12134 };
12135 if segment_key != Some(key)
12136 && let Some(done) = segment_key.replace(key)
12137 {
12138 groups.entry(done).or_default().append(&mut segment);
12139 }
12140 segment.push(ComputedWriteChunkEntryPlan {
12141 row_in_chunk,
12142 seq,
12143 value,
12144 format_id,
12145 });
12146 }
12147 if let Some(done) = segment_key {
12148 groups.entry(done).or_default().append(&mut segment);
12149 }
12150 }
12151 ComputedWrite::Rect {
12152 seq,
12153 sheet_id,
12154 sr0,
12155 sc0,
12156 values,
12157 } => {
12158 for (r_off, row) in values.into_iter().enumerate() {
12159 for (c_off, value) in row.into_iter().enumerate() {
12160 input_cells = input_cells.saturating_add(1);
12161 self.push_computed_write_plan_entry(
12162 &mut groups,
12163 seq,
12164 sheet_id,
12165 sr0.saturating_add(r_off as u32),
12166 sc0.saturating_add(c_off as u32),
12167 value,
12168 None,
12169 );
12170 }
12171 }
12172 }
12173 }
12174 }
12175
12176 let mut plan = ComputedWriteCoalescingPlan {
12177 chunks: Vec::with_capacity(groups.len()),
12178 input_cells,
12179 coalesced_cells: 0,
12180 overwritten_cells: 0,
12181 };
12182 for (key, entries) in groups {
12183 let computed_lane_has_formats =
12184 self.computed_overlay_chunk_has_formats(key.sheet_id, key.col0, key.chunk_idx);
12185 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(
12186 key,
12187 entries,
12188 formats_present,
12189 computed_lane_has_formats,
12190 );
12191 #[cfg(test)]
12192 if matches!(
12193 &chunk_plan.format_effect,
12194 ComputedWriteChunkFormatEffect::SetExplicit(_)
12195 ) {
12196 self.computed_format_vector_allocations_for_test
12197 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
12198 }
12199 plan.coalesced_cells = plan
12200 .coalesced_cells
12201 .saturating_add(chunk_plan.entries.len());
12202 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
12203 plan.chunks.push(chunk_plan);
12204 }
12205 debug_assert_eq!(
12206 plan.input_cells,
12207 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
12208 );
12209 plan
12210 }
12211
12212 fn push_computed_write_plan_entry(
12213 &self,
12214 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
12215 seq: u64,
12216 sheet_id: SheetId,
12217 row0: u32,
12218 col0: u32,
12219 value: OverlayValue,
12220 format_id: Option<crate::format::FormatId>,
12221 ) {
12222 let (chunk_idx, chunk_start_row0, row_in_chunk) =
12223 self.locate_computed_write_chunk(sheet_id, row0);
12224 let key = ComputedWriteChunkKey {
12225 sheet_id,
12226 col0,
12227 chunk_idx,
12228 chunk_start_row0,
12229 };
12230 groups
12231 .entry(key)
12232 .or_default()
12233 .push(ComputedWriteChunkEntryPlan {
12234 row_in_chunk,
12235 seq,
12236 value,
12237 format_id,
12238 });
12239 }
12240
12241 fn computed_overlay_chunk_has_formats(
12242 &self,
12243 sheet_id: SheetId,
12244 col0: u32,
12245 chunk_idx: usize,
12246 ) -> bool {
12247 let sheet_name = self.graph.sheet_name(sheet_id);
12248 self.arrow_sheets
12249 .sheet(sheet_name)
12250 .and_then(|sheet| sheet.columns.get(col0 as usize))
12251 .and_then(|column| column.chunk(chunk_idx))
12252 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
12253 }
12254
12255 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
12256 let sheet_name = self.graph.sheet_name(sheet_id);
12257 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
12258 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
12259 }
12260 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
12261 }
12262
12263 fn locate_row_in_sheet_for_computed_write_plan(
12264 sheet: &crate::arrow_store::ArrowSheet,
12265 row0: usize,
12266 ) -> (usize, u32, usize) {
12267 if row0 < sheet.nrows as usize
12268 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
12269 {
12270 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
12271 return (chunk_idx, chunk_start as u32, row_in_chunk);
12272 }
12273
12274 let chunk_rows = sheet.chunk_rows.max(1);
12275 if sheet.chunk_starts.is_empty() {
12276 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
12277 }
12278
12279 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
12280 let mut chunk_start = sheet.chunk_starts[chunk_idx];
12281 while chunk_start.saturating_add(chunk_rows) <= row0 {
12282 chunk_idx = chunk_idx.saturating_add(1);
12283 chunk_start = chunk_start.saturating_add(chunk_rows);
12284 }
12285 (
12286 chunk_idx,
12287 chunk_start as u32,
12288 row0.saturating_sub(chunk_start),
12289 )
12290 }
12291
12292 fn locate_row_in_empty_sheet_for_computed_write_plan(
12293 row0: usize,
12294 chunk_rows: usize,
12295 ) -> (usize, u32, usize) {
12296 let chunk_rows = chunk_rows.max(1);
12297 let chunk_idx = row0 / chunk_rows;
12298 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
12299 (
12300 chunk_idx,
12301 chunk_start as u32,
12302 row0.saturating_sub(chunk_start),
12303 )
12304 }
12305
12306 #[cfg(test)]
12307 pub(crate) fn debug_plan_computed_write_coalescing(
12308 &self,
12309 buffer: &ComputedWriteBuffer,
12310 ) -> ComputedWriteCoalescingPlan {
12311 self.plan_computed_write_coalescing(buffer)
12312 }
12313
12314 pub(crate) fn flush_computed_write_buffer(
12315 &mut self,
12316 buffer: &mut ComputedWriteBuffer,
12317 ) -> Result<(), ExcelError> {
12318 if buffer.is_empty() {
12319 return Ok(());
12320 }
12321
12322 self.resource_checkpoint(0)?;
12327 let commit_started = self.preflight_evaluation_commit_window(buffer.len())?;
12328 let (writes, formats_present) = buffer.take_writes();
12329 let plan = self.plan_owned_computed_write_coalescing(writes, formats_present);
12330 self.flush_computed_write_plan(plan);
12331 self.observe_evaluation_commit_window(commit_started);
12332
12333 Ok(())
12334 }
12335
12336 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
12337 for chunk in plan.chunks {
12338 self.flush_computed_write_chunk_plan(chunk);
12339 }
12340 }
12341
12342 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
12343 match &chunk.shape {
12344 ComputedWriteChunkPlanShape::Point => {
12345 self.flush_computed_write_chunk_plan_as_points(chunk);
12346 }
12347 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
12348 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
12349 }
12350 ComputedWriteChunkPlanShape::DenseRange { .. } => {
12351 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12352 }
12353 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
12354 if Self::should_emit_computed_run_fragment(*len, *runs) {
12355 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
12356 } else {
12357 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12358 }
12359 }
12360 }
12361 }
12362
12363 #[inline]
12364 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
12365 runs <= len / 2
12366 }
12367
12368 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
12369 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
12370 for entry in chunk.entries {
12371 let row0 = chunk
12372 .chunk_start_row0
12373 .saturating_add(entry.row_in_chunk as u32);
12374 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
12375 }
12376 self.apply_computed_overlay_format_effect(
12377 chunk.sheet_id,
12378 chunk.col0,
12379 chunk.chunk_idx,
12380 chunk.format_effect,
12381 );
12382 }
12383
12384 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
12385 &mut self,
12386 chunk: ComputedWriteChunkPlan,
12387 ) {
12388 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
12389 let sheet_id = chunk.sheet_id;
12390 let col0 = chunk.col0;
12391 let chunk_idx = chunk.chunk_idx;
12392 let chunk_start_row0 = chunk.chunk_start_row0;
12393 let format_effect = chunk.format_effect;
12394 let items: Vec<(usize, OverlayValue)> = chunk
12395 .entries
12396 .into_iter()
12397 .map(|entry| (entry.row_in_chunk, entry.value))
12398 .collect();
12399 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
12400 items,
12401 point_estimate,
12402 ) {
12403 Some(Ok(fragment)) => {
12404 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
12405 }
12406 Some(Err(cells)) => {
12407 self.flush_computed_overlay_cells_as_points(
12408 sheet_id,
12409 col0,
12410 chunk_start_row0,
12411 cells,
12412 );
12413 }
12414 None => {}
12415 }
12416 self.apply_computed_overlay_format_effect(sheet_id, col0, chunk_idx, format_effect);
12417 }
12418
12419 #[inline]
12420 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
12421 chunk
12422 .entries
12423 .iter()
12424 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
12425 .fold(0usize, usize::saturating_add)
12426 }
12427
12428 fn flush_computed_overlay_cells_as_points(
12429 &mut self,
12430 sheet_id: SheetId,
12431 col0: u32,
12432 chunk_start_row0: u32,
12433 cells: Vec<(usize, OverlayValue)>,
12434 ) {
12435 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12436 for (row_in_chunk, value) in cells {
12437 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
12438 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
12439 }
12440 }
12441
12442 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12443 if chunk.entries.is_empty() {
12444 return;
12445 }
12446 let start = chunk.entries[0].row_in_chunk;
12447 let values: Vec<OverlayValue> =
12448 chunk.entries.into_iter().map(|entry| entry.value).collect();
12449 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
12450 self.apply_computed_overlay_fragment(
12451 chunk.sheet_id,
12452 chunk.col0,
12453 chunk.chunk_idx,
12454 fragment,
12455 );
12456 }
12457 self.apply_computed_overlay_format_effect(
12458 chunk.sheet_id,
12459 chunk.col0,
12460 chunk.chunk_idx,
12461 chunk.format_effect,
12462 );
12463 }
12464
12465 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12466 if chunk.entries.is_empty() {
12467 return;
12468 }
12469 let start = chunk.entries[0].row_in_chunk;
12470 let values: Vec<OverlayValue> =
12471 chunk.entries.into_iter().map(|entry| entry.value).collect();
12472 if let Some(fragment) = OverlayFragment::run_range(start, values) {
12473 self.apply_computed_overlay_fragment(
12474 chunk.sheet_id,
12475 chunk.col0,
12476 chunk.chunk_idx,
12477 fragment,
12478 );
12479 }
12480 self.apply_computed_overlay_format_effect(
12481 chunk.sheet_id,
12482 chunk.col0,
12483 chunk.chunk_idx,
12484 chunk.format_effect,
12485 );
12486 }
12487
12488 fn apply_computed_overlay_format_effect(
12489 &mut self,
12490 sheet_id: SheetId,
12491 col0: u32,
12492 chunk_idx: usize,
12493 effect: ComputedWriteChunkFormatEffect,
12494 ) {
12495 if !(self.config.arrow_storage_enabled
12496 && self.config.delta_overlay_enabled
12497 && self.config.write_formula_overlay_enabled)
12498 || self.computed_overlay_mirroring_disabled
12499 {
12500 return;
12501 }
12502
12503 let sheet_name = self.graph.sheet_name(sheet_id);
12504 let Some(sheet) = self.arrow_sheets.sheet_mut(sheet_name) else {
12505 return;
12506 };
12507 let Some(chunk) = sheet
12508 .columns
12509 .get_mut(col0 as usize)
12510 .and_then(|column| column.chunk_mut(chunk_idx))
12511 else {
12512 return;
12513 };
12514 match effect {
12515 ComputedWriteChunkFormatEffect::NoFormatWork => {}
12516 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Range {
12517 start,
12518 end,
12519 }) => {
12520 #[cfg(test)]
12521 {
12522 self.computed_overlay_stale_clear_range_effects_for_test = self
12523 .computed_overlay_stale_clear_range_effects_for_test
12524 .saturating_add(1);
12525 }
12526 chunk.computed_overlay.clear_format_range(start, end);
12527 }
12528 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Offsets(
12529 offsets,
12530 )) => {
12531 #[cfg(test)]
12532 {
12533 self.computed_overlay_stale_clear_offset_attempts_for_test = self
12534 .computed_overlay_stale_clear_offset_attempts_for_test
12535 .saturating_add(offsets.len() as u64);
12536 }
12537 chunk.computed_overlay.clear_format_offsets(&offsets);
12538 }
12539 ComputedWriteChunkFormatEffect::SetExplicit(formats) => {
12540 #[cfg(test)]
12541 {
12542 self.computed_overlay_set_explicit_entry_operations_for_test = self
12543 .computed_overlay_set_explicit_entry_operations_for_test
12544 .saturating_add(formats.len() as u64);
12545 }
12546 for (row_in_chunk, format_id) in formats {
12547 chunk.computed_overlay.set_format(row_in_chunk, format_id);
12548 }
12549 }
12550 }
12551 }
12552
12553 fn apply_computed_overlay_fragment(
12554 &mut self,
12555 sheet_id: SheetId,
12556 col0: u32,
12557 chunk_idx: usize,
12558 fragment: OverlayFragment,
12559 ) {
12560 if !(self.config.arrow_storage_enabled
12561 && self.config.delta_overlay_enabled
12562 && self.config.write_formula_overlay_enabled)
12563 {
12564 return;
12565 }
12566 if self.computed_overlay_mirroring_disabled {
12567 return;
12568 }
12569
12570 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12571 self.ensure_arrow_sheet(&sheet_name);
12572
12573 let col0 = col0 as usize;
12574 let asheet = self
12575 .arrow_sheets
12576 .sheet_mut(&sheet_name)
12577 .expect("ArrowSheet must exist");
12578
12579 let cur_cols = asheet.columns.len();
12580 if col0 >= cur_cols {
12581 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
12582 }
12583
12584 let start_row0 = asheet
12585 .chunk_starts
12586 .get(chunk_idx)
12587 .copied()
12588 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
12589 let required_rows =
12590 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
12591 if required_rows > asheet.nrows as usize {
12592 if asheet.columns.is_empty() {
12593 asheet.insert_columns(0, 1);
12594 }
12595 asheet.ensure_row_capacity(required_rows);
12596 }
12597
12598 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
12599 return;
12600 };
12601 let delta = ch.computed_overlay.apply_fragment(fragment);
12602 self.adjust_computed_overlay_bytes(delta);
12603
12604 if let Some(cap) = self.config.max_overlay_memory_bytes
12605 && self.computed_overlay_bytes_estimate > cap
12606 {
12607 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12608 }
12609 }
12610
12611 #[inline]
12612 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
12613 if delta >= 0 {
12614 self.computed_overlay_bytes_estimate = self
12615 .computed_overlay_bytes_estimate
12616 .saturating_add(delta as usize);
12617 } else {
12618 self.computed_overlay_bytes_estimate = self
12619 .computed_overlay_bytes_estimate
12620 .saturating_sub((-delta) as usize);
12621 }
12622 }
12623
12624 fn clear_all_computed_overlays(&mut self) {
12625 let mut freed_total = 0usize;
12626 for sh in self.arrow_sheets.sheets.iter_mut() {
12627 for col in sh.columns.iter_mut() {
12628 for ch in col.chunks.iter_mut() {
12629 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12630 }
12631 for ch in col.sparse_chunks.values_mut() {
12632 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12633 }
12634 }
12635 }
12636 self.computed_overlay_bytes_estimate = self
12637 .computed_overlay_bytes_estimate
12638 .saturating_sub(freed_total);
12639 }
12640
12641 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
12642 self.compact_all_computed_overlays();
12645 }
12646
12647 fn compact_all_computed_overlays(&mut self) {
12650 let mut freed_total = 0usize;
12651 for sheet in self.arrow_sheets.sheets.iter_mut() {
12652 for col_idx in 0..sheet.columns.len() {
12653 let num_dense = sheet.columns[col_idx].chunks.len();
12655 for ch_idx in 0..num_dense {
12656 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
12657 }
12658 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
12660 .sparse_chunks
12661 .keys()
12662 .copied()
12663 .collect();
12664 for ch_idx in sparse_keys {
12665 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
12666 }
12667 }
12668 }
12669 self.computed_overlay_bytes_estimate = self
12670 .computed_overlay_bytes_estimate
12671 .saturating_sub(freed_total);
12672 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
12673 }
12674
12675 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
12676 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
12677 }
12678
12679 fn record_vertex_value_to_overlay(
12680 &mut self,
12681 vertex_id: VertexId,
12682 value: &LiteralValue,
12683 computed_writes: Option<&mut ComputedWriteBuffer>,
12684 ) -> Result<(), ExcelError> {
12685 if !(self.config.arrow_storage_enabled
12686 && self.config.delta_overlay_enabled
12687 && self.config.write_formula_overlay_enabled)
12688 {
12689 return Ok(());
12690 }
12691 if self.computed_overlay_mirroring_disabled {
12692 return Ok(());
12693 }
12694 if !matches!(
12695 self.graph.get_vertex_kind(vertex_id),
12696 VertexKind::FormulaScalar | VertexKind::FormulaArray
12697 ) {
12698 return Ok(());
12699 }
12700 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
12701 return Ok(());
12702 };
12703 let Some(buffer) = computed_writes else {
12704 let format_id = self.derived_formats.get(&cell);
12706 self.write_computed_cell_0based(cell, value, format_id);
12707 return Ok(());
12708 };
12709 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12710 let date_system = self.arrow_sheet_date_system(&sheet_name);
12711 let ov = Self::literal_to_overlay_value(value, date_system);
12712 {
12713 let format_id = self.derived_formats.get(&cell);
12714 buffer.push_cell_with_format(
12715 cell.sheet_id,
12716 cell.coord.row(),
12717 cell.coord.col(),
12718 ov,
12719 format_id,
12720 );
12721 if self.should_flush_computed_write_buffer(buffer) {
12722 self.flush_computed_write_buffer(buffer)?;
12723 }
12724 }
12725 Ok(())
12726 }
12727
12728 #[inline]
12729 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
12730 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
12731 if cap == 0 {
12732 return false;
12733 }
12734 self.computed_overlay_bytes_estimate
12735 .saturating_add(buffer.estimated_bytes())
12736 > cap
12737 })
12738 }
12739
12740 pub fn overlay_memory_usage(&self) -> usize {
12742 self.computed_overlay_bytes_estimate
12743 }
12744
12745 #[cfg(test)]
12746 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
12747 self.overlay_compactions
12748 }
12749
12750 #[cfg(test)]
12751 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
12752 let mut total = 0usize;
12753 for sheet in &self.arrow_sheets.sheets {
12754 for column in &sheet.columns {
12755 for chunk in &column.chunks {
12756 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
12757 }
12758 for chunk in column.sparse_chunks.values() {
12759 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
12760 }
12761 }
12762 }
12763 self.computed_overlay_bytes_estimate = total;
12764 total
12765 }
12766
12767 fn resolve_sheet_locator_for_write(
12768 &mut self,
12769 loc: formualizer_common::SheetLocator<'_>,
12770 current_sheet: &str,
12771 ) -> Result<SheetId, ExcelError> {
12772 Ok(match loc {
12773 formualizer_common::SheetLocator::Id(id) => id,
12774 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
12775 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
12776 })
12777 }
12778
12779 fn resolve_sheet_locator_for_read(
12780 &self,
12781 loc: formualizer_common::SheetLocator<'_>,
12782 current_sheet: &str,
12783 ) -> Result<SheetId, ExcelError> {
12784 match loc {
12785 formualizer_common::SheetLocator::Id(id) => Ok(id),
12786 formualizer_common::SheetLocator::Name(name) => self
12787 .graph
12788 .sheet_id(name.as_ref())
12789 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
12790 formualizer_common::SheetLocator::Current => self
12791 .graph
12792 .sheet_id(current_sheet)
12793 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
12794 }
12795 }
12796
12797 pub fn set_cell_value(
12799 &mut self,
12800 sheet: &str,
12801 row: u32,
12802 col: u32,
12803 value: LiteralValue,
12804 ) -> Result<(), ExcelError> {
12805 self.observe_function_semantic_epoch()?;
12806 let sheet_existed = self.graph.sheet_id(sheet).is_some();
12807 let sheet_id = self.graph.sheet_id_mut(sheet);
12808 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
12809 let replaced_formula =
12810 self.graph
12811 .get_vertex_id_for_address(&cell_ref)
12812 .is_some_and(|vertex| {
12813 matches!(
12814 self.graph.get_vertex_kind(vertex),
12815 VertexKind::FormulaScalar | VertexKind::FormulaArray
12816 )
12817 });
12818 self.graph.set_cell_value(sheet, row, col, value.clone())?;
12819 self.clear_cell_format_state(sheet, cell_ref);
12820 self.record_changed_cell(sheet, row, col);
12821 if !sheet_existed || replaced_formula {
12822 self.mark_topology_edited();
12823 }
12824 self.mirror_value_to_overlay(sheet, row, col, &value);
12826 self.mark_data_edited();
12828 Ok(())
12829 }
12830
12831 fn record_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
12833 let sheet_id = self.graph.sheet_id_mut(sheet);
12834 self.record_structural_change(StructuralScope::Cell {
12835 sheet: sheet_id,
12836 row: row.saturating_sub(1),
12837 col: col.saturating_sub(1),
12838 });
12839 }
12840
12841 fn record_change_for_event(&mut self, event: &ChangeEvent) {
12842 match event {
12843 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
12844 self.record_structural_change(StructuralScope::Cell {
12845 sheet: addr.sheet_id,
12846 row: addr.coord.row(),
12847 col: addr.coord.col(),
12848 });
12849 }
12850 ChangeEvent::SpillCommitted { new, .. } => {
12851 if let Some(scope) = Self::structural_scope_from_cells(&new.target_cells) {
12852 self.record_structural_change(scope);
12853 }
12854 }
12855 ChangeEvent::SpillCleared { old, .. } => {
12856 if let Some(scope) = Self::structural_scope_from_cells(&old.target_cells) {
12857 self.record_structural_change(scope);
12858 }
12859 }
12860 ChangeEvent::DefineName { .. }
12861 | ChangeEvent::UpdateName { .. }
12862 | ChangeEvent::DeleteName { .. }
12863 | ChangeEvent::NamedRangeAdjusted { .. } => {
12864 self.record_structural_change(StructuralScope::AllSheets);
12868 }
12869 ChangeEvent::VertexMoved { .. } | ChangeEvent::FormulaAdjusted { .. } => {
12870 }
12873 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
12874 self.record_structural_change(StructuralScope::Region(Region::whole_row(
12875 *sheet_id, *row0,
12876 )));
12877 }
12878 ChangeEvent::AddVertex { .. }
12879 | ChangeEvent::RemoveVertex { .. }
12880 | ChangeEvent::EdgeAdded { .. }
12881 | ChangeEvent::EdgeRemoved { .. }
12882 | ChangeEvent::CompoundStart { .. }
12883 | ChangeEvent::CompoundEnd { .. }
12884 | ChangeEvent::StagedFormulaCellChanged { .. } => {}
12885 }
12886 }
12887
12888 fn record_structural_change(&mut self, scope: StructuralScope) {
12889 self.invalidate_pending_spills(scope);
12890 }
12891
12892 fn structural_scope_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
12893 let first = cells.first()?;
12894 let sheet_id = first.sheet_id;
12895 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
12896 return Some(StructuralScope::OpaqueGlobal);
12897 }
12898 let mut row_start = first.coord.row();
12899 let mut row_end = row_start;
12900 let mut col_start = first.coord.col();
12901 let mut col_end = col_start;
12902 for cell in cells.iter().skip(1) {
12903 row_start = row_start.min(cell.coord.row());
12904 row_end = row_end.max(cell.coord.row());
12905 col_start = col_start.min(cell.coord.col());
12906 col_end = col_end.max(cell.coord.col());
12907 }
12908 Some(StructuralScope::Region(Region::rect(
12909 sheet_id, row_start, row_end, col_start, col_end,
12910 )))
12911 }
12912
12913 pub fn set_cell_value_ref(
12914 &mut self,
12915 cell: formualizer_common::SheetCellRef<'_>,
12916 current_sheet: &str,
12917 value: LiteralValue,
12918 ) -> Result<(), ExcelError> {
12919 let owned = cell.into_owned();
12920 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
12921 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12922 self.set_cell_value(
12923 &sheet_name,
12924 owned.coord.row() + 1,
12925 owned.coord.col() + 1,
12926 value,
12927 )
12928 }
12929
12930 pub fn set_cell_formula_ref(
12931 &mut self,
12932 cell: formualizer_common::SheetCellRef<'_>,
12933 current_sheet: &str,
12934 ast: ASTNode,
12935 ) -> Result<(), ExcelError> {
12936 let owned = cell.into_owned();
12937 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
12938 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12939 self.set_cell_formula(
12940 &sheet_name,
12941 owned.coord.row() + 1,
12942 owned.coord.col() + 1,
12943 ast,
12944 )
12945 }
12946
12947 pub fn get_cell_value_ref(
12948 &self,
12949 cell: formualizer_common::SheetCellRef<'_>,
12950 current_sheet: &str,
12951 ) -> Result<Option<LiteralValue>, ExcelError> {
12952 let owned = cell.into_owned();
12953 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
12954 let sheet_name = self.graph.sheet_name(sheet_id);
12955 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
12956 }
12957
12958 pub fn resolve_range_view_sheet_ref<'c>(
12959 &'c self,
12960 r: &formualizer_common::SheetRef<'_>,
12961 current_sheet: &str,
12962 ) -> Result<RangeView<'c>, ExcelError> {
12963 use formualizer_common::SheetLocator;
12964
12965 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
12966 match loc {
12967 SheetLocator::Current => Ok(None),
12968 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
12969 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
12970 }
12971 };
12972
12973 let rt = match r {
12974 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
12975 sheet: sheet_to_opt_name(cell.sheet.clone())?,
12976 row: cell.coord.row() + 1,
12977 col: cell.coord.col() + 1,
12978 row_abs: cell.coord.row_abs(),
12979 col_abs: cell.coord.col_abs(),
12980 },
12981 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
12982 sheet: sheet_to_opt_name(range.sheet.clone())?,
12983 start_row: range.start_row.map(|b| b.index + 1),
12984 start_col: range.start_col.map(|b| b.index + 1),
12985 end_row: range.end_row.map(|b| b.index + 1),
12986 end_col: range.end_col.map(|b| b.index + 1),
12987 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
12988 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
12989 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
12990 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
12991 },
12992 };
12993
12994 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
12995 }
12996
12997 pub fn set_cell_formula(
12999 &mut self,
13000 sheet: &str,
13001 row: u32,
13002 col: u32,
13003 ast: ASTNode,
13004 ) -> Result<(), ExcelError> {
13005 self.observe_function_semantic_epoch()?;
13006 let sheet_id = self.graph.sheet_id_mut(sheet);
13007 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13008 let ingested = {
13009 let mut pipeline = self.ingest_pipeline();
13010 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
13011 };
13012 self.graph.set_cell_formula_with_plan(
13013 sheet,
13014 row,
13015 col,
13016 ingested.ast_id,
13017 &ingested.dep_plan,
13018 ingested.dep_plan.volatile,
13019 ingested.dep_plan.dynamic,
13020 )?;
13021 self.clear_cell_format_state(sheet, placement);
13022 self.record_changed_cell(sheet, row, col);
13023
13024 self.clear_delta_overlay_cell(sheet, row, col);
13029
13030 self.mark_topology_edited();
13032 Ok(())
13033 }
13034
13035 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
13037 where
13038 I: IntoIterator<Item = (u32, u32, ASTNode)>,
13039 {
13040 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
13041 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
13042 let sheet_id = self.graph.sheet_id_mut(sheet);
13043 let ingested = {
13044 let mut pipeline = self.ingest_pipeline();
13045 let inputs = collected.into_iter().map(|(row, col, ast)| {
13046 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13047 (FormulaAstInput::Tree(ast), placement, None)
13048 });
13049 pipeline.ingest_batch(inputs)?
13050 };
13051 let planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)> = ingested
13052 .into_iter()
13053 .map(|formula| {
13054 (
13055 formula.placement.coord.row() + 1,
13056 formula.placement.coord.col() + 1,
13057 formula.ast_id,
13058 formula.dep_plan,
13059 )
13060 })
13061 .collect();
13062 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
13063 for (row, col) in edited_cells {
13064 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13065 self.clear_cell_format_state(sheet, cell);
13066 self.record_changed_cell(sheet, row, col);
13067 }
13068 if n > 0 {
13070 self.mark_topology_edited();
13071 }
13072 Ok(n)
13073 }
13074
13075 #[inline]
13076 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
13077 match v {
13078 LiteralValue::Empty => None,
13079 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
13080 other => Some(other),
13081 }
13082 }
13083
13084 fn materialize_temporal_egress(
13085 value: LiteralValue,
13086 class: Option<&formualizer_common::numfmt::FormatClass>,
13087 policy: crate::engine::TemporalEgress,
13088 date_system: crate::engine::DateSystem,
13089 ) -> LiteralValue {
13090 use formualizer_common::numfmt::FormatClass;
13091 if policy == crate::engine::TemporalEgress::Serial {
13092 return value;
13093 }
13094 let LiteralValue::Number(serial) = value else {
13095 return value;
13096 };
13097 match class {
13098 Some(FormatClass::Date) => {
13099 formualizer_common::try_serial_to_date_for(date_system, serial)
13100 .map(LiteralValue::Date)
13101 .unwrap_or(LiteralValue::Number(serial))
13102 }
13103 Some(FormatClass::DateTime) => {
13104 formualizer_common::try_serial_to_datetime_for(date_system, serial)
13105 .map(LiteralValue::DateTime)
13106 .unwrap_or(LiteralValue::Number(serial))
13107 }
13108 Some(FormatClass::Time) => {
13109 let seconds = (serial.rem_euclid(1.0) * 86_400.0).round() as u32 % 86_400;
13110 chrono::NaiveTime::from_num_seconds_from_midnight_opt(seconds, 0)
13111 .map(LiteralValue::Time)
13112 .unwrap_or(LiteralValue::Number(serial))
13113 }
13114 Some(FormatClass::Duration) => {
13115 let nanos = (serial * 86_400.0 * 1_000_000_000.0).round();
13116 if nanos.is_finite() && nanos >= i64::MIN as f64 && nanos <= i64::MAX as f64 {
13117 LiteralValue::Duration(chrono::Duration::nanoseconds(nanos as i64))
13118 } else {
13119 LiteralValue::Number(serial)
13120 }
13121 }
13122 _ => LiteralValue::Number(serial),
13123 }
13124 }
13125
13126 pub(crate) fn effective_format_id(
13127 &self,
13128 sheet: &str,
13129 row: u32,
13130 col: u32,
13131 ) -> Option<crate::format::FormatId> {
13132 let arrow = self.arrow_sheets.sheet(sheet).and_then(|arrow| {
13133 arrow.format_id(
13134 row.saturating_sub(1) as usize,
13135 col.saturating_sub(1) as usize,
13136 )
13137 });
13138 arrow.or_else(|| {
13139 let sheet_id = self.graph.sheet_id(sheet)?;
13140 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13141 self.derived_formats.get(&cell)
13142 })
13143 }
13144
13145 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13147 let raw = self.read_cell_value(sheet, row, col)?;
13148 let format = self.effective_format_id(sheet, row, col);
13149 let class = format.and_then(|id| self.format_registry.class(id));
13150 Self::normalize_public_cell_read(Self::materialize_temporal_egress(
13151 raw,
13152 class,
13153 self.config.temporal_egress,
13154 self.config.date_system,
13155 ))
13156 }
13157
13158 pub fn get_range_values(
13160 &self,
13161 sheet: &str,
13162 sr: u32,
13163 sc: u32,
13164 er: u32,
13165 ec: u32,
13166 ) -> Vec<Vec<LiteralValue>> {
13167 let height = er.saturating_sub(sr).saturating_add(1) as usize;
13168 let width = ec.saturating_sub(sc).saturating_add(1) as usize;
13169 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13170 return vec![vec![LiteralValue::Empty; width]; height];
13171 };
13172 let view = asheet.range_view(
13173 sr.saturating_sub(1) as usize,
13174 sc.saturating_sub(1) as usize,
13175 er.saturating_sub(1) as usize,
13176 ec.saturating_sub(1) as usize,
13177 );
13178 let sheet_id = self.graph.sheet_id(sheet);
13179 let derived_formats = &self.derived_formats;
13180 let has_derived_formats =
13181 sheet_id.is_some_and(|sheet_id| derived_formats.any(|cell| cell.sheet_id == sheet_id));
13182 let mut out = Vec::with_capacity(height);
13183 if !asheet.has_formats() && !has_derived_formats {
13184 for rr in 0..height {
13185 let mut row = Vec::with_capacity(width);
13186 for cc in 0..width {
13187 row.push(view.get_cell(rr, cc));
13188 }
13189 out.push(row);
13190 }
13191 return out;
13192 }
13193 let format_registry = &self.format_registry;
13194 for rr in 0..height {
13195 let mut row = Vec::with_capacity(width);
13196 for cc in 0..width {
13197 let raw = view.get_cell(rr, cc);
13198 let row0 = sr.saturating_sub(1).saturating_add(rr as u32);
13199 let col0 = sc.saturating_sub(1).saturating_add(cc as u32);
13200 let format = asheet.format_id(row0 as usize, col0 as usize).or_else(|| {
13201 let cell = CellRef::new(sheet_id?, Coord::new(row0, col0, true, true));
13202 derived_formats.get(&cell)
13203 });
13204 let class = format.and_then(|id| format_registry.class(id));
13205 row.push(Self::materialize_temporal_egress(
13206 raw,
13207 class,
13208 self.config.temporal_egress,
13209 self.config.date_system,
13210 ));
13211 }
13212 out.push(row);
13213 }
13214 out
13215 }
13216
13217 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13219 let asheet = self.sheet_store().sheet(sheet)?;
13220 let r0 = row.saturating_sub(1) as usize;
13221 let c0 = col.saturating_sub(1) as usize;
13222 let v = asheet.get_cell_value(r0, c0);
13223 if matches!(v, LiteralValue::Empty) {
13224 None
13225 } else {
13226 Some(v)
13227 }
13228 }
13229
13230 pub(crate) fn read_range_values(
13232 &self,
13233 sheet: &str,
13234 sr: u32,
13235 sc: u32,
13236 er: u32,
13237 ec: u32,
13238 ) -> RangeView<'_> {
13239 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13240 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
13241 };
13242 if er < sr || ec < sc {
13243 return asheet.range_view(1, 1, 0, 0);
13244 }
13245 let sr0 = sr.saturating_sub(1) as usize;
13246 let sc0 = sc.saturating_sub(1) as usize;
13247 let er0 = er.saturating_sub(1) as usize;
13248 let ec0 = ec.saturating_sub(1) as usize;
13249 asheet.range_view(sr0, sc0, er0, ec0)
13250 }
13251
13252 pub fn get_cell(
13254 &self,
13255 sheet: &str,
13256 row: u32,
13257 col: u32,
13258 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
13259 let v = self.get_cell_value(sheet, row, col);
13260 let sheet_id = self.graph.sheet_id(sheet)?;
13261 let coord = Coord::from_excel(row, col, true, true);
13262 let cell = CellRef::new(sheet_id, coord);
13263 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
13264 let ast = self.graph.get_formula(vid);
13265 Some((ast, v))
13266 } else if v.is_some() || self.graph.had_legacy_cell_vertex(&cell) {
13267 Some((None, v))
13271 } else {
13272 None
13273 }
13274 }
13275
13276 pub fn begin_batch(&mut self) {
13278 self.graph.begin_batch();
13279 }
13280
13281 pub fn end_batch(&mut self) {
13283 self.graph.end_batch();
13284 }
13285
13286 pub fn begin_deferred_dirty(&mut self) {
13295 self.graph.begin_deferred_dirty();
13296 }
13297
13298 pub fn end_deferred_dirty(&mut self) {
13301 let _ = self.graph.end_deferred_dirty();
13302 }
13303
13304 pub fn dirty_propagation_visits(&self) -> u64 {
13308 self.graph.dirty_propagation_visits()
13309 }
13310
13311 #[inline]
13314 fn record_cell_if_changed(
13315 delta: &mut DeltaCollector,
13316 cell: &CellRef,
13317 old: &LiteralValue,
13318 new: &LiteralValue,
13319 ) {
13320 if old != new {
13321 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13322 }
13323 }
13324
13325 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
13326 self.observe_evaluation_resource_request(EvaluationRequestKind::Vertex, |engine| {
13327 engine.observe_function_semantic_epoch()?;
13328 engine.resource_checkpoint(1)?;
13330 if !engine.graph.vertex_exists(vertex_id) {
13331 return engine.evaluate_vertex_impl(vertex_id, None);
13332 }
13333 let is_formula = matches!(
13334 engine.graph.get_vertex_kind(vertex_id),
13335 VertexKind::FormulaScalar | VertexKind::FormulaArray
13336 );
13337 if is_formula {
13338 engine.begin_evaluation_request();
13339 #[cfg(any(test, feature = "legacy_oracle"))]
13340 engine.graph.flush_pending_edge_deltas();
13341 let roots = [crate::engine::target_preparation::TargetProducer::Legacy(
13342 vertex_id,
13343 )];
13344 engine.evaluate_legacy_target_roots(&roots, None)?;
13345 }
13346 engine.evaluate_vertex_impl(vertex_id, None)
13347 })
13348 }
13349
13350 fn publish_oversized_spill(
13352 &mut self,
13353 vertex_id: VertexId,
13354 error: ExcelError,
13355 mut delta: Option<&mut DeltaCollector>,
13356 ) -> LiteralValue {
13357 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
13358 let anchor = self
13359 .graph
13360 .get_cell_ref(vertex_id)
13361 .expect("cell ref for vertex");
13362 let spill_val = LiteralValue::Error(error);
13363 if let Some(d) = delta {
13364 let old = self
13365 .read_cell_value(
13366 self.graph.sheet_name(anchor.sheet_id),
13367 anchor.coord.row() + 1,
13368 anchor.coord.col() + 1,
13369 )
13370 .unwrap_or(LiteralValue::Empty);
13371 if old != spill_val {
13372 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
13373 }
13374 }
13375 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13376 if self.config.arrow_storage_enabled
13377 && self.config.delta_overlay_enabled
13378 && self.config.write_formula_overlay_enabled
13379 {
13380 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13381 self.mirror_value_to_computed_overlay(
13382 &sheet_name,
13383 anchor.coord.row() + 1,
13384 anchor.coord.col() + 1,
13385 &spill_val,
13386 );
13387 }
13388 spill_val
13389 }
13390
13391 fn evaluate_vertex_impl(
13392 &mut self,
13393 vertex_id: VertexId,
13394 delta: Option<&mut DeltaCollector>,
13395 ) -> Result<LiteralValue, ExcelError> {
13396 if !self.graph.vertex_exists(vertex_id) {
13400 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
13401 .with_message(format!("Vertex not found: {vertex_id:?}")));
13402 }
13403 if self.active_resource_ledger.is_some()
13404 && matches!(
13405 self.graph.get_vertex_kind(vertex_id),
13406 VertexKind::FormulaScalar | VertexKind::FormulaArray
13407 )
13408 {
13409 let value = self
13410 .evaluate_vertex_immutable(vertex_id)
13411 .unwrap_or_else(LiteralValue::Error);
13412 let effects = self.plan_vertex_effects(vertex_id, value.clone(), None)?;
13413 self.resource_checkpoint(0)?;
13415 let mut delta = delta;
13416 for effect in &effects {
13417 self.apply_effect_with_computed_writes(effect, delta.as_deref_mut(), None, None)?;
13418 }
13419 return Ok(value);
13420 }
13421
13422 let mut delta = delta;
13423
13424 let kind = self.graph.get_vertex_kind(vertex_id);
13426 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
13427
13428 let view = match kind {
13429 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
13430 if let Some(view) = self.graph.formula_view(vertex_id) {
13431 view
13432 } else {
13433 return Ok(LiteralValue::Number(0.0));
13434 }
13435 }
13436 VertexKind::Empty | VertexKind::Cell => {
13437 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
13438 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13439 let row = cell_ref.coord.row() + 1;
13440 let col = cell_ref.coord.col() + 1;
13441 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
13442 return Ok(v);
13443 }
13444 }
13445 return Ok(LiteralValue::Number(0.0));
13446 }
13447 VertexKind::NamedScalar => {
13448 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
13449 return Ok(value);
13450 }
13451 VertexKind::NamedArray => {
13452 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
13453 return Ok(value);
13454 }
13455 VertexKind::InfiniteRange
13456 | VertexKind::Range
13457 | VertexKind::External
13458 | VertexKind::Table => {
13459 return Ok(LiteralValue::Number(0.0));
13461 }
13462 };
13463
13464 let sheet_name = self.graph.sheet_name(sheet_id);
13466 let cell_ref = self
13467 .graph
13468 .get_cell_ref(vertex_id)
13469 .expect("cell ref for vertex");
13470 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13471
13472 let result = interpreter.evaluate_formula_view(
13473 view,
13474 self.graph.data_store(),
13475 self.graph.sheet_reg(),
13476 );
13477
13478 match result {
13480 Ok(cv) => {
13481 let derived_format = cv.format_id();
13482 self.record_derived_format(vertex_id, derived_format);
13483 let oversized_range = crate::engine::result_finalization::range_spill_error(
13484 &cv,
13485 self.config.spill.max_spill_cells,
13486 );
13487 let is_oversized_range = oversized_range.is_some();
13488 let result_literal = if let Some(error) = oversized_range {
13489 drop(cv);
13490 LiteralValue::Error(error)
13491 } else {
13492 crate::engine::result_finalization::finalize_formula_result(cv.into_literal())
13493 };
13494 let output_sheet_name = sheet_name.to_string();
13495 self.write_computed_overlay_format_0based(
13496 &output_sheet_name,
13497 cell_ref.coord.row(),
13498 cell_ref.coord.col(),
13499 derived_format,
13500 );
13501 if is_oversized_range {
13502 let LiteralValue::Error(error) = result_literal else {
13503 unreachable!()
13504 };
13505 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
13506 return Ok(self.publish_oversized_spill(
13507 vertex_id,
13508 error,
13509 delta.as_deref_mut(),
13510 ));
13511 }
13512 match result_literal {
13513 LiteralValue::Array(rows) => {
13514 self.graph
13516 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
13517 let anchor = self
13519 .graph
13520 .get_cell_ref(vertex_id)
13521 .expect("cell ref for vertex");
13522 let sheet_id = anchor.sheet_id;
13523 let h = rows.len() as u32;
13524 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
13525
13526 let spill_cells = (h as u64).saturating_mul(w as u64);
13528 if spill_cells > self.config.spill.max_spill_cells as u64 {
13529 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13530 .with_message("SpillTooLarge")
13531 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13532 expected_rows: h,
13533 expected_cols: w,
13534 });
13535 return Ok(self.publish_oversized_spill(
13536 vertex_id,
13537 spill_err,
13538 delta.as_deref_mut(),
13539 ));
13540 }
13541 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);
13545 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
13546 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
13547 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
13548 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13549 .with_message("Spill exceeds sheet bounds")
13550 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13551 expected_rows: h,
13552 expected_cols: w,
13553 });
13554 let spill_val = LiteralValue::Error(spill_err.clone());
13555 if let Some(d) = delta.as_deref_mut() {
13556 let old = self
13557 .read_cell_value(
13558 self.graph.sheet_name(anchor.sheet_id),
13559 anchor.coord.row() + 1,
13560 anchor.coord.col() + 1,
13561 )
13562 .unwrap_or(LiteralValue::Empty);
13563 if old != spill_val {
13564 d.record_cell(
13565 anchor.sheet_id,
13566 anchor.coord.row(),
13567 anchor.coord.col(),
13568 );
13569 }
13570 }
13571 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13572 if self.config.arrow_storage_enabled
13573 && self.config.delta_overlay_enabled
13574 && self.config.write_formula_overlay_enabled
13575 {
13576 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13577 self.mirror_value_to_computed_overlay(
13578 &sheet_name,
13579 anchor.coord.row() + 1,
13580 anchor.coord.col() + 1,
13581 &spill_val,
13582 );
13583 }
13584 return Ok(spill_val);
13585 }
13586 let mut targets = Vec::new();
13587 for r in 0..h {
13588 for c in 0..w {
13589 targets.push(self.graph.make_cell_ref_internal(
13590 sheet_id,
13591 anchor.coord.row() + r,
13592 anchor.coord.col() + c,
13593 ));
13594 }
13595 }
13596
13597 match self.spill_mgr.reserve(
13599 vertex_id,
13600 anchor,
13601 SpillShape { rows: h, cols: w },
13602 SpillMeta {
13603 epoch: self.recalc_epoch,
13604 config: self.config.spill,
13605 },
13606 ) {
13607 Ok(()) => {
13608 if let Err(e) = self.commit_spill_and_mirror(
13612 vertex_id,
13613 &targets,
13614 rows.clone(),
13615 delta.as_deref_mut(),
13616 None,
13617 ) {
13618 if e.kind != ExcelErrorKind::Spill {
13619 return Err(e);
13620 }
13621 self.clear_spill_projection_and_mirror(
13623 vertex_id,
13624 delta.as_deref_mut(),
13625 );
13626 if let Some(d) = delta.as_deref_mut() {
13627 let old = self
13628 .read_cell_value(
13629 self.graph.sheet_name(anchor.sheet_id),
13630 anchor.coord.row() + 1,
13631 anchor.coord.col() + 1,
13632 )
13633 .unwrap_or(LiteralValue::Empty);
13634 let new = LiteralValue::Error(e.clone());
13635 if old != new {
13636 d.record_cell(
13637 anchor.sheet_id,
13638 anchor.coord.row(),
13639 anchor.coord.col(),
13640 );
13641 }
13642 }
13643 let err_val = LiteralValue::Error(e.clone());
13644 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13645 if self.config.arrow_storage_enabled
13646 && self.config.delta_overlay_enabled
13647 && self.config.write_formula_overlay_enabled
13648 {
13649 let sheet_name =
13650 self.graph.sheet_name(anchor.sheet_id).to_string();
13651 self.mirror_value_to_computed_overlay(
13652 &sheet_name,
13653 anchor.coord.row() + 1,
13654 anchor.coord.col() + 1,
13655 &err_val,
13656 );
13657 }
13658 return Ok(err_val);
13659 }
13660 let top_left = rows
13662 .first()
13663 .and_then(|r| r.first())
13664 .cloned()
13665 .unwrap_or(LiteralValue::Empty);
13666 self.graph.update_vertex_value_ref(vertex_id, &top_left);
13667 Ok(top_left)
13668 }
13669 Err(e) => {
13670 self.clear_spill_projection_and_mirror(
13671 vertex_id,
13672 delta.as_deref_mut(),
13673 );
13674 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13675 .with_message(
13676 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
13677 )
13678 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13679 expected_rows: h,
13680 expected_cols: w,
13681 });
13682 let spill_val = LiteralValue::Error(spill_err.clone());
13683 if let Some(d) = delta.as_deref_mut() {
13684 let old = self
13685 .read_cell_value(
13686 self.graph.sheet_name(anchor.sheet_id),
13687 anchor.coord.row() + 1,
13688 anchor.coord.col() + 1,
13689 )
13690 .unwrap_or(LiteralValue::Empty);
13691 if old != spill_val {
13692 d.record_cell(
13693 anchor.sheet_id,
13694 anchor.coord.row(),
13695 anchor.coord.col(),
13696 );
13697 }
13698 }
13699 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13700 if self.config.arrow_storage_enabled
13701 && self.config.delta_overlay_enabled
13702 && self.config.write_formula_overlay_enabled
13703 {
13704 let sheet_name =
13705 self.graph.sheet_name(anchor.sheet_id).to_string();
13706 self.mirror_value_to_computed_overlay(
13707 &sheet_name,
13708 anchor.coord.row() + 1,
13709 anchor.coord.col() + 1,
13710 &spill_val,
13711 );
13712 }
13713 Ok(spill_val)
13714 }
13715 }
13716 }
13717 other => {
13718 let spill_cells = self
13720 .graph
13721 .spill_cells_for_anchor(vertex_id)
13722 .map(|cells| cells.to_vec())
13723 .unwrap_or_default();
13724 if let Some(d) = delta.as_deref_mut()
13725 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
13726 {
13727 if spill_cells.is_empty() {
13728 let old = self
13729 .read_cell_value(
13730 self.graph.sheet_name(anchor.sheet_id),
13731 anchor.coord.row() + 1,
13732 anchor.coord.col() + 1,
13733 )
13734 .unwrap_or(LiteralValue::Empty);
13735 if old != other {
13736 d.record_cell(
13737 anchor.sheet_id,
13738 anchor.coord.row(),
13739 anchor.coord.col(),
13740 );
13741 }
13742 } else {
13743 for cell in spill_cells.iter() {
13744 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13745 let old = self
13746 .get_cell_value(
13747 sheet_name,
13748 cell.coord.row() + 1,
13749 cell.coord.col() + 1,
13750 )
13751 .unwrap_or(LiteralValue::Empty);
13752 let new = if cell.sheet_id == anchor.sheet_id
13753 && cell.coord.row() == anchor.coord.row()
13754 && cell.coord.col() == anchor.coord.col()
13755 {
13756 other.clone()
13757 } else {
13758 LiteralValue::Empty
13759 };
13760 Self::record_cell_if_changed(d, cell, &old, &new);
13761 }
13762 }
13763 }
13764 self.graph.clear_spill_region(vertex_id);
13765 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
13766 self.record_structural_change(scope);
13767 }
13768 if self.config.arrow_storage_enabled
13769 && self.config.delta_overlay_enabled
13770 && self.config.write_formula_overlay_enabled
13771 {
13772 let empty = LiteralValue::Empty;
13773 for cell in spill_cells.iter() {
13774 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13775 self.mirror_value_to_computed_overlay(
13776 &sheet_name,
13777 cell.coord.row() + 1,
13778 cell.coord.col() + 1,
13779 &empty,
13780 );
13781 }
13782 }
13783 self.graph.update_vertex_value_ref(vertex_id, &other);
13784 if self.config.arrow_storage_enabled
13786 && self.config.delta_overlay_enabled
13787 && self.config.write_formula_overlay_enabled
13788 {
13789 let anchor = self
13790 .graph
13791 .get_cell_ref(vertex_id)
13792 .expect("cell ref for vertex");
13793 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13794 self.mirror_value_to_computed_overlay(
13795 &sheet_name,
13796 anchor.coord.row() + 1,
13797 anchor.coord.col() + 1,
13798 &other,
13799 );
13800 }
13801 Ok(other)
13802 }
13803 }
13804 }
13805 Err(e) => {
13806 let spill_cells = self
13809 .graph
13810 .spill_cells_for_anchor(vertex_id)
13811 .map(|cells| cells.to_vec())
13812 .unwrap_or_default();
13813 let err_val = LiteralValue::Error(e.clone());
13814 if let Some(d) = delta
13815 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
13816 {
13817 if spill_cells.is_empty() {
13818 let old = self
13819 .read_cell_value(
13820 self.graph.sheet_name(anchor.sheet_id),
13821 anchor.coord.row() + 1,
13822 anchor.coord.col() + 1,
13823 )
13824 .unwrap_or(LiteralValue::Empty);
13825 if old != err_val {
13826 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
13827 }
13828 } else {
13829 for cell in spill_cells.iter() {
13830 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13831 let old = self
13832 .get_cell_value(
13833 sheet_name,
13834 cell.coord.row() + 1,
13835 cell.coord.col() + 1,
13836 )
13837 .unwrap_or(LiteralValue::Empty);
13838 let new = if cell.sheet_id == anchor.sheet_id
13839 && cell.coord.row() == anchor.coord.row()
13840 && cell.coord.col() == anchor.coord.col()
13841 {
13842 err_val.clone()
13843 } else {
13844 LiteralValue::Empty
13845 };
13846 Self::record_cell_if_changed(d, cell, &old, &new);
13847 }
13848 }
13849 }
13850 self.graph.clear_spill_region(vertex_id);
13851 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
13852 self.record_structural_change(scope);
13853 }
13854 if self.config.arrow_storage_enabled
13855 && self.config.delta_overlay_enabled
13856 && self.config.write_formula_overlay_enabled
13857 {
13858 let empty = LiteralValue::Empty;
13859 for cell in spill_cells.iter() {
13860 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13861 self.mirror_value_to_computed_overlay(
13862 &sheet_name,
13863 cell.coord.row() + 1,
13864 cell.coord.col() + 1,
13865 &empty,
13866 );
13867 }
13868 }
13869 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13870 if self.config.arrow_storage_enabled
13871 && self.config.delta_overlay_enabled
13872 && self.config.write_formula_overlay_enabled
13873 {
13874 let anchor = self
13875 .graph
13876 .get_cell_ref(vertex_id)
13877 .expect("cell ref for vertex");
13878 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13879 self.mirror_value_to_computed_overlay(
13880 &sheet_name,
13881 anchor.coord.row() + 1,
13882 anchor.coord.col() + 1,
13883 &err_val,
13884 );
13885 }
13886 Ok(err_val)
13887 }
13888 }
13889 }
13890
13891 fn evaluate_named_scalar(
13892 &mut self,
13893 vertex_id: VertexId,
13894 sheet_id: SheetId,
13895 ) -> Result<LiteralValue, ExcelError> {
13896 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
13897 ExcelError::new(ExcelErrorKind::Name)
13898 .with_message("Named range metadata missing".to_string())
13899 })?;
13900
13901 match &named_range.definition {
13902 NamedDefinition::Cell(cell_ref) => {
13903 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13904 let row = cell_ref.coord.row() + 1;
13905 let col = cell_ref.coord.col() + 1;
13906
13907 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
13908 && matches!(
13909 self.graph.get_vertex_kind(dep_vertex),
13910 VertexKind::FormulaScalar | VertexKind::FormulaArray
13911 )
13912 {
13913 let value = self.evaluate_vertex(dep_vertex)?;
13915 self.graph.update_vertex_value_ref(vertex_id, &value);
13916 Ok(value)
13917 } else {
13918 let value = self
13919 .get_cell_value(sheet_name, row, col)
13920 .unwrap_or(LiteralValue::Empty);
13921 self.graph.update_vertex_value_ref(vertex_id, &value);
13922 Ok(value)
13923 }
13924 }
13925 NamedDefinition::Literal(v) => {
13926 let out = v.clone();
13927 self.graph.update_vertex_value_ref(vertex_id, &out);
13928 Ok(out)
13929 }
13930 NamedDefinition::Formula { ast, .. } => {
13931 let context_sheet = match named_range.scope {
13932 NameScope::Sheet(id) => id,
13933 NameScope::Workbook => sheet_id,
13934 };
13935 let sheet_name = self.graph.sheet_name(context_sheet);
13936 let cell_ref = self
13937 .graph
13938 .get_cell_ref(vertex_id)
13939 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
13940 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13941 match interpreter.evaluate_ast(ast) {
13942 Ok(cv) => {
13943 let value = cv.into_literal();
13944 match value {
13945 LiteralValue::Array(_) => {
13946 let err = ExcelError::new(ExcelErrorKind::NImpl)
13947 .with_message("Array result in scalar named range".to_string());
13948 let err_val = LiteralValue::Error(err.clone());
13949 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13950 Ok(err_val)
13951 }
13952 other => {
13953 self.graph.update_vertex_value_ref(vertex_id, &other);
13954 Ok(other)
13955 }
13956 }
13957 }
13958 Err(err) => {
13959 let err_val = LiteralValue::Error(err.clone());
13960 self.graph.update_vertex_value_ref(vertex_id, &err_val);
13961 Ok(err_val)
13962 }
13963 }
13964 }
13965 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
13966 .with_message("Range-valued name evaluated as scalar".to_string())),
13967 }
13968 }
13969
13970 fn evaluate_named_array(
13971 &mut self,
13972 vertex_id: VertexId,
13973 sheet_id: SheetId,
13974 ) -> Result<LiteralValue, ExcelError> {
13975 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
13976 ExcelError::new(ExcelErrorKind::Name)
13977 .with_message("Named range metadata missing".to_string())
13978 })?;
13979
13980 let out = match &named_range.definition {
13981 NamedDefinition::Range(range_ref) => {
13982 if range_ref.start.sheet_id != range_ref.end.sheet_id {
13983 return Err(ExcelError::new(ExcelErrorKind::Ref)
13984 .with_message("Named range cannot span sheets".to_string()));
13985 }
13986
13987 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
13988 let sr0 = range_ref.start.coord.row();
13989 let sc0 = range_ref.start.coord.col();
13990 let er0 = range_ref.end.coord.row();
13991 let ec0 = range_ref.end.coord.col();
13992 if sr0 > er0 || sc0 > ec0 {
13993 return Err(ExcelError::new(ExcelErrorKind::Ref)
13994 .with_message("Invalid named range bounds".to_string()));
13995 }
13996
13997 let h = (er0 - sr0 + 1) as usize;
13998 let w = (ec0 - sc0 + 1) as usize;
13999 let cell_count = (h as u64).saturating_mul(w as u64);
14000 if cell_count > self.config.spill.max_spill_cells as u64 {
14001 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
14002 "Named range too large to materialize as an array".to_string(),
14003 ));
14004 }
14005
14006 let mut rows = Vec::with_capacity(h);
14007 for r0 in sr0..=er0 {
14008 let mut row = Vec::with_capacity(w);
14009 for c0 in sc0..=ec0 {
14010 let v = self
14011 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
14012 .unwrap_or(LiteralValue::Empty);
14013 row.push(v);
14014 }
14015 rows.push(row);
14016 }
14017 LiteralValue::Array(rows)
14018 }
14019 NamedDefinition::Cell(cell_ref) => {
14020 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
14021 let row = cell_ref.coord.row() + 1;
14022 let col = cell_ref.coord.col() + 1;
14023 let v = self
14024 .get_cell_value(sheet_name, row, col)
14025 .unwrap_or(LiteralValue::Empty);
14026 LiteralValue::Array(vec![vec![v]])
14027 }
14028 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
14029 NamedDefinition::Formula { ast, .. } => {
14030 let context_sheet = match named_range.scope {
14031 NameScope::Sheet(id) => id,
14032 NameScope::Workbook => sheet_id,
14033 };
14034 let sheet_name = self.graph.sheet_name(context_sheet);
14035 let cell_ref = self
14036 .graph
14037 .get_cell_ref(vertex_id)
14038 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
14039 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
14040 match interpreter.evaluate_ast(ast) {
14041 Ok(cv) => {
14042 let v = cv.into_literal();
14043 match v {
14044 LiteralValue::Array(_) => v,
14045 other => LiteralValue::Array(vec![vec![other]]),
14046 }
14047 }
14048 Err(err) => LiteralValue::Error(err),
14049 }
14050 }
14051 };
14052
14053 self.graph.update_vertex_value_ref(vertex_id, &out);
14054 Ok(out)
14055 }
14056
14057 fn replan_exhausted_error(&self, limit: usize, context: &str) -> ExcelError {
14058 crate::engine::ResourceLedgerError::Exhausted(
14059 formualizer_common::ResourceExhaustionDetail {
14060 reason: formualizer_common::ResourceExhaustionReason::WorkUnits,
14061 limit: limit as u64,
14062 observed: limit.saturating_add(1) as u64,
14063 request_id: self
14064 .active_evaluation_resource_request
14065 .as_ref()
14066 .map(|stats| stats.request_id),
14067 },
14068 )
14069 .into_excel_error()
14070 .with_message(format!("{context} did not converge after {limit} replans"))
14071 }
14072
14073 fn transient_target_preparation_stale(error: &ExcelError) -> bool {
14074 matches!(
14075 &error.extra,
14076 formualizer_common::ExcelErrorExtra::PreparationStale {
14077 reason: formualizer_common::PreparationStaleReason::Semantic
14078 | formualizer_common::PreparationStaleReason::Provider
14079 }
14080 )
14081 }
14082
14083 fn prepare_graph_for_routed_evaluation(
14084 &mut self,
14085 targets: &[crate::engine::EvaluationTarget],
14086 options: &crate::engine::TargetEvalOptions<'_>,
14087 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
14088 const MAX_TRANSIENT_PREPARATION_RETRIES: usize = 2;
14089 let mut retries = 0usize;
14090 loop {
14091 match self.prepare_graph_for_targets_unobserved(targets, options) {
14092 Err(error)
14093 if Self::transient_target_preparation_stale(&error)
14094 && retries < MAX_TRANSIENT_PREPARATION_RETRIES =>
14095 {
14096 retries = retries.saturating_add(1);
14097 }
14098 result => return result,
14099 }
14100 }
14101 }
14102
14103 fn evaluate_mixed_targets(
14104 &mut self,
14105 targets: &[crate::engine::EvaluationTarget],
14106 delta: Option<&mut DeltaCollector>,
14107 ) -> Result<EvalResult, ExcelError> {
14108 let _source_cache = self.source_cache_session();
14109 let cancel = self.active_cancel_flag.clone();
14110 let options = crate::engine::TargetEvalOptions {
14111 request_id: self
14112 .active_evaluation_resource_request
14113 .as_ref()
14114 .map(|stats| stats.request_id),
14115 cancel,
14116 deadline: None,
14117 budgets: None,
14118 opaque_policy: crate::engine::OpaquePreparePolicy::Widen,
14119 };
14120 self.prepare_and_execute_target_recipe(targets, &options, delta)
14121 }
14122
14123 fn prepare_and_execute_target_recipe(
14124 &mut self,
14125 targets: &[crate::engine::EvaluationTarget],
14126 options: &crate::engine::TargetEvalOptions<'_>,
14127 delta: Option<&mut DeltaCollector>,
14128 ) -> Result<EvalResult, ExcelError> {
14129 let preparation = self.prepare_graph_for_routed_evaluation(targets, options)?;
14130 self.execute_prepared_target_recipe(targets, &preparation.widened_scope, delta)
14131 }
14132
14133 fn execute_prepared_target_recipe(
14134 &mut self,
14135 targets: &[crate::engine::EvaluationTarget],
14136 scope: &crate::engine::PrepareScope,
14137 delta: Option<&mut DeltaCollector>,
14138 ) -> Result<EvalResult, ExcelError> {
14139 self.require_unified_authority()?;
14140 if matches!(scope, crate::engine::PrepareScope::Workbook)
14141 && let Some(stats) = self.active_evaluation_resource_request.as_mut()
14142 {
14143 stats.workbook_exact_attempts = stats.workbook_exact_attempts.max(1);
14144 }
14145 let mut roots = self.resolve_target_producers(targets)?;
14146 if let crate::engine::PrepareScope::Sheets(sheets) = scope {
14147 let request_id = self
14148 .active_evaluation_resource_request
14149 .as_ref()
14150 .map(|request| request.request_id);
14151 let root_count = roots.len();
14152 let mut widened_roots =
14153 OrderedTargetProducers::from_ordered(std::mem::take(&mut roots))
14154 .map_err(|_| target_root_allocation_error(root_count, request_id))?;
14155 let sheet_ids = sheets
14156 .iter()
14157 .filter_map(|sheet| self.graph.sheet_id(sheet))
14158 .collect::<FxHashSet<_>>();
14159 for vertex in self.graph.formula_vertices() {
14160 if sheet_ids.contains(&self.graph.get_vertex_sheet_id(vertex)) {
14161 widened_roots
14162 .push(crate::engine::target_preparation::TargetProducer::Legacy(
14163 vertex,
14164 ))
14165 .map_err(|_| {
14166 target_root_allocation_error(widened_roots.len() + 1, request_id)
14167 })?;
14168 }
14169 }
14170 roots = widened_roots.into_vec();
14171 }
14172 self.begin_evaluation_request();
14173 #[cfg(any(test, feature = "legacy_oracle"))]
14174 self.graph.flush_pending_edge_deltas();
14175 let workbook_scope = matches!(scope, crate::engine::PrepareScope::Workbook);
14176 if workbook_scope {
14177 if let Some(delta) = delta {
14178 self.evaluate_all_with_delta_collector(delta)
14179 } else {
14180 self.evaluate_all_legacy_impl()
14181 }
14182 } else {
14183 self.evaluate_legacy_target_roots(&roots, delta)
14184 }
14185 }
14186
14187 fn legacy_coordinate_targets(
14188 &mut self,
14189 targets: &[(&str, u32, u32)],
14190 ) -> Vec<crate::engine::EvaluationTarget> {
14191 targets
14192 .iter()
14193 .map(|(sheet, row, col)| {
14194 self.graph.sheet_id_mut(sheet);
14197 crate::engine::EvaluationTarget::Cell {
14198 sheet: (*sheet).to_string(),
14199 row: *row,
14200 col: *col,
14201 }
14202 })
14203 .collect()
14204 }
14205
14206 pub fn evaluate_targets(
14208 &mut self,
14209 targets: &[crate::engine::EvaluationTarget],
14210 ) -> Result<EvalResult, ExcelError> {
14211 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14212 engine.observe_function_semantic_epoch()?;
14213 engine.validate_deterministic_mode()?;
14214 engine.evaluate_mixed_targets(targets, None)
14215 })
14216 }
14217
14218 pub fn evaluate_targets_with_options(
14220 &mut self,
14221 targets: &[crate::engine::EvaluationTarget],
14222 options: crate::engine::TargetEvalOptions<'_>,
14223 ) -> Result<EvalResult, ExcelError> {
14224 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14225 engine.active_cancel_flag = options.cancel.clone();
14226 engine.active_evaluation_deadline = options.deadline;
14227 let result = (|| {
14228 engine.cancellation_checkpoint("Evaluation cancelled before target preparation")?;
14229 engine.observe_function_semantic_epoch()?;
14230 engine.validate_deterministic_mode()?;
14231 let _source_cache = engine.source_cache_session();
14232 engine.prepare_and_execute_target_recipe(targets, &options, None)
14233 })();
14234 engine.active_cancel_flag = None;
14235 engine.active_evaluation_deadline = None;
14236 result
14237 })
14238 }
14239
14240 pub fn evaluate_targets_with_delta(
14242 &mut self,
14243 targets: &[crate::engine::EvaluationTarget],
14244 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
14245 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
14246 engine.observe_function_semantic_epoch()?;
14247 engine.validate_deterministic_mode()?;
14248 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14249 let result = engine.evaluate_mixed_targets(targets, Some(&mut collector))?;
14250 Ok((result, collector.finish_target()))
14251 })
14252 }
14253
14254 pub fn evaluate_until(
14256 &mut self,
14257 targets: &[(&str, u32, u32)],
14258 ) -> Result<EvalResult, ExcelError> {
14259 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14260 engine.evaluate_until_unobserved(targets)
14261 })
14262 }
14263
14264 fn evaluate_until_unobserved(
14265 &mut self,
14266 targets: &[(&str, u32, u32)],
14267 ) -> Result<EvalResult, ExcelError> {
14268 self.observe_function_semantic_epoch()?;
14269 let targets = self.legacy_coordinate_targets(targets);
14270 self.evaluate_mixed_targets(&targets, None)
14271 }
14272
14273 fn evaluate_until_with_delta_collector(
14274 &mut self,
14275 targets: &[(&str, u32, u32)],
14276 delta: &mut DeltaCollector,
14277 ) -> Result<EvalResult, ExcelError> {
14278 let targets = self.legacy_coordinate_targets(targets);
14279 self.evaluate_mixed_targets(&targets, Some(delta))
14280 }
14281
14282 fn evaluate_legacy_target_roots(
14283 &mut self,
14284 roots: &[crate::engine::target_preparation::TargetProducer],
14285 mut delta: Option<&mut DeltaCollector>,
14286 ) -> Result<EvalResult, ExcelError> {
14287 use crate::engine::target_preparation::TargetProducer;
14288 #[cfg(any(test, feature = "benchmark_internal"))]
14289 {
14290 self.recalc_reuse_probe
14291 .get_mut()
14292 .unwrap()
14293 .legacy_target_requests += 1;
14294 }
14295 let start = crate::instant::FzInstant::now();
14296 let root_vertices = roots
14297 .iter()
14298 .filter_map(|root| match root {
14299 TargetProducer::Legacy(vertex) | TargetProducer::Symbol(vertex) => Some(*vertex),
14300 TargetProducer::ValueOnly(_) => None,
14301 })
14302 .collect::<Vec<_>>();
14303 let mut computed_vertices = 0usize;
14304 let mut cycle_errors = 0usize;
14305 let mut replans = 0usize;
14306 const MAX_REPLAN: usize = 5;
14307 self.graph.authority_sync();
14308 loop {
14309 let (precedents_to_eval, old_vdeps) = self.demand_subgraph(&root_vertices)?;
14310 if precedents_to_eval.is_empty() {
14311 break;
14312 }
14313 #[cfg(any(test, feature = "benchmark_internal"))]
14314 {
14315 self.recalc_reuse_probe
14316 .get_mut()
14317 .unwrap()
14318 .target_schedule_builds += 1;
14319 }
14320 let schedule = {
14321 self.graph.authority_sync();
14322 let mut ledger = self.active_resource_ledger.take();
14323 let result = self.create_authority_schedule(
14324 &precedents_to_eval,
14325 &old_vdeps,
14326 ledger.as_mut(),
14327 );
14328 self.active_resource_ledger = ledger;
14329 result?
14330 };
14331 self.begin_pass(&schedule);
14332 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14333 self.cancellation_checkpoint("Evaluation cancelled before target schedule unit")?;
14334 match unit {
14335 ScheduleUnit::Cycle(index) => {
14336 if self.handle_cycle_unit(
14337 schedule.unit_cycle(index),
14338 delta.as_deref_mut(),
14339 None,
14340 None,
14341 )? > 0
14342 {
14343 cycle_errors = cycle_errors.saturating_add(1);
14344 }
14345 }
14346 ScheduleUnit::Layer(index) => {
14347 let layer = schedule.unit_layer(index);
14348 let evaluated = if let Some(delta) = delta.as_deref_mut() {
14349 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14350 self.evaluate_layer_parallel_with_delta(layer, delta)?
14351 } else {
14352 self.evaluate_layer_sequential_with_delta(layer, delta)?
14353 }
14354 } else if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14355 self.evaluate_layer_parallel(layer)?
14356 } else {
14357 self.evaluate_layer_sequential(layer)?
14358 };
14359 computed_vertices = computed_vertices.saturating_add(evaluated);
14360 }
14361 }
14362 if self.stop_after_unit(&schedule, unit_index) {
14363 break;
14364 }
14365 }
14366 let changed = self.changed_virtual_dep_vertices(&precedents_to_eval, &old_vdeps);
14367 self.resource_checkpoint(0)?;
14368 if !self.finish_target_pass_dirty(&precedents_to_eval, &changed) {
14369 break;
14370 }
14371 if replans >= MAX_REPLAN {
14372 return Err(self.replan_exhausted_error(
14373 MAX_REPLAN,
14374 "targeted legacy dynamic dependency evaluation",
14375 ));
14376 }
14377 replans = replans.saturating_add(1);
14378 }
14379 self.redirty_for_next_recalc();
14380 Ok(EvalResult {
14381 computed_vertices,
14382 cycle_errors,
14383 elapsed: start.elapsed(),
14384 })
14385 }
14386
14387 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
14389 if self.has_staged_formulas() || self.staged_formula_index.has_packages() {
14390 return Err(
14391 Self::plan_stale(formualizer_common::PlanStaleReason::Staged).with_message(
14392 "compatibility recalculation plans require all staged formulas to be prepared",
14393 ),
14394 );
14395 }
14396 let key = self.recalc_plan_key();
14397 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
14398 vertices.sort_unstable();
14399 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
14400 let schedule = if vertices.is_empty() {
14401 crate::engine::Schedule {
14402 units: Vec::new(),
14403 layers: Vec::new(),
14404 cycles: Vec::new(),
14405 }
14406 } else {
14407 self.create_evaluation_schedule_uncached(&vertices, None)?.0
14408 };
14409 self.validate_recalc_plan_key(&key)?;
14410 Ok(RecalcPlan {
14411 key,
14412 kind: RecalcPlanKind::CompatibilityFull {
14413 schedule,
14414 has_dynamic_refs,
14415 },
14416 })
14417 }
14418
14419 pub fn build_recalc_plan_for_targets(
14421 &mut self,
14422 targets: &[crate::engine::EvaluationTarget],
14423 ) -> Result<RecalcPlan, ExcelError> {
14424 self.build_recalc_plan_for_targets_with_options(
14425 targets,
14426 crate::engine::TargetEvalOptions::default(),
14427 )
14428 }
14429
14430 pub fn build_recalc_plan_for_targets_with_options(
14431 &mut self,
14432 targets: &[crate::engine::EvaluationTarget],
14433 options: crate::engine::TargetEvalOptions<'_>,
14434 ) -> Result<RecalcPlan, ExcelError> {
14435 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14436 engine.observe_function_semantic_epoch()?;
14437 engine.validate_deterministic_mode()?;
14438 let _source_cache = engine.source_cache_session();
14439 let preparation = engine.prepare_graph_for_routed_evaluation(targets, &options)?;
14440 #[cfg(any(test, feature = "legacy_oracle"))]
14441 engine.graph.flush_pending_edge_deltas();
14442 let topology = if matches!(
14443 preparation.widened_scope,
14444 crate::engine::PrepareScope::Workbook
14445 ) {
14446 RecalcTopology::Workbook
14447 } else {
14448 RecalcTopology::RunLocalRecipe
14449 };
14450 Ok(RecalcPlan {
14451 key: engine.recalc_plan_key(),
14452 kind: RecalcPlanKind::Target {
14453 targets: targets.to_vec(),
14454 scope: preparation.widened_scope,
14455 topology,
14456 dynamic_policy: DynamicPlanPolicy::BoundedTargetReplan,
14457 },
14458 })
14459 })
14460 }
14461
14462 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
14464 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14465 engine.evaluate_recalc_plan_unobserved(plan)
14466 })
14467 }
14468
14469 pub fn evaluate_recalc_plan_with_controls(
14470 &mut self,
14471 plan: &RecalcPlan,
14472 cancel: Option<crate::engine::CancelToken>,
14473 deadline: Option<Instant>,
14474 ) -> Result<EvalResult, ExcelError> {
14475 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14476 engine.active_cancel_flag = cancel.clone();
14477 engine.active_evaluation_deadline = deadline;
14478 let result = engine.evaluate_recalc_plan_unobserved(plan);
14479 engine.active_cancel_flag = None;
14480 engine.active_evaluation_deadline = None;
14481 result
14482 })
14483 }
14484
14485 fn evaluate_recalc_plan_unobserved(
14486 &mut self,
14487 plan: &RecalcPlan,
14488 ) -> Result<EvalResult, ExcelError> {
14489 #[cfg(any(test, feature = "legacy_oracle"))]
14490 self.graph.flush_pending_edge_deltas();
14491 self.validate_recalc_plan_key(&plan.key)?;
14492 self.cancellation_checkpoint("Evaluation cancelled before recalculation plan execution")?;
14493 self.validate_deterministic_mode()?;
14494
14495 match &plan.kind {
14496 RecalcPlanKind::Target {
14497 targets,
14498 scope,
14499 topology,
14500 dynamic_policy,
14501 } => {
14502 debug_assert_eq!(*dynamic_policy, DynamicPlanPolicy::BoundedTargetReplan);
14503 debug_assert_eq!(
14504 matches!(topology, RecalcTopology::Workbook),
14505 matches!(scope, crate::engine::PrepareScope::Workbook)
14506 );
14507 let _source_cache = self.source_cache_session();
14508 self.execute_prepared_target_recipe(targets, scope, None)
14509 }
14510 RecalcPlanKind::CompatibilityFull {
14511 schedule,
14512 has_dynamic_refs,
14513 } => {
14514 let _source_cache = self.source_cache_session();
14515 self.begin_evaluation_request();
14516 if *has_dynamic_refs {
14517 self.virtual_dep_fallback_activations =
14518 self.virtual_dep_fallback_activations.saturating_add(1);
14519 return self.evaluate_all_coordinator();
14520 }
14521
14522 let start = crate::instant::FzInstant::now();
14523 let dirty_vertices = self.graph.get_evaluation_vertices();
14524 if dirty_vertices.is_empty() {
14525 return Ok(EvalResult {
14526 computed_vertices: 0,
14527 cycle_errors: 0,
14528 elapsed: start.elapsed(),
14529 });
14530 }
14531
14532 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
14533 let mut computed_vertices = 0;
14534 let mut cycle_errors = 0;
14535 for &unit in &schedule.units {
14536 self.cancellation_checkpoint(
14537 "Evaluation cancelled before recalculation plan schedule unit",
14538 )?;
14539 match unit {
14540 ScheduleUnit::Cycle(i) => {
14541 let stamped = self.handle_cycle_unit(
14542 schedule.unit_cycle(i),
14543 None,
14544 Some(&dirty_set),
14545 None,
14546 )?;
14547 if stamped > 0 {
14548 cycle_errors += 1;
14549 }
14550 }
14551 ScheduleUnit::Layer(i) => {
14552 let work: Vec<VertexId> = schedule
14553 .unit_layer(i)
14554 .vertices
14555 .iter()
14556 .copied()
14557 .filter(|v| dirty_set.contains(v))
14558 .collect();
14559 if work.is_empty() {
14560 continue;
14561 }
14562 let temp_layer = crate::engine::scheduler::Layer::new(work);
14563 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
14564 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
14565 } else {
14566 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
14567 }
14568 }
14569 }
14570 }
14571
14572 self.resource_checkpoint(0)?;
14573 self.graph.clear_dirty_flags(&dirty_vertices);
14574 self.redirty_for_next_recalc();
14575 Ok(EvalResult {
14576 computed_vertices,
14577 cycle_errors,
14578 elapsed: start.elapsed(),
14579 })
14580 }
14581 }
14582 }
14583}
14584
14585impl<R> Engine<R>
14586where
14587 R: EvaluationContext,
14588{
14589 fn require_unified_authority(&mut self) -> Result<(), ExcelError> {
14594 self.graph
14595 .authority()
14596 .map(|_| ())
14597 .map_err(Self::authority_excel_error)
14598 }
14599
14600 fn authority_excel_error(error: crate::engine::authority::store::AuthorityError) -> ExcelError {
14601 use crate::engine::authority::store::AuthorityError;
14602 #[cfg(test)]
14606 if std::env::var_os("FZ_AUTHORITY_DEFERRED_TRACE").is_some() {
14607 eprintln!("M1B_AUTHORITY_ERROR {error:?}");
14608 }
14609 let kind = match error {
14610 AuthorityError::Unsupported { .. } => ExcelErrorKind::NImpl,
14611 _ => ExcelErrorKind::Error,
14612 };
14613 ExcelError::new(kind).with_message(format!("unified_authority: {error:?}"))
14614 }
14615
14616 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
14618 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
14620 engine.evaluate_all_unobserved()
14621 })
14622 }
14623
14624 fn evaluate_all_unobserved(&mut self) -> Result<EvalResult, ExcelError> {
14625 debug_assert!(
14626 !self.graph.deferred_dirty_active(),
14627 "deferred-dirty scope leaked into evaluate_all: a begin_deferred_dirty \
14628 was not balanced by end_deferred_dirty"
14629 );
14630 self.observe_function_semantic_epoch()?;
14631 self.lookup_index_cache.reset_counters();
14632 let _source_cache = self.source_cache_session();
14633 self.validate_deterministic_mode()?;
14634 if self.config.defer_graph_building {
14635 self.build_graph_all()?;
14637 }
14638 self.evaluate_all_coordinator()
14639 }
14640
14641 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
14644 self.require_unified_authority()?;
14645 self.begin_evaluation_request();
14646 self.evaluate_all_legacy_impl()
14647 }
14648
14649 fn legacy_pass_run_units(
14655 &mut self,
14656 schedule: &crate::engine::scheduler::Schedule,
14657 ) -> Result<(usize, usize), ExcelError> {
14658 let mut computed_vertices = 0;
14659 let mut cycle_count = 0;
14660 self.begin_pass(schedule);
14661 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14662 match unit {
14663 ScheduleUnit::Cycle(i) => {
14664 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
14665 cycle_count += 1;
14666 }
14667 }
14668 ScheduleUnit::Layer(i) => {
14669 let layer = schedule.unit_layer(i);
14670 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14671 computed_vertices += self.evaluate_layer_parallel(layer)?;
14672 } else {
14673 computed_vertices += self.evaluate_layer_sequential(layer)?;
14674 }
14675 }
14676 }
14677 if self.stop_after_unit(schedule, unit_index) {
14678 break;
14679 }
14680 }
14681 Ok((computed_vertices, cycle_count))
14682 }
14683
14684 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
14691 self.reset_virtual_dep_telemetry_if_disabled();
14692 let _span_eval =
14693 crate::engine::trace::fz_span!(tracing::Level::INFO, "evaluate", "evaluate.legacy");
14694 let start = crate::instant::FzInstant::now();
14695 let mut computed_vertices = 0;
14696 let mut cycle_errors = 0;
14697 let mut replan_iterations = 0;
14698 const MAX_REPLAN: usize = 5;
14699 let mut telemetry = self
14700 .config
14701 .enable_virtual_dep_telemetry
14702 .then(|| self.start_virtual_dep_telemetry());
14703
14704 loop {
14705 let to_evaluate = self.graph.get_evaluation_vertices();
14706 if to_evaluate.is_empty() {
14707 if let Some(t) = telemetry.as_mut()
14708 && t.bailout_reason.is_none()
14709 {
14710 t.bailout_reason = Some("no_work");
14711 }
14712 break;
14713 }
14714
14715 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14716 if let Some(t) = telemetry.as_mut() {
14717 Self::accumulate_schedule_meta(t, &meta);
14718 }
14719
14720 let (pass_computed, pass_cycles) = self.legacy_pass_run_units(&schedule)?;
14721 computed_vertices += pass_computed;
14722 cycle_errors += pass_cycles;
14723
14724 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14726 if let Some(t) = telemetry.as_mut() {
14727 t.changed_vdeps_total += changed_vertices.len();
14728 }
14729
14730 self.resource_checkpoint(0)?;
14731 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
14732 if let Some(t) = telemetry.as_mut() {
14733 t.bailout_reason = Some("converged");
14734 }
14735 break;
14736 }
14737 if replan_iterations >= MAX_REPLAN {
14738 if let Some(mut t) = telemetry.take() {
14739 t.bailout_reason = Some("max_replan");
14740 t.replan_iterations = replan_iterations;
14741 self.last_virtual_dep_telemetry = t;
14742 }
14743 return Err(
14744 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
14745 );
14746 }
14747
14748 replan_iterations += 1;
14749 }
14750
14751 if let Some(mut t) = telemetry {
14752 t.replan_iterations = replan_iterations;
14753 self.last_virtual_dep_telemetry = t;
14754 }
14755
14756 self.redirty_for_next_recalc();
14758
14759 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14761
14762 Ok(EvalResult {
14763 computed_vertices,
14764 cycle_errors,
14765 elapsed: start.elapsed(),
14766 })
14767 }
14768
14769 pub fn evaluate_all_with_target_delta(
14770 &mut self,
14771 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
14772 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
14773 engine.observe_function_semantic_epoch()?;
14774 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14775 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
14776 Ok((result, collector.finish_target()))
14777 })
14778 }
14779
14780 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
14781 self.evaluate_all_with_delta_policy(EvalDeltaCompatibilityPolicy::Unlimited)
14782 }
14783
14784 pub fn evaluate_all_with_delta_policy(
14785 &mut self,
14786 policy: EvalDeltaCompatibilityPolicy,
14787 ) -> Result<(EvalResult, EvalDelta), ExcelError> {
14788 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
14789 engine.observe_function_semantic_epoch()?;
14790 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14791 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
14792 Ok((result, collector.finish_with_policy(policy)?))
14793 })
14794 }
14795
14796 fn evaluate_all_with_delta_collector(
14797 &mut self,
14798 delta: &mut DeltaCollector,
14799 ) -> Result<EvalResult, ExcelError> {
14800 let _source_cache = self.source_cache_session();
14801 if self.config.defer_graph_building {
14802 self.build_graph_all()?;
14803 }
14804 self.require_unified_authority()?;
14805 self.begin_evaluation_request();
14806 self.reset_virtual_dep_telemetry_if_disabled();
14807 let _span_eval = crate::engine::trace::fz_span!(
14808 tracing::Level::INFO,
14809 "evaluate",
14810 "evaluate.legacy_delta"
14811 );
14812 let start = crate::instant::FzInstant::now();
14813 let mut computed_vertices = 0;
14814 let mut cycle_errors = 0;
14815
14816 let mut replan_iterations = 0;
14817 const MAX_REPLAN: usize = 5;
14818 let mut telemetry = self
14819 .config
14820 .enable_virtual_dep_telemetry
14821 .then(|| self.start_virtual_dep_telemetry());
14822
14823 loop {
14824 let to_evaluate = self.graph.get_evaluation_vertices();
14825 if to_evaluate.is_empty() {
14826 if let Some(t) = telemetry.as_mut()
14827 && t.bailout_reason.is_none()
14828 {
14829 t.bailout_reason = Some("no_work");
14830 }
14831 break;
14832 }
14833
14834 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14835 if let Some(t) = telemetry.as_mut() {
14836 Self::accumulate_schedule_meta(t, &meta);
14837 }
14838
14839 self.begin_pass(&schedule);
14840 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14841 match unit {
14842 ScheduleUnit::Cycle(i) => {
14843 if self.handle_cycle_unit(
14844 schedule.unit_cycle(i),
14845 Some(delta),
14846 None,
14847 None,
14848 )? > 0
14849 {
14850 cycle_errors += 1;
14851 }
14852 }
14853 ScheduleUnit::Layer(i) => {
14854 let layer = schedule.unit_layer(i);
14855 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14856 computed_vertices +=
14857 self.evaluate_layer_parallel_with_delta(layer, delta)?;
14858 } else {
14859 computed_vertices +=
14860 self.evaluate_layer_sequential_with_delta(layer, delta)?;
14861 }
14862 }
14863 }
14864 if self.stop_after_unit(&schedule, unit_index) {
14865 break;
14866 }
14867 }
14868
14869 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14870 if let Some(t) = telemetry.as_mut() {
14871 t.changed_vdeps_total += changed_vertices.len();
14872 }
14873 self.resource_checkpoint(0)?;
14874 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
14875 if let Some(t) = telemetry.as_mut() {
14876 t.bailout_reason = Some("converged");
14877 }
14878 break;
14879 }
14880 if replan_iterations >= MAX_REPLAN {
14881 if let Some(mut t) = telemetry.take() {
14882 t.bailout_reason = Some("max_replan");
14883 t.replan_iterations = replan_iterations;
14884 self.last_virtual_dep_telemetry = t;
14885 }
14886 return Err(
14887 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
14888 );
14889 }
14890 replan_iterations += 1;
14891 }
14892
14893 if let Some(mut t) = telemetry {
14894 t.replan_iterations = replan_iterations;
14895 self.last_virtual_dep_telemetry = t;
14896 }
14897
14898 self.redirty_for_next_recalc();
14899 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14900
14901 Ok(EvalResult {
14902 computed_vertices,
14903 cycle_errors,
14904 elapsed: start.elapsed(),
14905 })
14906 }
14907
14908 pub fn evaluate_cell(
14918 &mut self,
14919 sheet: &str,
14920 row: u32,
14921 col: u32,
14922 ) -> Result<Option<LiteralValue>, ExcelError> {
14923 self.observe_evaluation_resource_request(EvaluationRequestKind::Cell, |engine| {
14924 engine.evaluate_cell_unobserved(sheet, row, col)
14925 })
14926 }
14927
14928 fn evaluate_cell_unobserved(
14929 &mut self,
14930 sheet: &str,
14931 row: u32,
14932 col: u32,
14933 ) -> Result<Option<LiteralValue>, ExcelError> {
14934 if row == 0 || col == 0 {
14935 return Err(ExcelError::new(ExcelErrorKind::Ref)
14936 .with_message("Row and column must be >= 1".to_string()));
14937 }
14938
14939 let result = self.evaluate_cells(&[(sheet, row, col)])?;
14940
14941 match result.len() {
14942 0 => Ok(None),
14943 1 => {
14944 let v = result.into_iter().next().unwrap();
14945 Ok(v)
14946 }
14947 _ => unreachable!("evaluate_cells returned unexpected length"),
14948 }
14949 }
14950
14951 pub fn evaluate_cells(
14958 &mut self,
14959 targets: &[(&str, u32, u32)],
14960 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14961 self.observe_evaluation_resource_request(EvaluationRequestKind::Cells, |engine| {
14962 engine.evaluate_cells_unobserved(targets)
14963 })
14964 }
14965
14966 fn evaluate_cells_unobserved(
14967 &mut self,
14968 targets: &[(&str, u32, u32)],
14969 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14970 self.observe_function_semantic_epoch()?;
14971 debug_assert!(
14972 !self.graph.deferred_dirty_active(),
14973 "deferred-dirty scope leaked into evaluate_cells: a begin_deferred_dirty \
14974 was not balanced by end_deferred_dirty"
14975 );
14976 self.validate_deterministic_mode()?;
14977 if targets.is_empty() {
14978 return Ok(Vec::new());
14979 }
14980 let typed_targets = self.legacy_coordinate_targets(targets);
14981 self.evaluate_mixed_targets(&typed_targets, None)?;
14982 Ok(targets
14983 .iter()
14984 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
14985 .collect())
14986 }
14987
14988 pub fn evaluate_cells_cancellable(
14989 &mut self,
14990 targets: &[(&str, u32, u32)],
14991 cancel: crate::engine::CancelToken,
14992 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
14993 self.observe_evaluation_resource_request(
14994 EvaluationRequestKind::CellsCancellable,
14995 |engine| {
14996 engine.observe_function_semantic_epoch()?;
14997 engine.active_cancel_flag = Some(cancel.clone());
14998 let res = engine.evaluate_cells_cancellable_impl(targets, cancel.as_flag());
14999 engine.active_cancel_flag = None;
15000 res
15001 },
15002 )
15003 }
15004
15005 fn evaluate_cells_cancellable_impl(
15006 &mut self,
15007 targets: &[(&str, u32, u32)],
15008 cancel_flag: &AtomicBool,
15009 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
15010 self.validate_deterministic_mode()?;
15011 if targets.is_empty() {
15012 return Ok(Vec::new());
15013 }
15014 if cancel_flag.load(Ordering::Relaxed) {
15015 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
15016 .with_message("Evaluation cancelled before target preparation"));
15017 }
15018 let typed_targets = self.legacy_coordinate_targets(targets);
15019 self.evaluate_mixed_targets(&typed_targets, None)?;
15020 Ok(targets
15021 .iter()
15022 .map(|(sheet, row, col)| self.get_cell_value(sheet, *row, *col))
15023 .collect())
15024 }
15025
15026 pub fn evaluate_cells_with_target_delta(
15027 &mut self,
15028 targets: &[(&str, u32, u32)],
15029 ) -> Result<(Vec<Option<LiteralValue>>, crate::engine::TargetEvalDelta), ExcelError> {
15030 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
15031 engine.observe_function_semantic_epoch()?;
15032 engine.validate_deterministic_mode()?;
15033 if targets.is_empty() {
15034 return Ok((Vec::new(), crate::engine::TargetEvalDelta::default()));
15035 }
15036 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15037 engine.evaluate_until_with_delta_collector(targets, &mut collector)?;
15038 let values = targets
15039 .iter()
15040 .map(|(sheet, row, col)| engine.get_cell_value(sheet, *row, *col))
15041 .collect();
15042 Ok((values, collector.finish_target()))
15043 })
15044 }
15045
15046 pub fn evaluate_cells_with_delta(
15047 &mut self,
15048 targets: &[(&str, u32, u32)],
15049 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15050 self.evaluate_cells_with_delta_policy(targets, EvalDeltaCompatibilityPolicy::Unlimited)
15051 }
15052
15053 pub fn evaluate_cells_with_delta_policy(
15054 &mut self,
15055 targets: &[(&str, u32, u32)],
15056 policy: EvalDeltaCompatibilityPolicy,
15057 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15058 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
15059 engine.evaluate_cells_with_delta_unobserved(targets, policy)
15060 })
15061 }
15062
15063 fn evaluate_cells_with_delta_unobserved(
15064 &mut self,
15065 targets: &[(&str, u32, u32)],
15066 policy: EvalDeltaCompatibilityPolicy,
15067 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15068 self.observe_function_semantic_epoch()?;
15069 self.validate_deterministic_mode()?;
15070 if targets.is_empty() {
15071 return Ok((Vec::new(), EvalDelta::default()));
15072 }
15073 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15074 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
15075 let values = targets
15076 .iter()
15077 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
15078 .collect();
15079 Ok((values, collector.finish_with_policy(policy)?))
15080 }
15081
15082 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
15084 if targets.is_empty() {
15085 return Ok(EvalPlan {
15086 total_vertices_to_evaluate: 0,
15087 layers: Vec::new(),
15088 cycles_detected: 0,
15089 dirty_count: 0,
15090 volatile_count: 0,
15091 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15092 estimated_parallel_layers: 0,
15093 target_cells: Vec::new(),
15094 });
15095 }
15096 if self.config.defer_graph_building && self.has_staged_formulas() {
15097 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
15098 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
15099 ));
15100 }
15101
15102 let addresses: Vec<String> = targets
15104 .iter()
15105 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
15106 .collect();
15107
15108 let mut target_addrs = Vec::new();
15110 for (sheet, row, col) in targets {
15111 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
15112 let coord = Coord::from_excel(*row, *col, true, true);
15113 target_addrs.push(CellRef::new(sheet_id, coord));
15114 }
15115 }
15116
15117 let mut target_vertex_ids = Vec::new();
15119 for addr in &target_addrs {
15120 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
15121 target_vertex_ids.push(vertex_id);
15122 }
15123 }
15124
15125 if target_vertex_ids.is_empty() {
15126 return Ok(EvalPlan {
15127 total_vertices_to_evaluate: 0,
15128 layers: Vec::new(),
15129 cycles_detected: 0,
15130 dirty_count: 0,
15131 volatile_count: 0,
15132 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15133 estimated_parallel_layers: 0,
15134 target_cells: addresses,
15135 });
15136 }
15137
15138 let (precedents_to_eval, vdeps) = self.demand_subgraph(&target_vertex_ids)?;
15140
15141 if precedents_to_eval.is_empty() {
15142 return Ok(EvalPlan {
15143 total_vertices_to_evaluate: 0,
15144 layers: Vec::new(),
15145 cycles_detected: 0,
15146 dirty_count: 0,
15147 volatile_count: 0,
15148 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15149 estimated_parallel_layers: 0,
15150 target_cells: addresses,
15151 });
15152 }
15153
15154 let mut dirty_count = 0;
15156 let mut volatile_count = 0;
15157 for &vertex_id in &precedents_to_eval {
15158 if self.graph.is_dirty(vertex_id) {
15159 dirty_count += 1;
15160 }
15161 if self.graph.is_volatile(vertex_id) {
15162 volatile_count += 1;
15163 }
15164 }
15165
15166 let schedule = self.create_authority_schedule(&precedents_to_eval, &vdeps, None)?;
15168
15169 let mut layers = Vec::new();
15171 let mut estimated_parallel_layers = 0;
15172 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
15173
15174 for layer in &schedule.layers {
15175 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
15176 if parallel_eligible {
15177 estimated_parallel_layers += 1;
15178 }
15179
15180 let sample_cells: Vec<String> = layer
15182 .vertices
15183 .iter()
15184 .take(5)
15185 .filter_map(|&vertex_id| {
15186 self.graph
15187 .get_cell_ref_for_vertex(vertex_id)
15188 .map(|cell_ref| {
15189 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
15190 format!(
15191 "{}!{}{}",
15192 sheet_name,
15193 Self::col_to_letters(cell_ref.coord.col().saturating_add(1)),
15194 cell_ref.coord.row() + 1
15195 )
15196 })
15197 })
15198 .collect();
15199
15200 layers.push(LayerInfo {
15201 vertex_count: layer.vertices.len(),
15202 parallel_eligible,
15203 sample_cells,
15204 });
15205 }
15206
15207 Ok(EvalPlan {
15208 total_vertices_to_evaluate: precedents_to_eval.len(),
15209 layers,
15210 cycles_detected: schedule.cycles.len(),
15211 dirty_count,
15212 volatile_count,
15213 parallel_enabled,
15214 estimated_parallel_layers,
15215 target_cells: addresses,
15216 })
15217 }
15218 fn create_evaluation_schedule(
15220 &mut self,
15221 to_evaluate: &[VertexId],
15222 ) -> Result<EvaluationScheduleBuildOutput, ExcelError> {
15223 #[cfg(any(test, feature = "benchmark_internal"))]
15224 {
15225 self.recalc_reuse_probe.get_mut().unwrap().schedule_requests += 1;
15226 }
15227 #[cfg(any(test, feature = "legacy_oracle"))]
15230 self.graph.flush_pending_edge_deltas();
15231 self.graph.authority_sync();
15233 if self.can_use_static_schedule_cache(to_evaluate) {
15234 let revision = self.schedule_cache_authority_revision();
15236 let current = |e: &CachedScheduleEntry| {
15237 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15238 };
15239 if !self
15240 .cached_static_schedule
15241 .as_ref()
15242 .is_some_and(|c| current(c) && c.candidate_vertices.equals(to_evaluate))
15243 && let Some(i) = self.recent_schedules.iter().position(|e| {
15244 current(e)
15245 && e.candidate_vertices.len() == to_evaluate.len()
15246 && e.candidate_vertices.equals(to_evaluate)
15247 })
15248 {
15249 let hit = self.recent_schedules.remove(i);
15250 if let Some(previous) = self.cached_static_schedule.replace(hit) {
15251 self.retain_recent_schedule(previous);
15252 }
15253 }
15254 if let Some(cached) = self.cached_static_schedule.as_ref()
15255 && cached.topology_epoch == self.topology_epoch
15256 && cached.authority_revision == self.schedule_cache_authority_revision()
15257 && cached.candidate_vertices.equals(to_evaluate)
15258 {
15259 let meta = ScheduleBuildMeta {
15260 candidate_vertices: to_evaluate.len(),
15261 vdeps_vertices: 0,
15262 vdeps_edges: 0,
15263 builder_elapsed_ms: 0,
15264 used_virtual_schedule: false,
15265 schedule_cache_hit: true,
15266 schedule_cache_eligible: true,
15267 };
15268 #[cfg(any(test, feature = "benchmark_internal"))]
15269 {
15270 let mut probe = self.recalc_reuse_probe.lock().unwrap();
15271 probe.schedule_cache_hits += 1;
15272 probe.schedule_shared_handles += 1;
15273 }
15274 return Ok((
15275 EvaluationSchedule::Shared(Arc::clone(&cached.schedule)),
15276 FxHashMap::default(),
15277 meta,
15278 ));
15279 }
15280
15281 let (schedule, vdeps, mut meta) = match self.schedule_from_base(to_evaluate)? {
15282 Some(schedule) => (
15283 schedule,
15284 FxHashMap::default(),
15285 ScheduleBuildMeta {
15286 candidate_vertices: to_evaluate.len(),
15287 vdeps_vertices: 0,
15288 vdeps_edges: 0,
15289 builder_elapsed_ms: 0,
15290 used_virtual_schedule: false,
15291 schedule_cache_hit: false,
15292 schedule_cache_eligible: true,
15293 },
15294 ),
15295 None => self.create_evaluation_schedule_active(to_evaluate)?,
15296 };
15297 meta.schedule_cache_hit = false;
15298 meta.schedule_cache_eligible = true;
15299 #[cfg(any(test, feature = "benchmark_internal"))]
15300 {
15301 self.recalc_reuse_probe
15302 .get_mut()
15303 .unwrap()
15304 .schedule_cache_misses += 1;
15305 }
15306 let schedule = if vdeps.is_empty() {
15307 let mut schedule = schedule;
15310 schedule.units.shrink_to_fit();
15311 for layer in &mut schedule.layers {
15312 layer.vertices.shrink_to_fit();
15313 }
15314 schedule.layers.shrink_to_fit();
15315 for cycle in &mut schedule.cycles {
15316 cycle.shrink_to_fit();
15317 }
15318 schedule.cycles.shrink_to_fit();
15319 let schedule = Arc::new(schedule);
15320 #[cfg(any(test, feature = "benchmark_internal"))]
15321 {
15322 self.recalc_reuse_probe
15323 .get_mut()
15324 .unwrap()
15325 .schedule_shared_handles += 1;
15326 }
15327 let entry = CachedScheduleEntry {
15328 topology_epoch: self.topology_epoch,
15329 authority_revision: self.schedule_cache_authority_revision(),
15330 candidate_vertices: VertexIdRuns::from_slice(to_evaluate),
15331 schedule: Arc::clone(&schedule),
15332 };
15333 if let Some(previous) = self.cached_static_schedule.replace(entry) {
15334 self.retain_recent_schedule(previous);
15335 }
15336 EvaluationSchedule::Shared(schedule)
15337 } else {
15338 EvaluationSchedule::Owned(schedule)
15339 };
15340 return Ok((schedule, vdeps, meta));
15341 }
15342
15343 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_active(to_evaluate)?;
15344 meta.schedule_cache_hit = false;
15345 meta.schedule_cache_eligible = false;
15346 #[cfg(any(test, feature = "benchmark_internal"))]
15347 {
15348 self.recalc_reuse_probe
15349 .get_mut()
15350 .unwrap()
15351 .schedule_cache_ineligible += 1;
15352 }
15353 Ok((EvaluationSchedule::Owned(schedule), vdeps, meta))
15354 }
15355
15356 fn schedule_from_base(
15362 &mut self,
15363 to_evaluate: &[VertexId],
15364 ) -> Result<Option<crate::engine::scheduler::Schedule>, ExcelError> {
15365 let revision = self.schedule_cache_authority_revision();
15366 let current = |e: &&CachedScheduleEntry| {
15367 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15368 };
15369 let Some(base) = [
15372 self.base_schedule.as_ref(),
15373 self.cached_static_schedule.as_ref(),
15374 ]
15375 .into_iter()
15376 .flatten()
15377 .filter(current)
15378 .max_by_key(|e| e.candidate_vertices.len()) else {
15379 return Ok(None);
15380 };
15381 let base_len = base.candidate_vertices.len();
15382 if base_len <= BASE_SCHEDULE_MIN_VERTICES
15383 || to_evaluate.len() < BASE_SCHEDULE_MIN_REQUEST
15384 || to_evaluate.len() > base_len
15385 || base_len > to_evaluate.len().saturating_mul(BASE_SCHEDULE_RATIO)
15386 {
15387 return Ok(None);
15388 }
15389 let mut keep = crate::engine::idset::DenseIdSet::default();
15390 keep.extend(to_evaluate.iter().copied());
15391 let Some((schedule, kept)) = base.schedule.restrict(&keep) else {
15392 return Ok(None);
15393 };
15394 if kept != keep.len() {
15396 return Ok(None);
15397 }
15398 if let Some(ledger) = self.active_resource_ledger.as_mut() {
15399 let layers: usize = schedule
15400 .layers
15401 .iter()
15402 .map(|l| {
15403 l.vertices.capacity() * std::mem::size_of::<VertexId>()
15404 + l.runs.capacity()
15405 * std::mem::size_of::<crate::engine::scheduler::LayerRun>()
15406 })
15407 .sum();
15408 let bytes = (keep.heap_bytes()
15409 + layers
15410 + schedule.layers.capacity()
15411 * std::mem::size_of::<crate::engine::scheduler::Layer>()
15412 + schedule.units.capacity()
15413 * std::mem::size_of::<crate::engine::scheduler::ScheduleUnit>()
15414 + schedule
15415 .cycles
15416 .iter()
15417 .map(|c| c.capacity() * 4)
15418 .sum::<usize>()) as u64;
15419 ledger
15420 .reserve_schedule_discovery(bytes)
15421 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15422 ledger
15423 .release_scratch(bytes)
15424 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15425 }
15426 #[cfg(debug_assertions)]
15427 self.debug_check_restricted_schedule(to_evaluate, &schedule);
15428 #[cfg(any(test, feature = "benchmark_internal"))]
15429 {
15430 self.recalc_reuse_probe
15431 .get_mut()
15432 .unwrap()
15433 .schedule_base_restrictions += 1;
15434 }
15435 Ok(Some(schedule))
15436 }
15437
15438 #[cfg(debug_assertions)]
15442 fn debug_check_restricted_schedule(
15443 &self,
15444 to_evaluate: &[VertexId],
15445 schedule: &crate::engine::scheduler::Schedule,
15446 ) {
15447 let mut position: FxHashMap<VertexId, usize> = FxHashMap::default();
15448 for (u, unit) in schedule.units.iter().enumerate() {
15449 let vertices: &[VertexId] = match *unit {
15450 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
15451 &schedule.unit_layer(i).vertices
15452 }
15453 crate::engine::scheduler::ScheduleUnit::Cycle(i) => schedule.unit_cycle(i),
15454 };
15455 for &v in vertices {
15456 assert!(
15457 position.insert(v, u).is_none(),
15458 "vertex twice in a restricted schedule"
15459 );
15460 }
15461 }
15462 assert_eq!(position.len(), to_evaluate.len());
15463 if to_evaluate.len() > 512 {
15464 return;
15465 }
15466 let Ok(store) = self.graph.authority_plan_store() else {
15469 return;
15470 };
15471 let cells: Vec<(VertexId, (u16, u32, u32))> = to_evaluate
15472 .iter()
15473 .filter_map(|&v| self.graph.authority_cell_of_vertex(v).map(|c| (v, c)))
15474 .collect();
15475 for &(v, (sheet, row, col)) in &cells {
15476 let Some(owner) = store.owner_at((sheet, row, col)) else {
15477 continue;
15478 };
15479 let Ok(refined) = store.refine_owner_column(owner, col, row, row, None) else {
15480 continue;
15481 };
15482 for piece in &refined.pieces {
15483 for edge in &refined.edges[piece.edge_start..piece.edge_end] {
15484 let Some(image) = edge.proj.forward(&piece.domain) else {
15485 continue;
15486 };
15487 for &(d, (s2, r2, c2)) in &cells {
15488 if d == v
15489 || edge.proj.sheet != s2
15490 || !(image.r0 <= r2
15491 && r2 <= image.r1
15492 && image.c0 <= c2
15493 && c2 <= image.c1)
15494 {
15495 continue;
15496 }
15497 let (pd, pv) = (position[&d], position[&v]);
15498 let same_ok = pd == pv
15499 && match schedule.units[pv] {
15500 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
15501 schedule.unit_layer(i).sequential
15502 }
15503 crate::engine::scheduler::ScheduleUnit::Cycle(_) => true,
15504 };
15505 assert!(
15506 pd < pv || same_ok,
15507 "restricted schedule orders {v:?} before its precedent {d:?}"
15508 );
15509 }
15510 }
15511 }
15512 }
15513 }
15514
15515 fn maybe_compress_formulas(&mut self) {
15519 if !self.config.formula_compression {
15520 return;
15521 }
15522 let builds = self.graph.authority_host().builds;
15523 if self.compressed_at_build == Some(builds) {
15524 return;
15525 }
15526 self.compressed_at_build = Some(builds);
15527 if self.has_staged_formulas() {
15528 self.graph.virtualize_family_members();
15531 return;
15532 }
15533 let pool = self.thread_pool.clone();
15534 let (_, garbage) = self.graph.compress_family_formulas(pool.as_deref());
15535 self.graph.virtualize_family_members();
15536 match pool {
15540 Some(pool) if garbage.len() >= 1024 => pool.spawn(move || drop(garbage)),
15541 _ => drop(garbage),
15542 }
15543 }
15544
15545 fn create_evaluation_schedule_active(
15546 &mut self,
15547 to_evaluate: &[VertexId],
15548 ) -> Result<ScheduleBuildOutput, ExcelError> {
15549 self.graph.authority_sync();
15550 self.maybe_compress_formulas();
15551 let mut ledger = self.active_resource_ledger.take();
15552 let result = self.create_evaluation_schedule_uncached(to_evaluate, ledger.as_mut());
15553 self.active_resource_ledger = ledger;
15554 result
15555 }
15556
15557 fn create_evaluation_schedule_uncached(
15558 &self,
15559 to_evaluate: &[VertexId],
15560 #[allow(unused_variables)] ledger: Option<&mut ResourceLedger>,
15561 ) -> Result<ScheduleBuildOutput, ExcelError> {
15562 #[cfg(any(test, feature = "benchmark_internal"))]
15563 {
15564 self.recalc_reuse_probe.lock().unwrap().schedule_builds += 1;
15565 }
15566 let builder = VirtualDepBuilder::new(self);
15567 #[allow(unused_mut)]
15568 let (mut vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
15569 if self.config.enable_virtual_dep_telemetry {
15570 let build_started = crate::instant::FzInstant::now();
15571 let (vdeps, augmented) = builder.build(to_evaluate);
15572 let builder_elapsed_ms = build_started.elapsed().as_millis();
15573 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
15574 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
15575 } else {
15576 let (vdeps, augmented) = builder.build(to_evaluate);
15577 (vdeps, augmented, 0, 0)
15578 };
15579
15580 {
15582 self.freshness_merge_hints(to_evaluate, &mut vdeps);
15583 self.freshness_extent_hints(to_evaluate, &mut vdeps);
15584 }
15585 let mut final_evaluate = to_evaluate.to_vec();
15586 if !augmented.is_empty() {
15587 final_evaluate.extend(augmented);
15588 final_evaluate.sort_unstable();
15589 final_evaluate.dedup();
15590 }
15591
15592 let use_virtual = !vdeps.is_empty();
15593
15594 let schedule = self.create_authority_schedule(&final_evaluate, &vdeps, ledger)?;
15595
15596 let meta = ScheduleBuildMeta {
15597 candidate_vertices: to_evaluate.len(),
15598 vdeps_vertices: vdeps.len(),
15599 vdeps_edges,
15600 builder_elapsed_ms,
15601 used_virtual_schedule: use_virtual,
15602 schedule_cache_hit: false,
15603 schedule_cache_eligible: false,
15604 };
15605
15606 Ok((schedule, vdeps, meta))
15607 }
15608
15609 fn create_authority_schedule(
15610 &self,
15611 candidates: &[VertexId],
15612 vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
15613 mut ledger: Option<&mut ResourceLedger>,
15614 ) -> Result<crate::engine::scheduler::Schedule, ExcelError> {
15615 use crate::engine::authority::{
15616 geom::{Cover, Rect},
15617 plan_schedule, planner,
15618 proj::{AxisMap, RefProj},
15619 store::{EdgeKey, Tag},
15620 };
15621 let failure = |message: String| {
15622 ExcelError::new(ExcelErrorKind::Error)
15623 .with_message(format!("unified_authority planner: {message}"))
15624 };
15625 self.cancellation_checkpoint("Evaluation cancelled before authority planning")?;
15626 let store = self
15627 .graph
15628 .authority_plan_store()
15629 .map_err(Self::authority_excel_error)?;
15630 if let [only] = candidates
15633 && vdeps.is_empty()
15634 && self.graph.authority_host().observed(*only).is_none()
15635 && let Some(cell) = self.graph.authority_cell_of_vertex(*only)
15636 && cell.0 != crate::engine::authority::geom::SYMBOL_SHEET
15637 && let Some(single) = planner::plan_single(store, cell)
15638 {
15639 #[cfg(debug_assertions)]
15640 {
15641 let mut cover = Cover::new();
15642 cover.insert_rect(cell.0, &Rect::new(cell.1, cell.2, cell.1, cell.2));
15643 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
15644 .expect("general plan of one cell");
15645 assert_eq!(
15646 general.cells.as_slice(),
15647 &[single],
15648 "single-cell plan differs from the planner at {cell:?}"
15649 );
15650 }
15651 let adapted = plan_schedule::schedule(
15652 &[single],
15653 0,
15654 None,
15655 |cell| {
15656 self.graph
15657 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15658 .ok_or_else(|| failure("missing executor identity".to_owned()))
15659 },
15660 |_work| Ok(()),
15661 )
15662 .map_err(|error| match error {
15663 plan_schedule::ScheduleError::Runtime(error) => error,
15664 other => failure(format!("{other:?}")),
15665 })?;
15666 if let Some(ledger) = ledger {
15667 ledger
15668 .reserve_schedule_discovery(adapted.peak_heap_bytes)
15669 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15670 ledger
15671 .release_scratch(adapted.peak_heap_bytes)
15672 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15673 }
15674 return Ok(adapted.schedule);
15675 }
15676 if (2..=planner::SMALL_PLAN_MAX).contains(&candidates.len())
15679 && vdeps.is_empty()
15680 && candidates
15681 .iter()
15682 .all(|&v| self.graph.authority_host().observed(v).is_none())
15683 {
15684 let cells: Option<Vec<(u16, u32, u32)>> = candidates
15685 .iter()
15686 .map(|&v| self.graph.authority_cell_of_vertex(v))
15687 .collect();
15688 if let Some(small) = cells.as_deref().and_then(|c| planner::plan_small(store, c)) {
15689 #[cfg(debug_assertions)]
15690 {
15691 let mut cover = Cover::new();
15692 for c in cells.as_deref().unwrap_or_default() {
15693 cover.insert_rect(c.0, &Rect::new(c.1, c.2, c.1, c.2));
15694 }
15695 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
15696 .expect("general plan of a small request");
15697 let key = |c: &planner::OrderedCell| (c.sheet, c.row, c.col, c.id, c.owner);
15698 let mut a: Vec<_> = general.cells.iter().map(key).collect();
15699 let mut b: Vec<_> = small.iter().map(key).collect();
15700 a.sort_unstable();
15701 b.sort_unstable();
15702 assert_eq!(a, b, "small plan cells differ from the planner");
15703 assert!(
15704 general.cells.iter().all(|c| c.cycle.is_none()),
15705 "small plan of a cyclic request"
15706 );
15707 }
15708 let adapted = plan_schedule::schedule(
15709 &small,
15710 0,
15711 None,
15712 |cell| {
15713 self.graph
15714 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15715 .ok_or_else(|| failure("missing executor identity".to_owned()))
15716 },
15717 |_work| Ok(()),
15718 )
15719 .map_err(|error| match error {
15720 plan_schedule::ScheduleError::Runtime(error) => error,
15721 other => failure(format!("{other:?}")),
15722 })?;
15723 if let Some(ledger) = ledger {
15724 ledger
15725 .reserve_schedule_discovery(adapted.peak_heap_bytes)
15726 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15727 ledger
15728 .release_scratch(adapted.peak_heap_bytes)
15729 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15730 }
15731 return Ok(adapted.schedule);
15732 }
15733 }
15734 let mut cover = Cover::new();
15739 {
15740 let cell_of = |&id: &VertexId| {
15741 self.graph
15742 .authority_cell_of_vertex(id)
15743 .map(|(sheet, row, col)| (sheet, col, row))
15744 };
15745 let cells: Vec<(u16, u32, u32)> = match self.thread_pool.as_deref() {
15748 Some(pool) if candidates.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
15749 use rayon::prelude::*;
15750 pool.install(|| {
15751 let mut cells: Vec<_> = candidates.par_iter().filter_map(cell_of).collect();
15752 cells.par_sort_unstable();
15753 cells
15754 })
15755 }
15756 _ => {
15757 let mut cells: Vec<_> = candidates.iter().filter_map(cell_of).collect();
15758 cells.sort_unstable();
15759 cells
15760 }
15761 };
15762 let mut i = 0;
15763 while i < cells.len() {
15764 let (sheet, col, r0) = cells[i];
15765 let mut r1 = r0;
15766 let mut j = i + 1;
15767 while j < cells.len() && cells[j].0 == sheet && cells[j].1 == col {
15768 if cells[j].2 > r1 + 1 {
15769 break;
15770 }
15771 r1 = r1.max(cells[j].2);
15772 j += 1;
15773 }
15774 cover.insert_rect(sheet, &Rect::new(r0, col, r1, col));
15775 i = j;
15776 }
15777 }
15778 let mut hints = Vec::new();
15779 for (&reader, deps) in vdeps {
15780 let Some(reader) = self.graph.authority_cell_of_vertex(reader) else {
15781 continue;
15782 };
15783 for &dependency in deps {
15784 let Some(dep) = self.graph.authority_cell_of_vertex(dependency) else {
15785 continue;
15786 };
15787 hints.push(planner::PlanHint {
15788 reader: (reader.0, reader.2, reader.1),
15789 edge: EdgeKey {
15790 dep_sheet: reader.0,
15791 tag: Tag::X,
15792 lk: u32::MAX,
15793 proj: RefProj {
15794 sheet: dep.0,
15795 rows: AxisMap::fixed(dep.1, dep.1),
15796 cols: AxisMap::fixed(dep.2, dep.2),
15797 },
15798 },
15799 });
15800 }
15801 }
15802 let host = self.graph.authority_host();
15804 for &id in candidates.iter().filter(|_| host.has_observed()) {
15805 let Some(reads) = host.observed(id) else {
15806 continue;
15807 };
15808 let Some(reader) = self.graph.authority_cell_of_vertex(id) else {
15809 continue;
15810 };
15811 for &(sheet, r0, c0, r1, c1) in reads {
15812 hints.push(planner::PlanHint {
15813 reader: (reader.0, reader.2, reader.1),
15814 edge: EdgeKey {
15815 dep_sheet: reader.0,
15816 tag: Tag::X,
15817 lk: u32::MAX,
15818 proj: RefProj {
15819 sheet,
15820 rows: AxisMap::fixed(r0, r1),
15821 cols: AxisMap::fixed(c0, c1),
15822 },
15823 },
15824 });
15825 }
15826 }
15827 hints.sort_unstable_by_key(|hint| hint.reader);
15828 let checkpoint = ledger
15829 .as_ref()
15830 .map_or(0, |ledger| ledger.scratch_checkpoint());
15831 let scratch_limit = ledger
15832 .as_ref()
15833 .and_then(|ledger| ledger.schedule_discovery_limit())
15834 .map(|limit| limit.saturating_sub(checkpoint));
15835 let ordered = planner::plan_with_hints(store, &cover, &hints, scratch_limit, None, None)
15836 .map_err(|error| failure(format!("{error:?}")))?;
15837 let adapted = plan_schedule::schedule(
15841 &ordered.cells,
15842 ordered.heap_bytes(),
15843 scratch_limit,
15844 |cell| {
15845 self.graph
15846 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
15847 .ok_or_else(|| failure("missing executor identity".to_owned()))
15848 },
15849 |_work| {
15850 self.cancellation_checkpoint("Evaluation cancelled during authority planning")?;
15851 if let Some(ledger) = ledger.as_deref_mut() {
15852 ledger
15853 .checkpoint_deadline()
15854 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15855 }
15856 Ok(())
15857 },
15858 )
15859 .map_err(|error| match error {
15860 plan_schedule::ScheduleError::Runtime(error) => error,
15861 other => failure(format!("{other:?}")),
15862 })?;
15863 if let Some(ledger) = ledger {
15864 let peak = ordered.peak_heap_bytes.max(adapted.peak_heap_bytes);
15865 ledger
15866 .reserve_schedule_discovery(peak)
15867 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15868 ledger
15869 .release_scratch(peak)
15870 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15871 }
15872 match self.thread_pool.as_deref() {
15874 Some(pool) if ordered.cells.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
15875 pool.spawn(move || drop(ordered))
15876 }
15877 _ => drop(ordered),
15878 }
15879 Ok(adapted.schedule)
15880 }
15881
15882 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
15888 {
15889 let host = self.graph.authority_host();
15893 !to_evaluate.is_empty()
15894 && !self.freshness_has_hints()
15895 && to_evaluate
15896 .iter()
15897 .all(|&v| !self.graph.is_dynamic(v) || host.observed(v).is_some())
15898 }
15899 }
15900
15901 fn schedule_cache_authority_revision(&self) -> (u64, u64) {
15902 {
15903 let host = self.graph.authority_host();
15906 (host.revision(), host.rev_dyn())
15907 }
15908 }
15909
15910 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
15911 VirtualDepTelemetry {
15912 fallback_mode_activations: self.virtual_dep_fallback_activations,
15913 ..VirtualDepTelemetry::default()
15914 }
15915 }
15916
15917 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
15918 telemetry.candidate_vertices_total += meta.candidate_vertices;
15919 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
15920 telemetry.vdeps_edges_total += meta.vdeps_edges;
15921 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
15922 if meta.schedule_cache_eligible {
15923 if meta.schedule_cache_hit {
15924 telemetry.schedule_cache_hits += 1;
15925 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
15926 } else {
15927 telemetry.schedule_cache_misses += 1;
15928 }
15929 }
15930 if meta.used_virtual_schedule {
15931 telemetry.schedule_virtual_passes += 1;
15932 } else {
15933 telemetry.schedule_static_passes += 1;
15934 }
15935 }
15936
15937 fn finish_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
15942 self.finish_pass_dirty_scoped(to_evaluate, changed, true)
15943 }
15944
15945 fn finish_target_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
15948 self.finish_pass_dirty_scoped(to_evaluate, changed, false)
15949 }
15950
15951 fn finish_pass_dirty_scoped(
15952 &mut self,
15953 to_evaluate: &[VertexId],
15954 changed: &[VertexId],
15955 #[allow(unused_variables)] whole_workbook: bool,
15956 ) -> bool {
15957 self.freshness_finish_pass(to_evaluate, changed, whole_workbook)
15958 }
15959
15960 fn begin_pass(
15962 &mut self,
15963 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
15964 ) {
15965 self.freshness_begin_pass(schedule);
15966 }
15967
15968 fn stop_after_unit(
15970 &mut self,
15971 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
15972 #[allow(unused_variables)] index: usize,
15973 ) -> bool {
15974 self.freshness_stop_after_unit(schedule, index)
15975 }
15976
15977 fn changed_virtual_dep_vertices(
15978 &mut self,
15979 to_evaluate: &[VertexId],
15980 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
15981 ) -> Vec<VertexId> {
15982 #[cfg(test)]
15983 if self.force_virtual_dep_changes_remaining_for_test > 0
15984 && let Some(vertex) = to_evaluate.first().copied()
15985 {
15986 self.force_virtual_dep_changes_remaining_for_test -= 1;
15987 return vec![vertex];
15988 }
15989 if self.freshness_armed() {
15992 return Vec::new();
15993 }
15994 if !to_evaluate
15995 .iter()
15996 .copied()
15997 .any(|v| self.graph.is_dynamic(v))
15998 {
15999 return Vec::new();
16000 }
16001
16002 let builder = VirtualDepBuilder::new(self);
16003 let (new_vdeps, _) = builder.build(to_evaluate);
16004
16005 let mut candidates = FxHashSet::default();
16006 candidates.extend(old_vdeps.keys().copied());
16007 candidates.extend(new_vdeps.keys().copied());
16008
16009 let mut changed = Vec::new();
16010 for v in candidates {
16011 if old_vdeps.get(&v) != new_vdeps.get(&v) {
16012 changed.push(v);
16013 }
16014 }
16015 changed
16016 }
16017
16018 #[allow(clippy::type_complexity)]
16024 fn demand_subgraph(
16025 &self,
16026 targets: &[VertexId],
16027 ) -> Result<
16028 (
16029 Vec<VertexId>,
16030 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
16031 ),
16032 ExcelError,
16033 > {
16034 self.authority_demand_subgraph(targets)
16035 }
16036
16037 #[allow(clippy::type_complexity)]
16043 fn authority_demand_subgraph(
16044 &self,
16045 targets: &[VertexId],
16046 ) -> Result<
16047 (
16048 Vec<VertexId>,
16049 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
16050 ),
16051 ExcelError,
16052 > {
16053 use crate::engine::authority::geom::{Cell, Rect, SYMBOL_SHEET};
16054 use crate::engine::authority::store::TagFilter;
16055 use rustc_hash::{FxHashMap, FxHashSet};
16056 let store = self
16057 .graph
16058 .authority_plan_store()
16059 .map_err(Self::authority_excel_error)?;
16060 let ids = store.ids();
16061 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
16062 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default();
16063 let mut visited: FxHashSet<Cell> = FxHashSet::default();
16064 let mut stack: Vec<(Cell, u32)> = Vec::new();
16067 let push_formulas = |stack: &mut Vec<(Cell, u32)>,
16069 visited: &FxHashSet<Cell>,
16070 sheet: u16,
16071 rect: Rect| {
16072 for col in rect.c0..=rect.c1 {
16073 ids.visit_runs_in(sheet, col, rect.r0, rect.r1, &mut |h| {
16074 let run = ids.run(h);
16075 let r0 = run.row_start.max(rect.r0);
16076 let r1 = (run.row_start + run.len - 1).min(rect.r1);
16077 for row in r0..=r1 {
16078 if !visited.contains(&(sheet, row, col)) {
16079 stack.push(((sheet, row, col), run.first_id + (row - run.row_start)));
16080 }
16081 }
16082 });
16083 }
16084 };
16085 for &v in targets {
16086 if let Some(table) = self.graph.table_by_vertex(v) {
16087 let (s, e) = (table.range.start, table.range.end);
16091 push_formulas(
16092 &mut stack,
16093 &visited,
16094 s.sheet_id,
16095 Rect::new(s.coord.row(), s.coord.col(), e.coord.row(), e.coord.col()),
16096 );
16097 } else if let Some(cell) = self.graph.authority_cell_of_vertex(v) {
16098 stack.push((cell, crate::engine::authority::identity::NO_VID));
16099 }
16100 }
16101 let mut hits = Vec::new();
16102 #[cfg(any(test, feature = "benchmark_internal"))]
16103 let (mut probe_vertices, mut probe_clean_formulas, mut probe_edges, mut probe_dynamic) =
16104 (0, 0, 0, 0);
16105 while let Some((cell, id)) = stack.pop() {
16106 if !visited.insert(cell) {
16107 continue;
16108 }
16109 let Some(v) = self.graph.authority_vertex_of_formula(id, cell) else {
16110 continue;
16111 };
16112 if !self.graph.vertex_exists(v) {
16113 continue;
16114 }
16115 #[cfg(any(test, feature = "benchmark_internal"))]
16116 {
16117 probe_vertices += 1;
16118 }
16119 match self.graph.get_vertex_kind(v) {
16120 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
16121 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
16122 to_evaluate.insert(v);
16123 } else {
16124 #[cfg(any(test, feature = "benchmark_internal"))]
16125 {
16126 probe_clean_formulas += 1;
16127 }
16128 }
16129 }
16130 VertexKind::NamedScalar | VertexKind::NamedArray => {
16131 to_evaluate.insert(v);
16132 }
16133 _ => {}
16134 }
16135 hits.clear();
16136 store.direct_precedents(cell, TagFilter::All, &mut hits);
16137 #[cfg(any(test, feature = "benchmark_internal"))]
16138 {
16139 probe_edges += hits.len();
16140 }
16141 for &(_, sheet, rect) in &hits {
16142 if sheet != SYMBOL_SHEET {
16145 for anchor in self
16146 .graph
16147 .spill_anchors_in_region(sheet, rect.r0, rect.c0, rect.r1, rect.c1)
16148 {
16149 if anchor != v && self.graph.is_dirty(anchor) {
16150 vdeps.entry(v).or_default().push(anchor);
16151 if let Some(c) = self.graph.authority_cell_of_vertex(anchor) {
16152 stack.push((c, crate::engine::authority::identity::NO_VID));
16153 }
16154 }
16155 }
16156 }
16157 if sheet == SYMBOL_SHEET {
16158 stack.extend((rect.r0..=rect.r1).map(|slot| {
16159 (
16160 (SYMBOL_SHEET, slot, 0),
16161 crate::engine::authority::identity::NO_VID,
16162 )
16163 }));
16164 continue;
16165 }
16166 push_formulas(&mut stack, &visited, sheet, rect);
16167 }
16168 if self.graph.is_dynamic(v) {
16169 #[cfg(any(test, feature = "benchmark_internal"))]
16170 {
16171 probe_dynamic += 1;
16172 }
16173 if let Some(reads) = self.graph.authority_host().observed(v) {
16175 for &(sheet, r0, c0, r1, c1) in reads {
16176 push_formulas(&mut stack, &visited, sheet, Rect::new(r0, c0, r1, c1));
16177 }
16178 }
16179 let (vdeps_map, _) = VirtualDepBuilder::new(self).build(&[v]);
16180 let hinted = self.freshness_hints(v).unwrap_or(&[]);
16183 if let Some(deps) = vdeps_map
16184 .get(&v)
16185 .map(|deps| deps.iter().chain(hinted))
16186 .or(Some([].iter().chain(hinted)))
16187 {
16188 for &u in deps {
16189 vdeps.entry(v).or_default().push(u);
16190 if let Some(c) = self.graph.authority_cell_of_vertex(u) {
16191 stack.push((c, crate::engine::authority::identity::NO_VID));
16192 }
16193 }
16194 }
16195 }
16196 }
16197 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
16198 result.sort_unstable();
16199 for deps in vdeps.values_mut() {
16200 deps.sort_unstable();
16201 deps.dedup();
16202 }
16203 #[cfg(any(test, feature = "benchmark_internal"))]
16204 {
16205 let mut probe = self.recalc_reuse_probe.lock().unwrap();
16206 probe.demand_builds += 1;
16207 probe.demand_vertices += probe_vertices;
16208 probe.demand_clean_formulas += probe_clean_formulas;
16209 probe.demand_explicit_edges += probe_edges;
16210 probe.demand_virtual_builder_calls += probe_dynamic;
16211 }
16212 Ok((result, vdeps))
16213 }
16214
16215 fn col_to_letters(col: u32) -> String {
16217 col_letters_from_1based(col).expect("column index must be >= 1")
16218 }
16219
16220 pub fn evaluate_all_cancellable(
16222 &mut self,
16223 cancel: crate::engine::CancelToken,
16224 ) -> Result<EvalResult, ExcelError> {
16225 self.observe_evaluation_resource_request(EvaluationRequestKind::FullCancellable, |engine| {
16226 engine.observe_function_semantic_epoch()?;
16227 engine.active_cancel_flag = Some(cancel.clone());
16228 let res = engine.evaluate_all_cancellable_impl(cancel.as_flag());
16229 engine.active_cancel_flag = None;
16230 res
16231 })
16232 }
16233
16234 fn evaluate_all_cancellable_impl(
16235 &mut self,
16236 cancel_flag: &AtomicBool,
16237 ) -> Result<EvalResult, ExcelError> {
16238 let _source_cache = self.source_cache_session();
16239 self.validate_deterministic_mode()?;
16240 if self.config.defer_graph_building {
16241 self.build_graph_all()?;
16242 }
16243 if cancel_flag.load(Ordering::Relaxed) {
16244 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16245 .with_message("Evaluation cancelled before scheduling".to_string()));
16246 }
16247 self.require_unified_authority()?;
16248 self.begin_evaluation_request();
16249 self.reset_virtual_dep_telemetry_if_disabled();
16250 let start = crate::instant::FzInstant::now();
16251 let mut computed_vertices = 0;
16252 let mut cycle_errors = 0;
16253
16254 let mut replan_iterations = 0;
16255 const MAX_REPLAN: usize = 5;
16256 let mut telemetry = self
16257 .config
16258 .enable_virtual_dep_telemetry
16259 .then(|| self.start_virtual_dep_telemetry());
16260
16261 loop {
16262 if cancel_flag.load(Ordering::Relaxed) {
16263 if let Some(mut t) = telemetry {
16264 t.bailout_reason = Some("cancelled");
16265 t.replan_iterations = replan_iterations;
16266 self.last_virtual_dep_telemetry = t;
16267 }
16268 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16269 .with_message("Evaluation cancelled before scheduling".to_string()));
16270 }
16271
16272 let to_evaluate = self.graph.get_evaluation_vertices();
16273 if to_evaluate.is_empty() {
16274 if let Some(t) = telemetry.as_mut()
16275 && t.bailout_reason.is_none()
16276 {
16277 t.bailout_reason = Some("no_work");
16278 }
16279 break;
16280 }
16281
16282 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
16283 if let Some(t) = telemetry.as_mut() {
16284 Self::accumulate_schedule_meta(t, &meta);
16285 }
16286
16287 self.begin_pass(&schedule);
16290 for (unit_index, &unit) in schedule.units.iter().enumerate() {
16291 match unit {
16292 ScheduleUnit::Cycle(i) => {
16293 if cancel_flag.load(Ordering::Relaxed) {
16295 if let Some(mut t) = telemetry {
16296 t.bailout_reason = Some("cancelled");
16297 t.replan_iterations = replan_iterations;
16298 self.last_virtual_dep_telemetry = t;
16299 }
16300 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
16301 "Evaluation cancelled during cycle handling".to_string(),
16302 ));
16303 }
16304
16305 if self.handle_cycle_unit(
16306 schedule.unit_cycle(i),
16307 None,
16308 None,
16309 Some(cancel_flag),
16310 )? > 0
16311 {
16312 cycle_errors += 1;
16313 }
16314 }
16315 ScheduleUnit::Layer(i) => {
16316 let layer = schedule.unit_layer(i);
16317 if cancel_flag.load(Ordering::Relaxed) {
16319 if let Some(mut t) = telemetry {
16320 t.bailout_reason = Some("cancelled");
16321 t.replan_iterations = replan_iterations;
16322 self.last_virtual_dep_telemetry = t;
16323 }
16324 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16325 .with_message("Evaluation cancelled between layers".to_string()));
16326 }
16327
16328 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
16330 computed_vertices +=
16331 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
16332 } else {
16333 computed_vertices +=
16334 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
16335 }
16336 }
16337 }
16338 if self.stop_after_unit(&schedule, unit_index) {
16339 break;
16340 }
16341 }
16342
16343 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
16344 if let Some(t) = telemetry.as_mut() {
16345 t.changed_vdeps_total += changed_vertices.len();
16346 }
16347 self.resource_checkpoint(0)?;
16348 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
16349 if let Some(t) = telemetry.as_mut() {
16350 t.bailout_reason = Some("converged");
16351 }
16352 break;
16353 }
16354 if replan_iterations >= MAX_REPLAN {
16355 if let Some(mut t) = telemetry.take() {
16356 t.bailout_reason = Some("max_replan");
16357 t.replan_iterations = replan_iterations;
16358 self.last_virtual_dep_telemetry = t;
16359 }
16360 return Err(
16361 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
16362 );
16363 }
16364 replan_iterations += 1;
16365 }
16366
16367 if let Some(mut t) = telemetry {
16368 t.replan_iterations = replan_iterations;
16369 self.last_virtual_dep_telemetry = t;
16370 }
16371
16372 self.redirty_for_next_recalc();
16374 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
16375
16376 Ok(EvalResult {
16377 computed_vertices,
16378 cycle_errors,
16379 elapsed: start.elapsed(),
16380 })
16381 }
16382
16383 pub fn evaluate_until_cancellable(
16385 &mut self,
16386 targets: &[&str],
16387 cancel: crate::engine::CancelToken,
16388 ) -> Result<EvalResult, ExcelError> {
16389 self.observe_evaluation_resource_request(
16390 EvaluationRequestKind::TargetedCancellable,
16391 |engine| {
16392 engine.observe_function_semantic_epoch()?;
16393 engine.active_cancel_flag = Some(cancel.clone());
16394 let res = engine.evaluate_until_cancellable_impl(targets, cancel.as_flag());
16395 engine.active_cancel_flag = None;
16396 res
16397 },
16398 )
16399 }
16400
16401 fn evaluate_until_cancellable_impl(
16402 &mut self,
16403 targets: &[&str],
16404 cancel_flag: &AtomicBool,
16405 ) -> Result<EvalResult, ExcelError> {
16406 if cancel_flag.load(Ordering::Relaxed) {
16407 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16408 .with_message("Evaluation cancelled before target preparation"));
16409 }
16410 let mut typed_targets = Vec::with_capacity(targets.len());
16411 for target in targets {
16412 let (sheet, row, col) = self.parse_a1_notation(target)?;
16413 self.graph.sheet_id_mut(&sheet);
16414 typed_targets.push(crate::engine::EvaluationTarget::Cell { sheet, row, col });
16415 }
16416 self.evaluate_mixed_targets(&typed_targets, None)
16417 }
16418
16419 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
16420 let mut quoted = false;
16421 let mut separator = None;
16422 let bytes = address.as_bytes();
16423 let mut index = 0usize;
16424 while index < bytes.len() {
16425 match bytes[index] {
16426 b'\'' => {
16427 if quoted && bytes.get(index + 1) == Some(&b'\'') {
16428 index = index.saturating_add(1);
16429 } else {
16430 quoted = !quoted;
16431 }
16432 }
16433 b'!' if !quoted => separator = Some(index),
16434 _ => {}
16435 }
16436 index = index.saturating_add(1);
16437 }
16438 if quoted {
16439 return Err(ExcelError::new(ExcelErrorKind::Ref)
16440 .with_message(format!("Invalid quoted sheet reference `{address}`")));
16441 }
16442 let (sheet, cell_part) = match separator {
16443 Some(separator) => {
16444 let raw_sheet = &address[..separator];
16445 let sheet = if raw_sheet.starts_with('\'') && raw_sheet.ends_with('\'') {
16446 raw_sheet[1..raw_sheet.len().saturating_sub(1)].replace("''", "'")
16447 } else {
16448 raw_sheet.to_string()
16449 };
16450 (sheet, &address[separator + 1..])
16451 }
16452 None => (self.default_sheet_name().to_string(), address),
16453 };
16454
16455 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
16456 ExcelError::new(ExcelErrorKind::Ref)
16457 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
16458 })?;
16459
16460 Ok((sheet, row, col))
16461 }
16462
16463 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
16465 use formualizer_parse::parser::ASTNodeType;
16466 match &ast.node_type {
16467 ASTNodeType::Function { name, args, .. } => {
16468 if let Some(func) = self
16469 .get_function("", name)
16470 .or_else(|| crate::function_registry::get("", name))
16471 && func.caps().contains(crate::function::FnCaps::VOLATILE)
16472 {
16473 return true;
16474 }
16475 args.iter()
16476 .any(|arg| self.is_ast_volatile_with_provider(arg))
16477 }
16478 ASTNodeType::BinaryOp { left, right, .. } => {
16479 self.is_ast_volatile_with_provider(left)
16480 || self.is_ast_volatile_with_provider(right)
16481 }
16482 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
16483 ASTNodeType::Array(rows) => rows.iter().any(|row| {
16484 row.iter()
16485 .any(|cell| self.is_ast_volatile_with_provider(cell))
16486 }),
16487 _ => false,
16488 }
16489 }
16490
16491 fn evaluate_layer_sequential(
16493 &mut self,
16494 layer: &super::scheduler::Layer,
16495 ) -> Result<usize, ExcelError> {
16496 self.resource_checkpoint(layer.vertices.len() as u64)?;
16497 self.evaluate_layer_sequential_effects(layer)
16498 }
16499
16500 fn update_vertex_value_with_delta(
16501 &mut self,
16502 vertex_id: VertexId,
16503 new_value: LiteralValue,
16504 delta: &mut DeltaCollector,
16505 ) {
16506 if delta.mode != DeltaMode::Off
16507 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
16508 {
16509 let sheet_name = self.graph.sheet_name(cell.sheet_id);
16510 let old = self
16511 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
16512 .unwrap_or(LiteralValue::Empty);
16513 if old != new_value {
16514 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
16515 }
16516 }
16517 self.graph.update_vertex_value_ref(vertex_id, &new_value);
16518 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
16519 }
16520
16521 fn evaluate_layer_sequential_with_delta(
16522 &mut self,
16523 layer: &super::scheduler::Layer,
16524 delta: &mut DeltaCollector,
16525 ) -> Result<usize, ExcelError> {
16526 self.resource_checkpoint(layer.vertices.len() as u64)?;
16527 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
16528 }
16529
16530 fn evaluate_layer_sequential_cancellable(
16532 &mut self,
16533 layer: &super::scheduler::Layer,
16534 cancel_flag: &AtomicBool,
16535 ) -> Result<usize, ExcelError> {
16536 self.resource_checkpoint(layer.vertices.len() as u64)?;
16537 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
16538 }
16539
16540 fn evaluate_layer_sequential_cancellable_demand_driven(
16542 &mut self,
16543 layer: &super::scheduler::Layer,
16544 cancel_flag: &AtomicBool,
16545 ) -> Result<usize, ExcelError> {
16546 self.resource_checkpoint(layer.vertices.len() as u64)?;
16547 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
16548 }
16549
16550 fn evaluate_layer_parallel(
16562 &mut self,
16563 layer: &super::scheduler::Layer,
16564 ) -> Result<usize, ExcelError> {
16565 if layer.sequential {
16566 return self.evaluate_layer_sequential(layer);
16567 }
16568 self.resource_checkpoint(layer.vertices.len() as u64)?;
16569 let len = layer.vertices.len();
16570 let buffered = buffer_layer_writes(layer);
16571 let (probe, worth) = (PARALLEL_LAYER_PROBE, PARALLEL_LAYER_WORTH);
16572 let start = crate::instant::FzInstant::now();
16573 let mut pos = 0usize;
16574 let mut step = 1usize;
16575 while pos < len {
16576 if pos > 0 {
16577 let elapsed = start.elapsed();
16578 let per_vertex = elapsed.as_nanos() / pos as u128 + 1;
16580 let rest_estimate = per_vertex * (len - pos) as u128;
16581 if len - pos >= 2 && (elapsed >= probe || rest_estimate >= worth.as_nanos()) {
16582 let rest = layer.sub_layer(pos, len);
16583 let min_chunk = if per_vertex >= EXPENSIVE_VERTEX_NS {
16586 1
16587 } else {
16588 8
16589 };
16590 return Ok(pos + self.evaluate_layer_parallel_effects(&rest, min_chunk)?);
16591 }
16592 let fit = (probe.saturating_sub(elapsed).as_nanos() / per_vertex) as usize + 1;
16595 step = step.saturating_mul(2).min(fit);
16596 }
16597 let end = (pos + step).min(len);
16598 let slice = layer.sub_layer(pos, end);
16599 pos += self.evaluate_layer_units_until(
16602 &slice,
16603 None,
16604 None,
16605 None,
16606 buffered,
16607 Some(start + probe),
16608 )?;
16609 }
16610 Ok(len)
16611 }
16612
16613 fn evaluate_layer_parallel_with_delta(
16614 &mut self,
16615 layer: &super::scheduler::Layer,
16616 delta: &mut DeltaCollector,
16617 ) -> Result<usize, ExcelError> {
16618 if layer.sequential {
16619 return self.evaluate_layer_sequential_with_delta(layer, delta);
16620 }
16621 self.resource_checkpoint(layer.vertices.len() as u64)?;
16622 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
16623 }
16624
16625 fn evaluate_layer_parallel_cancellable(
16627 &mut self,
16628 layer: &super::scheduler::Layer,
16629 cancel_flag: &AtomicBool,
16630 ) -> Result<usize, ExcelError> {
16631 if layer.sequential {
16632 return self.evaluate_layer_sequential_cancellable(layer, cancel_flag);
16633 }
16634 self.resource_checkpoint(layer.vertices.len() as u64)?;
16635 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
16636 }
16637
16638 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
16640 if !self.graph.vertex_exists(vertex_id) {
16642 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
16643 .with_message(format!("Vertex not found: {vertex_id:?}")));
16644 }
16645
16646 let kind = self.graph.get_vertex_kind(vertex_id);
16648 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
16649
16650 let view = match kind {
16651 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
16652 if let Some(view) = self.graph.formula_view(vertex_id) {
16653 view
16654 } else {
16655 return Ok(LiteralValue::Number(0.0));
16656 }
16657 }
16658 VertexKind::Empty | VertexKind::Cell => {
16659 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
16660 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16661 let row = cell_ref.coord.row() + 1;
16662 let col = cell_ref.coord.col() + 1;
16663 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
16664 return Ok(v);
16665 }
16666 }
16667 return Ok(LiteralValue::Number(0.0));
16668 }
16669 VertexKind::NamedScalar => {
16670 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
16671 ExcelError::new(ExcelErrorKind::Name)
16672 .with_message("Named range metadata missing".to_string())
16673 })?;
16674
16675 return match &named_range.definition {
16676 NamedDefinition::Cell(cell_ref) => {
16677 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16678 Ok(self
16679 .get_cell_value(
16680 sheet_name,
16681 cell_ref.coord.row() + 1,
16682 cell_ref.coord.col() + 1,
16683 )
16684 .unwrap_or(LiteralValue::Empty))
16685 }
16686 NamedDefinition::Literal(v) => Ok(v.clone()),
16687 NamedDefinition::Formula { ast, .. } => {
16688 let context_sheet = match named_range.scope {
16689 NameScope::Sheet(id) => id,
16690 NameScope::Workbook => sheet_id,
16691 };
16692 let sheet_name = self.graph.sheet_name(context_sheet);
16693 let cell_ref = self
16694 .graph
16695 .get_cell_ref(vertex_id)
16696 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
16697 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16698 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
16699 }
16700 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
16701 .with_message("Range-valued name evaluated as scalar".to_string())),
16702 };
16703 }
16704 VertexKind::NamedArray => {
16705 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
16706 ExcelError::new(ExcelErrorKind::Name)
16707 .with_message("Named range metadata missing".to_string())
16708 })?;
16709
16710 return match &named_range.definition {
16711 NamedDefinition::Range(range_ref) => {
16712 if range_ref.start.sheet_id != range_ref.end.sheet_id {
16713 return Err(ExcelError::new(ExcelErrorKind::Ref)
16714 .with_message("Named range cannot span sheets".to_string()));
16715 }
16716 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
16717 let sr0 = range_ref.start.coord.row();
16718 let sc0 = range_ref.start.coord.col();
16719 let er0 = range_ref.end.coord.row();
16720 let ec0 = range_ref.end.coord.col();
16721 if sr0 > er0 || sc0 > ec0 {
16722 return Err(ExcelError::new(ExcelErrorKind::Ref)
16723 .with_message("Invalid named range bounds".to_string()));
16724 }
16725
16726 let h = (er0 - sr0 + 1) as usize;
16727 let w = (ec0 - sc0 + 1) as usize;
16728 let cell_count = (h as u64).saturating_mul(w as u64);
16729 if cell_count > self.config.spill.max_spill_cells as u64 {
16730 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
16731 "Named range too large to materialize as an array".to_string(),
16732 ));
16733 }
16734
16735 let sheet_id = range_ref.start.sheet_id;
16738 let asheet = self.arrow_sheets.sheet(sheet_name);
16739 let mut rows = Vec::with_capacity(h);
16740 for r0 in sr0..=er0 {
16741 let mut row = Vec::with_capacity(w);
16742 for c0 in sc0..=ec0 {
16743 row.push(
16744 self.read_cell_formatted_in(sheet_id, asheet, r0 + 1, c0 + 1)
16745 .0,
16746 );
16747 }
16748 rows.push(row);
16749 }
16750 Ok(LiteralValue::Array(rows))
16751 }
16752 NamedDefinition::Cell(cell_ref) => {
16753 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
16754 let row = cell_ref.coord.row() + 1;
16755 let col = cell_ref.coord.col() + 1;
16756 let v = self
16757 .get_cell_value(sheet_name, row, col)
16758 .unwrap_or(LiteralValue::Empty);
16759 Ok(LiteralValue::Array(vec![vec![v]]))
16760 }
16761 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
16762 NamedDefinition::Formula { ast, .. } => {
16763 let context_sheet = match named_range.scope {
16764 NameScope::Sheet(id) => id,
16765 NameScope::Workbook => sheet_id,
16766 };
16767 let sheet_name = self.graph.sheet_name(context_sheet);
16768 let cell_ref = self
16769 .graph
16770 .get_cell_ref(vertex_id)
16771 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
16772 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16773 match interpreter.evaluate_ast(ast) {
16774 Ok(cv) => {
16775 let v = cv.into_literal();
16776 match v {
16777 LiteralValue::Array(_) => Ok(v),
16778 other => Ok(LiteralValue::Array(vec![vec![other]])),
16779 }
16780 }
16781 Err(err) => Ok(LiteralValue::Error(err)),
16782 }
16783 }
16784 };
16785 }
16786 VertexKind::InfiniteRange
16787 | VertexKind::Range
16788 | VertexKind::External
16789 | VertexKind::Table => {
16790 return Ok(LiteralValue::Number(0.0));
16792 }
16793 };
16794
16795 let sheet_name = self.graph.sheet_name(sheet_id);
16797 let cell_ref = self
16798 .graph
16799 .get_cell_ref(vertex_id)
16800 .expect("cell ref for vertex");
16801 if let Some(result) =
16802 self.freshness_evaluate_recorded(vertex_id, sheet_name, cell_ref, view)
16803 {
16804 return result;
16805 }
16806 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
16807
16808 interpreter
16809 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
16810 .map(|cv| {
16811 let format = cv.format_id();
16812 self.record_derived_format(vertex_id, format);
16813 crate::engine::result_finalization::finalize_published_calc_result(
16814 cv,
16815 self.config.spill.max_spill_cells,
16816 )
16817 })
16818 }
16819
16820 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
16822 self.thread_pool.as_ref()
16823 }
16824}
16825
16826#[derive(Default)]
16827struct RowBoundsCache {
16828 snapshot: u64,
16829 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
16831}
16832
16833impl RowBoundsCache {
16834 fn new(snapshot: u64) -> Self {
16835 Self {
16836 snapshot,
16837 map: Default::default(),
16838 }
16839 }
16840 fn get_row_bounds(
16841 &self,
16842 sheet_id: SheetId,
16843 col_idx: usize,
16844 snapshot: u64,
16845 ) -> Option<(Option<u32>, Option<u32>)> {
16846 if self.snapshot != snapshot {
16847 return None;
16848 }
16849 self.map.get(&(sheet_id as u32, col_idx)).copied()
16850 }
16851 fn put_row_bounds(
16852 &mut self,
16853 sheet_id: SheetId,
16854 col_idx: usize,
16855 snapshot: u64,
16856 bounds: (Option<u32>, Option<u32>),
16857 ) {
16858 if self.snapshot != snapshot {
16859 self.snapshot = snapshot;
16860 self.map.clear();
16861 }
16862 self.map.insert((sheet_id as u32, col_idx), bounds);
16863 }
16864}
16865
16866struct UsedAxisBoundsCache {
16867 snapshot: u64,
16868 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
16869 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
16870 #[cfg(test)]
16871 row_hits: std::sync::atomic::AtomicUsize,
16872 #[cfg(test)]
16873 row_misses: std::sync::atomic::AtomicUsize,
16874 #[cfg(test)]
16875 col_hits: std::sync::atomic::AtomicUsize,
16876 #[cfg(test)]
16877 col_misses: std::sync::atomic::AtomicUsize,
16878}
16879
16880impl UsedAxisBoundsCache {
16881 fn new(snapshot: u64) -> Self {
16882 Self {
16883 snapshot,
16884 row_bounds_by_col_span: Default::default(),
16885 col_bounds_by_row_span: Default::default(),
16886 #[cfg(test)]
16887 row_hits: std::sync::atomic::AtomicUsize::new(0),
16888 #[cfg(test)]
16889 row_misses: std::sync::atomic::AtomicUsize::new(0),
16890 #[cfg(test)]
16891 col_hits: std::sync::atomic::AtomicUsize::new(0),
16892 #[cfg(test)]
16893 col_misses: std::sync::atomic::AtomicUsize::new(0),
16894 }
16895 }
16896
16897 fn reset_for_snapshot(&mut self, snapshot: u64) {
16898 if self.snapshot != snapshot {
16899 self.snapshot = snapshot;
16900 self.row_bounds_by_col_span.clear();
16901 self.col_bounds_by_row_span.clear();
16902 }
16903 }
16904
16905 fn get_row_bounds(
16906 &self,
16907 sheet_id: SheetId,
16908 start_col: u32,
16909 end_col: u32,
16910 snapshot: u64,
16911 ) -> Option<Option<(u32, u32)>> {
16912 if self.snapshot != snapshot {
16913 return None;
16914 }
16915 let cached = self
16916 .row_bounds_by_col_span
16917 .get(&(sheet_id, start_col, end_col))
16918 .copied();
16919 #[cfg(test)]
16920 if cached.is_some() {
16921 self.row_hits.fetch_add(1, Ordering::Relaxed);
16922 }
16923 cached
16924 }
16925
16926 fn put_row_bounds(
16927 &mut self,
16928 sheet_id: SheetId,
16929 start_col: u32,
16930 end_col: u32,
16931 snapshot: u64,
16932 bounds: Option<(u32, u32)>,
16933 ) {
16934 self.reset_for_snapshot(snapshot);
16935 self.row_bounds_by_col_span
16936 .insert((sheet_id, start_col, end_col), bounds);
16937 #[cfg(test)]
16938 self.row_misses.fetch_add(1, Ordering::Relaxed);
16939 }
16940
16941 fn get_col_bounds(
16942 &self,
16943 sheet_id: SheetId,
16944 start_row: u32,
16945 end_row: u32,
16946 snapshot: u64,
16947 ) -> Option<Option<(u32, u32)>> {
16948 if self.snapshot != snapshot {
16949 return None;
16950 }
16951 let cached = self
16952 .col_bounds_by_row_span
16953 .get(&(sheet_id, start_row, end_row))
16954 .copied();
16955 #[cfg(test)]
16956 if cached.is_some() {
16957 self.col_hits.fetch_add(1, Ordering::Relaxed);
16958 }
16959 cached
16960 }
16961
16962 fn put_col_bounds(
16963 &mut self,
16964 sheet_id: SheetId,
16965 start_row: u32,
16966 end_row: u32,
16967 snapshot: u64,
16968 bounds: Option<(u32, u32)>,
16969 ) {
16970 self.reset_for_snapshot(snapshot);
16971 self.col_bounds_by_row_span
16972 .insert((sheet_id, start_row, end_row), bounds);
16973 #[cfg(test)]
16974 self.col_misses.fetch_add(1, Ordering::Relaxed);
16975 }
16976}
16977
16978#[derive(Default)]
16980pub struct ShimSpillManager {
16981 region_locks: RegionLockManager,
16982 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
16983}
16984
16985impl ShimSpillManager {
16986 pub(crate) fn reserve(
16987 &mut self,
16988 owner: VertexId,
16989 anchor_cell: CellRef,
16990 shape: SpillShape,
16991 _meta: SpillMeta,
16992 ) -> Result<(), ExcelError> {
16993 let region = crate::engine::spill::Region {
16995 sheet_id: anchor_cell.sheet_id as u32,
16996 row_start: anchor_cell.coord.row(),
16997 row_end: anchor_cell
16998 .coord
16999 .row()
17000 .saturating_add(shape.rows)
17001 .saturating_sub(1),
17002 col_start: anchor_cell.coord.col(),
17003 col_end: anchor_cell
17004 .coord
17005 .col()
17006 .saturating_add(shape.cols)
17007 .saturating_sub(1),
17008 };
17009 match self.region_locks.reserve(region, owner) {
17010 Ok(id) => {
17011 if id != 0 {
17012 self.active_locks.insert(owner, id);
17013 }
17014 Ok(())
17015 }
17016 Err(e) => Err(e),
17017 }
17018 }
17019
17020 pub(crate) fn release_owner(&mut self, owner: VertexId) {
17026 if let Some(id) = self.active_locks.remove(&owner) {
17027 self.region_locks.release(id);
17028 }
17029 }
17030
17031 pub(crate) fn commit_array_with_value_probe<F>(
17032 &mut self,
17033 graph: &mut DependencyGraph,
17034 anchor_vertex: VertexId,
17035 targets: &[CellRef],
17036 rows: Vec<Vec<LiteralValue>>,
17037 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
17038 mut value_probe: F,
17039 ) -> Result<(), ExcelError>
17040 where
17041 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
17042 {
17043 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
17044
17045 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
17049 anchor_vertex,
17050 targets,
17051 overwritable_formulas,
17052 );
17053 if let Err(e) = plan_res {
17054 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17055 self.region_locks.release(id);
17056 }
17057 return Err(e);
17058 }
17059
17060 if !graph.value_cache_enabled() {
17061 let (expected_rows, expected_cols) = if targets.is_empty() {
17063 (0u32, 0u32)
17064 } else {
17065 let mut min_r = u32::MAX;
17066 let mut max_r = 0u32;
17067 let mut min_c = u32::MAX;
17068 let mut max_c = 0u32;
17069 for cell in targets {
17070 let r = cell.coord.row();
17071 let c = cell.coord.col();
17072 min_r = min_r.min(r);
17073 max_r = max_r.max(r);
17074 min_c = min_c.min(c);
17075 max_c = max_c.max(c);
17076 }
17077 (
17078 max_r.saturating_sub(min_r).saturating_add(1),
17079 max_c.saturating_sub(min_c).saturating_add(1),
17080 )
17081 };
17082
17083 let anchor_cell = graph
17084 .get_cell_ref(anchor_vertex)
17085 .expect("anchor cell ref for spill commit");
17086
17087 for cell in targets {
17088 if *cell == anchor_cell {
17090 continue;
17091 }
17092 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
17094 continue;
17095 }
17096 if let Some(vid) = graph.get_vertex_id_for_address(cell)
17098 && vid != anchor_vertex
17099 {
17100 match graph.get_vertex_kind(vid) {
17101 crate::engine::vertex::VertexKind::FormulaScalar
17102 | crate::engine::vertex::VertexKind::FormulaArray => {
17103 continue;
17105 }
17106 _ => {}
17107 }
17108 }
17109
17110 if let Some(v) = value_probe(graph, cell)
17111 && !matches!(v, LiteralValue::Empty)
17112 {
17113 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17114 self.region_locks.release(id);
17115 }
17116 return Err(ExcelError::new(ExcelErrorKind::Spill)
17117 .with_message("BlockedByValue")
17118 .with_extra(ExcelErrorExtra::Spill {
17119 expected_rows,
17120 expected_cols,
17121 }));
17122 }
17123 }
17124 }
17125
17126 let commit_res = graph.commit_spill_region_atomic_with_fault(
17127 anchor_vertex,
17128 targets.to_vec(),
17129 rows,
17130 None,
17131 );
17132 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17133 self.region_locks.release(id);
17134 }
17135 commit_res.map(|_| ())
17136 }
17137
17138 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
17140 &mut self,
17141 engine: &mut Engine<R>,
17142 anchor_vertex: VertexId,
17143 targets: &[CellRef],
17144 rows: Vec<Vec<LiteralValue>>,
17145 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
17146 ) -> Result<(), ExcelError> {
17147 if let Err(error) = engine.guard_pending_spill_commit(anchor_vertex, targets) {
17148 self.release_owner(anchor_vertex);
17149 return Err(error);
17150 }
17151 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
17153 anchor_vertex,
17154 targets,
17155 overwritable_formulas,
17156 );
17157 if let Err(e) = plan_res {
17158 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17159 self.region_locks.release(id);
17160 }
17161 return Err(e);
17162 }
17163
17164 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
17165 anchor_vertex,
17166 targets.to_vec(),
17167 rows.clone(),
17168 None,
17169 );
17170 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17171 self.region_locks.release(id);
17172 }
17173 commit_res.map(|_| ())?;
17174 engine
17175 .blocked_pending_spills
17176 .retain(|entry| entry.0 != anchor_vertex);
17177
17178 if engine.config.arrow_storage_enabled
17180 && engine.config.delta_overlay_enabled
17181 && engine.config.write_formula_overlay_enabled
17182 {
17183 for (idx, cell) in targets.iter().enumerate() {
17185 let (r_off, c_off) = {
17186 if rows.is_empty() || rows[0].is_empty() {
17187 (0usize, 0usize)
17188 } else {
17189 let width = rows[0].len();
17190 (idx / width, idx % width)
17191 }
17192 };
17193 let v = rows
17194 .get(r_off)
17195 .and_then(|r| r.get(c_off))
17196 .cloned()
17197 .unwrap_or(LiteralValue::Empty);
17198 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
17199 engine.mirror_value_to_computed_overlay(
17200 &sheet_name,
17201 cell.coord.row() + 1,
17202 cell.coord.col() + 1,
17203 &v,
17204 );
17205 }
17206 }
17207 Ok(())
17208 }
17209}
17210
17211impl<R> Engine<R>
17212where
17213 R: EvaluationContext,
17214{
17215 fn resolve_shared_ref(
17216 &self,
17217 reference: &ReferenceType,
17218 current_sheet: &str,
17219 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
17220 use formualizer_common::{
17221 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
17222 SheetRef as SharedRef,
17223 };
17224
17225 let sr = match reference {
17227 ReferenceType::Cell {
17228 sheet,
17229 row,
17230 col,
17231 row_abs,
17232 col_abs,
17233 } => {
17234 let row0 = row
17235 .checked_sub(1)
17236 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17237 let col0 = col
17238 .checked_sub(1)
17239 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17240 let sheet_loc = match sheet.as_deref() {
17241 Some(name) => SheetLocator::from_name(name),
17242 None => SheetLocator::Current,
17243 };
17244 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
17245 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
17246 }
17247 ReferenceType::Range {
17248 sheet,
17249 start_row,
17250 start_col,
17251 end_row,
17252 end_col,
17253 start_row_abs,
17254 start_col_abs,
17255 end_row_abs,
17256 end_col_abs,
17257 } => {
17258 let sheet_loc = match sheet.as_deref() {
17259 Some(name) => SheetLocator::from_name(name),
17260 None => SheetLocator::Current,
17261 };
17262 let sr = start_row
17263 .map(|r| {
17264 r.checked_sub(1)
17265 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17266 })
17267 .transpose()?;
17268 let sc = start_col
17269 .map(|c| {
17270 c.checked_sub(1)
17271 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17272 })
17273 .transpose()?;
17274 let er = end_row
17275 .map(|r| {
17276 r.checked_sub(1)
17277 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17278 })
17279 .transpose()?;
17280 let ec = end_col
17281 .map(|c| {
17282 c.checked_sub(1)
17283 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17284 })
17285 .transpose()?;
17286 let range = SharedRangeRef::from_parts(
17287 sheet_loc,
17288 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
17289 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
17290 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
17291 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
17292 )
17293 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
17294 SharedRef::Range(range)
17295 }
17296 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
17297 };
17298
17299 let current_id = self
17300 .graph
17301 .sheet_id(current_sheet)
17302 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17303
17304 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
17305 match loc {
17306 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
17307 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
17308 SheetLocator::Name(name) => {
17309 let n = name.as_ref();
17310 self.graph
17311 .sheet_id(n)
17312 .map(SheetLocator::Id)
17313 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17314 }
17315 }
17316 };
17317
17318 match sr {
17319 SharedRef::Cell(cell) => {
17320 let owned = cell.into_owned();
17321 let sheet = resolve_loc(owned.sheet)?;
17322 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
17323 }
17324 SharedRef::Range(range) => {
17325 let owned = range.into_owned();
17326 let sheet = resolve_loc(owned.sheet)?;
17327 Ok(SharedRef::Range(SharedRangeRef {
17328 sheet,
17329 start_row: owned.start_row,
17330 start_col: owned.start_col,
17331 end_row: owned.end_row,
17332 end_col: owned.end_col,
17333 }))
17334 }
17335 }
17336 }
17337}
17338
17339impl<R> crate::traits::ReferenceResolver for Engine<R>
17342where
17343 R: EvaluationContext,
17344{
17345 fn resolve_cell_reference(
17346 &self,
17347 sheet: Option<&str>,
17348 row: u32,
17349 col: u32,
17350 ) -> Result<LiteralValue, ExcelError> {
17351 let Some(sheet_name) = sheet else {
17362 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
17363 "Unqualified cell reference resolved without sheet context".to_string(),
17364 ));
17365 };
17366 if let Some(v) = self.get_cell_value(sheet_name, row, col) {
17369 Ok(v)
17370 } else {
17371 Ok(LiteralValue::Number(0.0))
17373 }
17374 }
17375}
17376
17377impl<R> crate::traits::RangeResolver for Engine<R>
17378where
17379 R: EvaluationContext,
17380{
17381 fn resolve_range_reference(
17382 &self,
17383 sheet: Option<&str>,
17384 sr: Option<u32>,
17385 sc: Option<u32>,
17386 er: Option<u32>,
17387 ec: Option<u32>,
17388 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
17389 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
17392 }
17393}
17394
17395impl<R> crate::traits::NamedRangeResolver for Engine<R>
17396where
17397 R: EvaluationContext,
17398{
17399 fn resolve_named_range_reference(
17400 &self,
17401 name: &str,
17402 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
17403 self.resolver.resolve_named_range_reference(name)
17404 }
17405}
17406
17407impl<R> crate::traits::TableResolver for Engine<R>
17408where
17409 R: EvaluationContext,
17410{
17411 fn resolve_table_reference(
17412 &self,
17413 tref: &formualizer_parse::parser::TableReference,
17414 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17415 self.resolver.resolve_table_reference(tref)
17416 }
17417}
17418
17419impl<R> crate::traits::SourceResolver for Engine<R>
17420where
17421 R: EvaluationContext,
17422{
17423 fn source_scalar_version(&self, name: &str) -> Option<u64> {
17424 self.resolver.source_scalar_version(name)
17425 }
17426
17427 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
17428 self.resolver.resolve_source_scalar(name)
17429 }
17430
17431 fn source_table_version(&self, name: &str) -> Option<u64> {
17432 self.resolver.source_table_version(name)
17433 }
17434
17435 fn resolve_source_table(
17436 &self,
17437 name: &str,
17438 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17439 self.resolver.resolve_source_table(name)
17440 }
17441}
17442
17443impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
17445
17446impl<R> crate::traits::FunctionProvider for Engine<R>
17448where
17449 R: EvaluationContext,
17450{
17451 fn planning_semantic_revision(&self) -> Option<u64> {
17452 self.resolver.planning_semantic_revision()
17453 }
17454
17455 fn get_function(
17456 &self,
17457 prefix: &str,
17458 name: &str,
17459 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17460 self.resolver.get_function(prefix, name)
17461 }
17462
17463 fn get_function_for_planning(
17464 &self,
17465 prefix: &str,
17466 name: &str,
17467 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17468 self.resolver.get_function_for_planning(prefix, name)
17469 }
17470}
17471
17472impl<R> Engine<R>
17473where
17474 R: EvaluationContext,
17475{
17476 pub(crate) fn semantic_used_rows_for_columns(
17484 &self,
17485 sheet: &str,
17486 start_col: u32,
17487 end_col: u32,
17488 ) -> Option<(u32, u32)> {
17489 let arrow_bounds = self
17490 .sheet_store()
17491 .sheet(sheet)
17492 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
17493 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
17494 Self::union_used_bounds(arrow_bounds, formula_bounds)
17495 }
17496
17497 pub(crate) fn semantic_used_cols_for_rows(
17498 &self,
17499 sheet: &str,
17500 start_row: u32,
17501 end_row: u32,
17502 ) -> Option<(u32, u32)> {
17503 let arrow_bounds = self
17504 .sheet_store()
17505 .sheet(sheet)
17506 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
17507 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
17508 Self::union_used_bounds(arrow_bounds, formula_bounds)
17509 }
17510}
17511
17512impl<R> crate::traits::EvaluationContext for Engine<R>
17514where
17515 R: EvaluationContext,
17516{
17517 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
17518 &self.clock
17519 }
17520
17521 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
17522 self.thread_pool.as_ref()
17523 }
17524
17525 fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
17526 self.active_cancel_flag.clone()
17527 }
17528
17529 fn chunk_hint(&self) -> Option<usize> {
17530 let hint =
17532 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
17533 Some(hint.clamp(1024, 1 << 20)) }
17535
17536 fn volatile_level(&self) -> crate::traits::VolatileLevel {
17537 self.config.volatile_level
17538 }
17539
17540 fn workbook_seed(&self) -> u64 {
17541 self.config.workbook_seed
17542 }
17543
17544 fn recalc_epoch(&self) -> u64 {
17545 self.recalc_epoch
17546 }
17547
17548 fn workbook_sheet_count(&self) -> Option<usize> {
17549 Some(self.graph.sheet_reg().active_len())
17550 }
17551
17552 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
17553 self.graph.sheet_reg().active_position(sheet)
17554 }
17555
17556 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
17557 self.sheet_index_by_name(current_sheet)
17558 }
17559
17560 fn inspect_reference(
17561 &self,
17562 reference: &ReferenceType,
17563 current_sheet: &str,
17564 ) -> Result<Option<ReferenceInfo>, ExcelError> {
17565 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
17566 let sheet_id = self
17567 .graph
17568 .sheet_id(sheet_name)
17569 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17570 let sheet_index = self
17571 .graph
17572 .sheet_reg()
17573 .active_position_by_id(sheet_id)
17574 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17575 Ok((sheet_id, sheet_index))
17576 };
17577
17578 let cell_info =
17579 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
17580 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
17581 let row0 = row
17582 .checked_sub(1)
17583 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17584 let col0 = col
17585 .checked_sub(1)
17586 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17587 Ok(ReferenceInfo {
17588 first_sheet_index: Some(sheet_index),
17589 sheet_count: Some(1),
17590 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
17591 })
17592 };
17593
17594 let range_info = |sheet_name: &str,
17595 start_row: Option<u32>,
17596 start_col: Option<u32>|
17597 -> Result<ReferenceInfo, ExcelError> {
17598 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
17599 let row = start_row.unwrap_or(1);
17600 let col = start_col.unwrap_or(1);
17601 let row0 = row
17602 .checked_sub(1)
17603 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17604 let col0 = col
17605 .checked_sub(1)
17606 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17607 Ok(ReferenceInfo {
17608 first_sheet_index: Some(sheet_index),
17609 sheet_count: Some(1),
17610 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
17611 })
17612 };
17613
17614 let info = match reference {
17615 ReferenceType::Cell {
17616 sheet, row, col, ..
17617 } => {
17618 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
17619 cell_info(sheet_name, *row, *col)?
17620 }
17621 ReferenceType::Range {
17622 sheet,
17623 start_row,
17624 start_col,
17625 ..
17626 } => {
17627 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
17628 range_info(sheet_name, *start_row, *start_col)?
17629 }
17630 ReferenceType::Cell3D {
17631 sheet_first,
17632 sheet_last,
17633 row,
17634 col,
17635 ..
17636 } => {
17637 let first = cell_info(sheet_first, *row, *col)?;
17638 ReferenceInfo {
17639 first_sheet_index: first.first_sheet_index,
17640 sheet_count: self
17641 .graph
17642 .sheet_reg()
17643 .active_span_len(sheet_first, sheet_last),
17644 first_cell: first.first_cell,
17645 }
17646 }
17647 ReferenceType::Range3D {
17648 sheet_first,
17649 sheet_last,
17650 start_row,
17651 start_col,
17652 ..
17653 } => {
17654 let first = range_info(sheet_first, *start_row, *start_col)?;
17655 ReferenceInfo {
17656 first_sheet_index: first.first_sheet_index,
17657 sheet_count: self
17658 .graph
17659 .sheet_reg()
17660 .active_span_len(sheet_first, sheet_last),
17661 first_cell: first.first_cell,
17662 }
17663 }
17664 ReferenceType::NamedRange(name) => {
17665 let current_id = self
17666 .graph
17667 .sheet_id(current_sheet)
17668 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17669 let named = self
17670 .graph
17671 .resolve_name_entry(name, current_id)
17672 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17673 match &named.definition {
17674 NamedDefinition::Cell(cell) => ReferenceInfo {
17675 first_sheet_index: self
17676 .graph
17677 .sheet_reg()
17678 .active_position_by_id(cell.sheet_id),
17679 sheet_count: Some(1),
17680 first_cell: Some(*cell),
17681 },
17682 NamedDefinition::Range(range) => ReferenceInfo {
17683 first_sheet_index: self
17684 .graph
17685 .sheet_reg()
17686 .active_position_by_id(range.start.sheet_id),
17687 sheet_count: Some(1),
17688 first_cell: Some(range.start),
17689 },
17690 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
17691 ReferenceInfo {
17692 first_sheet_index: None,
17693 sheet_count: None,
17694 first_cell: None,
17695 }
17696 }
17697 }
17698 }
17699 ReferenceType::Table(tref) => {
17700 let table = self
17701 .graph
17702 .resolve_table_entry(&tref.name)
17703 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17704 ReferenceInfo {
17705 first_sheet_index: self
17706 .graph
17707 .sheet_reg()
17708 .active_position_by_id(table.range.start.sheet_id),
17709 sheet_count: Some(1),
17710 first_cell: Some(table.range.start),
17711 }
17712 }
17713 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
17714 };
17715
17716 Ok(Some(info))
17717 }
17718
17719 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
17720 let sheet_name = self.graph.sheet_name(cell.sheet_id);
17721 if sheet_name.is_empty() {
17722 return Err(ExcelError::new(ExcelErrorKind::Ref));
17723 }
17724 let row = cell.coord.row() + 1;
17725 let col = cell.coord.col() + 1;
17726
17727 if let Some(entries) = self.staged_formulas.get(sheet_name)
17728 && let Some(text) = entries.get(row, col)
17729 {
17730 return Ok(Some(if text.starts_with('=') {
17731 text.to_owned()
17732 } else {
17733 format!("={text}")
17734 }));
17735 }
17736
17737 let Some((Some(ast), _)) = self.get_cell(sheet_name, row, col) else {
17738 return Ok(None);
17739 };
17740 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
17741 }
17742
17743 fn used_rows_for_columns(
17744 &self,
17745 sheet: &str,
17746 start_col: u32,
17747 end_col: u32,
17748 ) -> Option<(u32, u32)> {
17749 let sheet_id = self.graph.sheet_id(sheet)?;
17751 let snap = self.data_snapshot_id();
17752 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
17753 guard
17754 .as_ref()
17755 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
17756 }) {
17757 return cached;
17758 }
17759
17760 let arrow_bounds = self
17761 .sheet_store()
17762 .sheet(sheet)
17763 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
17764 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
17765 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
17766 Some(bounds)
17767 } else {
17768 let sc0 = start_col.saturating_sub(1);
17769 let ec0 = end_col.saturating_sub(1);
17770 self.graph
17771 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
17772 .map(|(a0, b0)| (a0 + 1, b0 + 1))
17773 };
17774
17775 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
17776 guard
17777 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
17778 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
17779 }
17780
17781 computed
17782 }
17783
17784 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
17785 let sheet_id = self.graph.sheet_id(sheet)?;
17787 let snap = self.data_snapshot_id();
17788 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
17789 guard
17790 .as_ref()
17791 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
17792 }) {
17793 return cached;
17794 }
17795
17796 let arrow_bounds = self
17797 .sheet_store()
17798 .sheet(sheet)
17799 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
17800 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
17801 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
17802 Some(bounds)
17803 } else {
17804 let sr0 = start_row.saturating_sub(1);
17805 let er0 = end_row.saturating_sub(1);
17806 self.graph
17807 .used_col_bounds_for_rows(sheet_id, sr0, er0)
17808 .map(|(a0, b0)| (a0 + 1, b0 + 1))
17809 };
17810
17811 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
17812 guard
17813 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
17814 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
17815 }
17816
17817 computed
17818 }
17819
17820 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
17821 let _ = self.graph.sheet_id(sheet)?;
17822 Some((1_048_576, 16_384)) }
17826
17827 fn data_snapshot_id(&self) -> u64 {
17828 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
17829 }
17830
17831 fn backend_caps(&self) -> crate::traits::BackendCaps {
17832 crate::traits::BackendCaps {
17833 streaming: true,
17834 used_region: true,
17835 write: false,
17836 tables: false,
17837 async_stream: false,
17838 }
17839 }
17840
17841 fn build_lookup_index(
17842 &self,
17843 view: &RangeView<'_>,
17844 axis: LookupAxis,
17845 ) -> Option<Arc<LookupIndex>> {
17846 self.build_lookup_index_impl(view, axis)
17847 }
17848
17849 fn date_system(&self) -> crate::engine::DateSystem {
17852 self.config.date_system
17853 }
17854 fn resolve_range_view<'c>(
17856 &'c self,
17857 reference: &ReferenceType,
17858 current_sheet: &str,
17859 ) -> Result<RangeView<'c>, ExcelError> {
17860 match reference {
17861 ReferenceType::External(ext) => {
17862 let name = ext.raw.as_str();
17863 match ext.kind {
17864 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
17865 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
17866 return Err(ExcelError::new(ExcelErrorKind::Name)
17867 .with_message(format!("Undefined name: {name}")));
17868 };
17869 let version = source
17870 .version
17871 .or_else(|| self.resolver.source_scalar_version(name));
17872 let v = self.resolve_source_scalar_cached(name, version)?;
17873 Ok(RangeView::from_owned_rows(
17874 vec![vec![v]],
17875 self.config.date_system,
17876 ))
17877 }
17878 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
17879 let Some(source) = self.graph.resolve_source_table_entry(name) else {
17880 let kind =
17884 if crate::engine::refs::unbound_external_range_defers(&ext.kind) {
17885 ExcelErrorKind::Ref
17886 } else {
17887 ExcelErrorKind::Name
17888 };
17889 return Err(ExcelError::new(kind)
17890 .with_message(format!("Undefined table: {name}")));
17891 };
17892 let version = source
17893 .version
17894 .or_else(|| self.resolver.source_table_version(name));
17895 let table = self.resolve_source_table_cached(name, version)?;
17896 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
17897 self.source_table_to_range_view(table.as_ref(), &spec)
17898 }
17899 }
17900 }
17901 ReferenceType::Range { .. } => {
17902 let shared = self.resolve_shared_ref(reference, current_sheet)?;
17903 let formualizer_common::SheetRef::Range(range) = shared else {
17904 return Err(ExcelError::new(ExcelErrorKind::Ref));
17905 };
17906 let sheet_id = match range.sheet {
17909 formualizer_common::SheetLocator::Id(id) => id,
17910 formualizer_common::SheetLocator::Current
17911 | formualizer_common::SheetLocator::Name(_) => {
17912 return Err(ExcelError::new(ExcelErrorKind::Ref));
17913 }
17914 };
17915 let sheet_name = self.graph.sheet_name(sheet_id);
17916
17917 let bounded_range = if range.start_row.is_some()
17918 && range.start_col.is_some()
17919 && range.end_row.is_some()
17920 && range.end_col.is_some()
17921 {
17922 Some(RangeRef::try_from_shared(range.as_ref())?)
17923 } else {
17924 None
17925 };
17926
17927 let sr = bounded_range
17928 .as_ref()
17929 .map(|r| r.start.coord.row() + 1)
17930 .or_else(|| range.start_row.map(|b| b.index + 1));
17931 let sc = bounded_range
17932 .as_ref()
17933 .map(|r| r.start.coord.col() + 1)
17934 .or_else(|| range.start_col.map(|b| b.index + 1));
17935 let er = bounded_range
17936 .as_ref()
17937 .map(|r| r.end.coord.row() + 1)
17938 .or_else(|| range.end_row.map(|b| b.index + 1));
17939 let ec = bounded_range
17940 .as_ref()
17941 .map(|r| r.end.coord.col() + 1)
17942 .or_else(|| range.end_col.map(|b| b.index + 1));
17943
17944 let extent = resolve_used_extent_with_fallback(
17945 OpenRangeBounds {
17946 start_row: sr,
17947 start_column: sc,
17948 end_row: er,
17949 end_column: ec,
17950 },
17951 ExtentPolicy::EvaluationCompat {
17952 fallback_row: None,
17953 fallback_column: None,
17954 },
17955 || {
17956 self.sheet_bounds(sheet_name)
17957 .map(|_| self.config.max_open_ended_rows)
17958 },
17959 || {
17960 self.sheet_bounds(sheet_name)
17961 .map(|_| self.config.max_open_ended_cols)
17962 },
17963 |first, last| self.used_rows_for_columns(sheet_name, first, last),
17964 |first, last| self.used_cols_for_rows(sheet_name, first, last),
17965 );
17966 let (sr, sc, er, ec) = extent
17967 .map(|extent| {
17968 (
17969 extent.start_row,
17970 extent.start_column,
17971 extent.end_row,
17972 extent.end_column,
17973 )
17974 })
17975 .unwrap_or((1, 1, 0, 0));
17976
17977 if self.force_materialize_range_views {
17978 if er < sr || ec < sc {
17979 return Ok(RangeView::from_owned_rows(
17980 Vec::new(),
17981 self.config.date_system,
17982 ));
17983 }
17984 let h = (er - sr + 1) as u64;
17985 let w = (ec - sc + 1) as u64;
17986 let cell_count = h.saturating_mul(w);
17987 if cell_count <= self.config.spill.max_spill_cells as u64 {
17988 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
17989 for r in sr..=er {
17990 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
17991 for c in sc..=ec {
17992 rowv.push(
17993 self.get_cell_value(sheet_name, r, c)
17994 .unwrap_or(LiteralValue::Empty),
17995 );
17996 }
17997 rows.push(rowv);
17998 }
17999 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
18000 }
18001 }
18002
18003 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
18004 return Ok(RangeView::from_owned_rows(
18005 Vec::new(),
18006 self.config.date_system,
18007 ));
18008 };
18009
18010 let rv = if er < sr || ec < sc {
18011 asheet.range_view(1, 1, 0, 0)
18012 } else {
18013 let sr0 = sr.saturating_sub(1) as usize;
18014 let sc0 = sc.saturating_sub(1) as usize;
18015 let er0 = er.saturating_sub(1) as usize;
18016 let ec0 = ec.saturating_sub(1) as usize;
18017 asheet.range_view(sr0, sc0, er0, ec0)
18018 };
18019
18020 Ok(rv)
18021 }
18022 ReferenceType::Cell { .. } => {
18023 let shared = self.resolve_shared_ref(reference, current_sheet)?;
18024 let formualizer_common::SheetRef::Cell(cell) = shared else {
18025 return Err(ExcelError::new(ExcelErrorKind::Ref));
18026 };
18027 let addr = CellRef::try_from_shared(cell)?;
18028 let sheet_id = addr.sheet_id;
18029 let sheet_name = self.graph.sheet_name(sheet_id);
18030 let row = addr.coord.row() + 1;
18031 let col = addr.coord.col() + 1;
18032
18033 if self.force_materialize_range_views {
18034 let v = self
18035 .get_cell_value(sheet_name, row, col)
18036 .unwrap_or(LiteralValue::Empty);
18037 return Ok(RangeView::from_owned_rows(
18038 vec![vec![v]],
18039 self.config.date_system,
18040 ));
18041 }
18042
18043 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
18044 let r0 = row.saturating_sub(1) as usize;
18045 let c0 = col.saturating_sub(1) as usize;
18046 let rv = asheet.range_view(r0, c0, r0, c0);
18047 Ok(rv)
18048 } else {
18049 let v = self
18050 .get_cell_value(sheet_name, row, col)
18051 .unwrap_or(LiteralValue::Empty);
18052 Ok(RangeView::from_owned_rows(
18053 vec![vec![v]],
18054 self.config.date_system,
18055 ))
18056 }
18057 }
18058 ReferenceType::NamedRange(name) => {
18059 if let Some(current_id) = self.graph.sheet_id(current_sheet)
18060 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
18061 {
18062 match &named.definition {
18063 NamedDefinition::Cell(cell_ref) => {
18064 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
18065 if self.force_materialize_range_views {
18066 let v = self
18067 .get_cell_value(
18068 sheet_name,
18069 cell_ref.coord.row() + 1,
18070 cell_ref.coord.col() + 1,
18071 )
18072 .unwrap_or(LiteralValue::Empty);
18073 return Ok(RangeView::from_owned_rows(
18074 vec![vec![v]],
18075 self.config.date_system,
18076 ));
18077 } else {
18078 let asheet = self
18079 .sheet_store()
18080 .sheet(sheet_name)
18081 .expect("Arrow sheet missing for named cell");
18082 let r0 = cell_ref.coord.row() as usize;
18083 let c0 = cell_ref.coord.col() as usize;
18084 let rv = asheet.range_view(r0, c0, r0, c0);
18085 return Ok(rv);
18086 }
18087 }
18088 NamedDefinition::Range(range_ref) => {
18089 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
18090 let sr = range_ref.start.coord.row() + 1;
18091 let sc = range_ref.start.coord.col() + 1;
18092 let er = range_ref.end.coord.row() + 1;
18093 let ec = range_ref.end.coord.col() + 1;
18094 if self.force_materialize_range_views {
18095 let h = (er.saturating_sub(sr) + 1) as u64;
18096 let w = (ec.saturating_sub(sc) + 1) as u64;
18097 let cell_count = h.saturating_mul(w);
18098 if cell_count <= self.config.spill.max_spill_cells as u64 {
18099 let mut rows: Vec<Vec<LiteralValue>> =
18100 Vec::with_capacity(h as usize);
18101 for r in sr..=er {
18102 let mut rowv: Vec<LiteralValue> =
18103 Vec::with_capacity(w as usize);
18104 for c in sc..=ec {
18105 rowv.push(
18106 self.get_cell_value(sheet_name, r, c)
18107 .unwrap_or(LiteralValue::Empty),
18108 );
18109 }
18110 rows.push(rowv);
18111 }
18112 return Ok(RangeView::from_owned_rows(
18113 rows,
18114 self.config.date_system,
18115 ));
18116 }
18117 }
18118 let asheet = self
18119 .sheet_store()
18120 .sheet(sheet_name)
18121 .expect("Arrow sheet missing for named range");
18122 let sr0 = range_ref.start.coord.row() as usize;
18123 let sc0 = range_ref.start.coord.col() as usize;
18124 let er0 = range_ref.end.coord.row() as usize;
18125 let ec0 = range_ref.end.coord.col() as usize;
18126 let rv = asheet.range_view(sr0, sc0, er0, ec0);
18127 return Ok(rv);
18128 }
18129 NamedDefinition::Literal(v) => {
18130 return Ok(RangeView::from_owned_rows(
18131 vec![vec![v.clone()]],
18132 self.config.date_system,
18133 ));
18134 }
18135 NamedDefinition::Formula { .. } => {
18136 if let Some(value) = self.graph.get_value(named.vertex) {
18137 return Ok(RangeView::from_owned_rows(
18138 vec![vec![value]],
18139 self.config.date_system,
18140 ));
18141 }
18142 }
18143 }
18144 }
18145
18146 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
18147 let version = source
18148 .version
18149 .or_else(|| self.resolver.source_scalar_version(name));
18150 let v = self.resolve_source_scalar_cached(name, version)?;
18151 return Ok(RangeView::from_owned_rows(
18152 vec![vec![v]],
18153 self.config.date_system,
18154 ));
18155 }
18156
18157 let data = self.resolver.resolve_named_range_reference(name)?;
18158 Ok(RangeView::from_owned_rows(data, self.config.date_system))
18159 }
18160 ReferenceType::Table(tref) => {
18161 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
18162 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
18163 let asheet = self
18164 .sheet_store()
18165 .sheet(sheet_name)
18166 .expect("Arrow sheet missing for table reference");
18167
18168 let sr0 = table.range.start.coord.row() as usize;
18169 let sc0 = table.range.start.coord.col() as usize;
18170 let er0 = table.range.end.coord.row() as usize;
18171 let ec0 = table.range.end.coord.col() as usize;
18172
18173 let has_totals = table.totals_row;
18174 let has_headers = table.header_row;
18175 let data_sr = if has_headers {
18176 sr0.saturating_add(1)
18177 } else {
18178 sr0
18179 };
18180 let data_er = if has_totals {
18181 er0.saturating_sub(1)
18182 } else {
18183 er0
18184 };
18185
18186 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
18187 if sr > er || sc > ec {
18188 asheet.range_view(1, 1, 0, 0)
18189 } else {
18190 asheet.range_view(sr, sc, er, ec)
18191 }
18192 };
18193
18194 let av = match &tref.specifier {
18195 None => {
18196 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18197 "Table reference without specifier is unsupported".to_string(),
18198 ));
18199 }
18200 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
18201 let Some(idx) = table.col_index(col) else {
18202 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18203 "Column refers to unknown table column".to_string(),
18204 ));
18205 };
18206 let c0 = sc0 + idx;
18207 select(data_sr, c0, data_er, c0)
18208 }
18209 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
18210 start,
18211 end,
18212 )) => {
18213 let Some(si) = table.col_index(start) else {
18214 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18215 "Column range refers to unknown column(s)".to_string(),
18216 ));
18217 };
18218 let Some(ei) = table.col_index(end) else {
18219 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18220 "Column range refers to unknown column(s)".to_string(),
18221 ));
18222 };
18223 let (mut a, mut b) = (si, ei);
18224 if a > b {
18225 std::mem::swap(&mut a, &mut b);
18226 }
18227 let c_start = sc0 + a;
18228 let c_end = sc0 + b;
18229 select(data_sr, c_start, data_er, c_end)
18230 }
18231 Some(formualizer_parse::parser::TableSpecifier::All)
18232 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18233 formualizer_parse::parser::SpecialItem::All,
18234 )) => select(sr0, sc0, er0, ec0),
18235 Some(formualizer_parse::parser::TableSpecifier::Data)
18236 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18237 formualizer_parse::parser::SpecialItem::Data,
18238 )) => select(data_sr, sc0, data_er, ec0),
18239 Some(formualizer_parse::parser::TableSpecifier::Headers)
18240 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18241 formualizer_parse::parser::SpecialItem::Headers,
18242 )) => {
18243 if !has_headers {
18244 asheet.range_view(1, 1, 0, 0)
18245 } else {
18246 select(sr0, sc0, sr0, ec0)
18247 }
18248 }
18249 Some(formualizer_parse::parser::TableSpecifier::Totals)
18250 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18251 formualizer_parse::parser::SpecialItem::Totals,
18252 )) => {
18253 if !has_totals {
18254 asheet.range_view(1, 1, 0, 0)
18255 } else {
18256 select(er0, sc0, er0, ec0)
18257 }
18258 }
18259 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18260 formualizer_parse::parser::SpecialItem::ThisRow,
18261 )) => {
18262 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18263 "@ (This Row) requires table-aware context; not yet supported"
18264 .to_string(),
18265 ));
18266 }
18267 Some(formualizer_parse::parser::TableSpecifier::Row(_))
18268 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
18269 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18270 "Complex structured references not yet supported".to_string(),
18271 ));
18272 }
18273 };
18274
18275 return Ok(av);
18276 }
18277
18278 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
18279 let version = source
18280 .version
18281 .or_else(|| self.resolver.source_table_version(&tref.name));
18282 let table = self.resolve_source_table_cached(&tref.name, version)?;
18283 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
18284 }
18285
18286 let boxed = self
18291 .resolve_range_like(&ReferenceType::Table(tref.clone()))
18292 .map_err(|e| {
18293 if e.kind == ExcelErrorKind::NImpl {
18294 ExcelError::new(ExcelErrorKind::Name)
18295 .with_message(format!("Unknown table: {}", tref.name))
18296 } else {
18297 e
18298 }
18299 })?;
18300 let owned = boxed.materialise().into_owned();
18301 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
18302 }
18303 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
18304 Err(ExcelError::new(ExcelErrorKind::NImpl)
18305 .with_message("3D references are not yet supported".to_string()))
18306 }
18307 }
18308 }
18309
18310 fn resolve_cell_format(
18311 &self,
18312 sheet: Option<&str>,
18313 row: u32,
18314 col: u32,
18315 current_sheet: &str,
18316 ) -> Option<crate::format::FormatId> {
18317 self.effective_format_id(sheet.unwrap_or(current_sheet), row, col)
18318 }
18319
18320 fn format_class(
18321 &self,
18322 format: crate::format::FormatId,
18323 ) -> Option<formualizer_common::numfmt::FormatClass> {
18324 self.format_registry.class(format).cloned()
18325 }
18326
18327 fn record_cell_derived_format(
18328 &self,
18329 sheet: &str,
18330 row: u32,
18331 col: u32,
18332 format: Option<crate::format::FormatId>,
18333 ) {
18334 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
18335 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
18336 self.record_derived_format_at(cell, format);
18337 }
18338 }
18339
18340 fn resolve_cell_reference_value(
18341 &self,
18342 sheet: Option<&str>,
18343 row: u32,
18344 col: u32,
18345 current_sheet: &str,
18346 ) -> Result<LiteralValue, ExcelError> {
18347 let sheet_name = sheet.unwrap_or(current_sheet);
18348 if self.graph.sheet_id(sheet_name).is_none() {
18349 return Err(ExcelError::new(ExcelErrorKind::Ref));
18350 }
18351 Ok(self
18352 .get_cell_value(sheet_name, row, col)
18353 .unwrap_or(LiteralValue::Empty))
18354 }
18355
18356 fn resolve_cell_reference_value_formatted(
18357 &self,
18358 sheet: Option<&str>,
18359 row: u32,
18360 col: u32,
18361 current_sheet: &str,
18362 ) -> Result<(LiteralValue, Option<crate::format::FormatId>), ExcelError> {
18363 let sheet_name = sheet.unwrap_or(current_sheet);
18366 let Some(sheet_id) = self.graph.sheet_id(sheet_name) else {
18367 return Err(ExcelError::new(ExcelErrorKind::Ref));
18368 };
18369 let asheet = self.arrow_sheets.sheet(sheet_name);
18370 Ok(self.read_cell_formatted_in(sheet_id, asheet, row, col))
18371 }
18372
18373 fn build_criteria_mask(
18374 &self,
18375 view: &RangeView<'_>,
18376 col_in_view: usize,
18377 pred: &crate::args::CriteriaPredicate,
18378 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18379 #[cfg(any(test, feature = "test-support"))]
18380 criteria_mask_test_hooks::note_mask(view.dims().0);
18381 if view.dims().1 == 0 {
18382 return None;
18383 }
18384 let sheet_rows = view.sheet().nrows as usize;
18387 if sheet_rows == 0 || view.start_row() >= sheet_rows {
18388 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
18389 }
18390 compute_criteria_mask(view, col_in_view, pred)
18391 }
18392
18393 fn build_row_visibility_mask(
18394 &self,
18395 view: &RangeView<'_>,
18396 mode: VisibilityMaskMode,
18397 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18398 self.build_row_visibility_mask_for_view(view, mode)
18399 }
18400}
18401
18402impl<R> Engine<R>
18403where
18404 R: EvaluationContext,
18405{
18406 fn clear_spill_projection_and_mirror(
18407 &mut self,
18408 anchor_vertex: VertexId,
18409 delta: Option<&mut DeltaCollector>,
18410 ) {
18411 let spill_cells = self
18412 .graph
18413 .spill_cells_for_anchor(anchor_vertex)
18414 .map(|cells| cells.to_vec())
18415 .unwrap_or_default();
18416 if spill_cells.is_empty() {
18417 return;
18418 }
18419
18420 if let Some(delta) = delta
18421 && delta.mode != DeltaMode::Off
18422 {
18423 let empty = LiteralValue::Empty;
18424 for cell in spill_cells.iter() {
18425 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18426 let old = self
18427 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18428 .unwrap_or(LiteralValue::Empty);
18429 if old != empty {
18430 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
18431 }
18432 }
18433 }
18434
18435 self.graph.clear_spill_region(anchor_vertex);
18436 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
18437 self.record_structural_change(scope);
18438 }
18439
18440 if self.config.arrow_storage_enabled
18441 && self.config.delta_overlay_enabled
18442 && self.config.write_formula_overlay_enabled
18443 {
18444 let empty = LiteralValue::Empty;
18445 for cell in spill_cells.iter() {
18446 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
18447 self.mirror_value_to_computed_overlay(
18448 &sheet_name,
18449 cell.coord.row() + 1,
18450 cell.coord.col() + 1,
18451 &empty,
18452 );
18453 }
18454 }
18455 }
18456
18457 fn apply_cycle_outcome(
18471 &mut self,
18472 cycle: &[VertexId],
18473 mut delta: Option<&mut DeltaCollector>,
18474 dirty_filter: Option<&FxHashSet<VertexId>>,
18475 ) -> usize {
18476 let circ_error = LiteralValue::Error(
18477 ExcelError::new(ExcelErrorKind::Circ)
18478 .with_message("Circular dependency detected".to_string()),
18479 );
18480 let mut stamped = 0usize;
18481 for &vertex_id in cycle {
18482 if let Some(filter) = dirty_filter
18483 && !filter.contains(&vertex_id)
18484 {
18485 continue;
18486 }
18487 self.stamp_cycle_error(vertex_id, &circ_error, delta.as_deref_mut());
18488 stamped += 1;
18489 }
18490 stamped
18491 }
18492
18493 fn stamp_cycle_error(
18508 &mut self,
18509 vertex_id: VertexId,
18510 circ_error: &LiteralValue,
18511 mut delta: Option<&mut DeltaCollector>,
18512 ) {
18513 if self.graph.spill_registry_has_anchor(vertex_id) {
18515 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
18516 }
18517 self.spill_mgr.release_owner(vertex_id);
18520
18521 if let Some(d) = delta
18523 && d.mode != DeltaMode::Off
18524 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
18525 {
18526 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18527 let old = self
18528 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18529 .unwrap_or(LiteralValue::Empty);
18530 if old != *circ_error {
18531 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
18532 }
18533 }
18534
18535 self.graph.update_vertex_value_ref(vertex_id, circ_error);
18536 self.mirror_vertex_value_to_overlay(vertex_id, circ_error);
18537 }
18538
18539 fn handle_cycle_unit(
18552 &mut self,
18553 cycle: &[VertexId],
18554 mut delta: Option<&mut DeltaCollector>,
18555 dirty_filter: Option<&FxHashSet<VertexId>>,
18556 cancel_flag: Option<&AtomicBool>,
18557 ) -> Result<usize, ExcelError> {
18558 self.resource_checkpoint(cycle.len() as u64)?;
18559 match self.config.cycle.detection {
18560 CycleDetection::Static => {
18561 Ok(self.apply_cycle_outcome(cycle, delta.as_deref_mut(), dirty_filter))
18562 }
18563 CycleDetection::Runtime => {
18564 if let Some(filter) = dirty_filter
18565 && !cycle.iter().any(|v| filter.contains(v))
18566 {
18567 return Ok(0);
18568 }
18569 self.evaluate_scc_unit(cycle, delta, cancel_flag)
18573 }
18574 }
18575 }
18576
18577 pub(crate) fn evaluate_scc_unit(
18597 &mut self,
18598 cycle: &[VertexId],
18599 mut delta: Option<&mut DeltaCollector>,
18600 cancel_flag: Option<&AtomicBool>,
18601 ) -> Result<usize, ExcelError> {
18602 struct SccMember {
18603 vertex: VertexId,
18604 cell: Option<CellRef>,
18605 }
18606
18607 let task_start = crate::instant::FzInstant::now();
18608
18609 let mut cell_members: Vec<(VertexId, CellRef)> = Vec::new();
18614 let mut name_members: Vec<(VertexId, String)> = Vec::new();
18615 let mut other_members: Vec<VertexId> = Vec::new();
18616 for &v in cycle {
18617 match self.graph.get_vertex_kind(v) {
18618 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
18619 match self.graph.get_cell_ref(v) {
18620 Some(cell) => cell_members.push((v, cell)),
18621 None => other_members.push(v),
18622 }
18623 }
18624 VertexKind::NamedScalar | VertexKind::NamedArray => {
18625 match self.graph.name_key_for_vertex(v) {
18626 Some(key) => name_members.push((v, key)),
18627 None => other_members.push(v),
18628 }
18629 }
18630 _ => other_members.push(v),
18631 }
18632 }
18633 cell_members.sort_unstable_by_key(|(_, c)| (c.sheet_id, c.coord.row(), c.coord.col()));
18634 name_members.sort_unstable_by(|(av, ak), (bv, bk)| ak.cmp(bk).then(av.cmp(bv)));
18635 other_members.sort_unstable();
18636
18637 let cell_refs: Vec<CellRef> = cell_members.iter().map(|(_, c)| *c).collect();
18638 let name_keys: Vec<String> = name_members.iter().map(|(_, k)| k.clone()).collect();
18639 let mut members: Vec<SccMember> = Vec::with_capacity(cycle.len());
18640 for (v, c) in &cell_members {
18641 members.push(SccMember {
18642 vertex: *v,
18643 cell: Some(*c),
18644 });
18645 }
18646 for (v, _) in &name_members {
18647 members.push(SccMember {
18648 vertex: *v,
18649 cell: None,
18650 });
18651 }
18652 for v in &other_members {
18653 members.push(SccMember {
18654 vertex: *v,
18655 cell: None,
18656 });
18657 }
18658 let n = members.len();
18659 let recordable = cell_refs.len() + name_keys.len();
18662
18663 let circ_error = LiteralValue::Error(
18664 ExcelError::new(ExcelErrorKind::Circ)
18665 .with_message("Circular dependency detected".to_string()),
18666 );
18667
18668 if !self.iterative_state_values.is_empty() {
18679 let restore: Vec<(VertexId, LiteralValue)> = members
18680 .iter()
18681 .filter_map(|m| {
18682 let cell = m.cell?;
18683 let persisted = self.iterative_state_values.get(&m.vertex)?;
18684 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18685 let overlay = self
18686 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18687 .unwrap_or(LiteralValue::Empty);
18688 if matches!(overlay, LiteralValue::Empty) {
18689 Some((m.vertex, persisted.clone()))
18690 } else {
18691 None
18692 }
18693 })
18694 .collect();
18695 for (vertex, value) in restore {
18696 self.mirror_vertex_value_to_overlay(vertex, &value);
18697 }
18698 }
18699
18700 let snapshot: Vec<LiteralValue> = members
18703 .iter()
18704 .map(|m| match m.cell {
18705 Some(cell) => {
18706 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18707 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
18708 .unwrap_or(LiteralValue::Empty)
18709 }
18710 None => self
18711 .graph
18712 .get_value(m.vertex)
18713 .unwrap_or(LiteralValue::Empty),
18714 })
18715 .collect();
18716
18717 let mut excluded = vec![false; n];
18722 let mut last_value = snapshot.clone();
18723 let mut stamped = 0usize;
18724 for (i, m) in members.iter().enumerate() {
18725 match self.graph.get_vertex_kind(m.vertex) {
18726 VertexKind::FormulaArray => {
18727 self.stamp_cycle_error(m.vertex, &circ_error, delta.as_deref_mut());
18731 excluded[i] = true;
18732 last_value[i] = circ_error.clone();
18733 stamped += 1;
18734 }
18735 VertexKind::FormulaScalar | VertexKind::NamedScalar | VertexKind::NamedArray => {}
18736 _ => excluded[i] = true,
18737 }
18738 }
18739
18740 let collector = LiveEdgeCollector::new_with_names(&cell_refs, &name_keys);
18741
18742 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
18744 let mut pos: Vec<i64> = vec![-1; n];
18746 let mut changed = vec![false; n];
18748
18749 macro_rules! run_member {
18752 ($i:expr) => {{
18753 let i: usize = $i;
18754 let m = &members[i];
18755 if i < recordable {
18756 collector.set_current(i as u32);
18757 }
18758 let value = {
18759 let ctx = RecordingContext::new(&*self, &collector);
18760 match self.evaluate_vertex_recorded(m.vertex, &ctx, &collector) {
18761 Ok(v) => v,
18762 Err(e) => LiteralValue::Error(e),
18763 }
18764 };
18765 let is_cell_formula = m.cell.is_some();
18766 if is_cell_formula && matches!(value, LiteralValue::Array(_)) {
18767 self.stamp_cycle_error(m.vertex, &circ_error, None);
18772 excluded[i] = true;
18773 stamped += 1;
18774 changed[i] = last_value[i] != circ_error;
18775 last_value[i] = circ_error.clone();
18776 } else {
18777 self.graph.update_vertex_value_ref(m.vertex, &value);
18778 self.mirror_vertex_value_to_overlay(m.vertex, &value);
18779 #[cfg(debug_assertions)]
18783 if let Some(cell) = m.cell {
18784 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
18785 debug_assert!(
18786 self.read_delta_overlay_cell(
18787 &sheet_name,
18788 cell.coord.row() + 1,
18789 cell.coord.col() + 1
18790 )
18791 .is_none(),
18792 "user overlay must never shadow a formula SCC member ({sheet_name}!r{}c{})",
18793 cell.coord.row() + 1,
18794 cell.coord.col() + 1
18795 );
18796 }
18797 changed[i] = last_value[i] != value;
18798 last_value[i] = value;
18799 }
18800 }};
18801 }
18802
18803 let check_cancel = |flag: Option<&AtomicBool>| -> Result<(), ExcelError> {
18804 if let Some(flag) = flag
18805 && flag.load(Ordering::Relaxed)
18806 {
18807 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
18808 .with_message("Evaluation cancelled during SCC evaluation".to_string()));
18809 }
18810 Ok(())
18811 };
18812
18813 check_cancel(cancel_flag)?;
18815 let mut passes = 1usize;
18816 {
18817 let mut p = 0i64;
18818 for i in 0..n {
18819 if excluded[i] {
18820 continue;
18821 }
18822 run_member!(i);
18823 pos[i] = p;
18824 p += 1;
18825 }
18826 }
18827
18828 let policy = self.config.cycle.policy;
18844 let cap = n + 2;
18845 let mut witnessed_cycles = 0usize;
18846 let mut capped = false;
18847 let mut iterating = false;
18849 let mut converged = false;
18850 let mut exact_fixed_point = false;
18851 let mut prev_pass: Option<Vec<LiteralValue>> = None;
18855 let mut iter_max_delta = 0f64;
18858 let mut iter_nan_converged = 0usize;
18859 let mut settle_passes = 0usize;
18863 loop {
18864 let drained = collector.take_edges();
18867 for i in 0..n {
18868 if pos[i] >= 0 {
18869 out_edges[i].clear();
18870 }
18871 }
18872 for (from, to) in drained {
18873 debug_assert!(
18874 pos[from as usize] >= 0,
18875 "edge from a member that did not run"
18876 );
18877 out_edges[from as usize].push(to);
18878 }
18879 let mut edges: Vec<(u32, u32)> = Vec::new();
18880 for (i, outs) in out_edges.iter().enumerate() {
18881 if excluded[i] {
18882 continue;
18883 }
18884 for &t in outs {
18885 edges.push((i as u32, t));
18886 }
18887 }
18888 edges.sort_unstable();
18889 edges.dedup();
18890
18891 let analysis = analyze_live_graph(n, &edges);
18892
18893 if analysis.cycle_count > 0 {
18894 witnessed_cycles = witnessed_cycles.max(analysis.cycle_count);
18898 match policy {
18899 CyclePolicy::Error => {
18900 for i in 0..n {
18906 if analysis.in_cycle[i] && !excluded[i] {
18907 self.stamp_cycle_error(members[i].vertex, &circ_error, None);
18908 excluded[i] = true;
18909 last_value[i] = circ_error.clone();
18910 stamped += 1;
18911 }
18912 }
18913 check_cancel(cancel_flag)?;
18914 let order: Vec<usize> = analysis
18915 .topo
18916 .iter()
18917 .map(|&i| i as usize)
18918 .filter(|&i| !excluded[i])
18919 .collect();
18920 if !order.is_empty() {
18921 passes += 1;
18922 for i in order {
18923 run_member!(i);
18924 }
18925 }
18926 break;
18927 }
18928 CyclePolicy::Iterate {
18929 max_iterations,
18930 max_change,
18931 } => {
18932 iterating = true;
18934
18935 if let Some(prev) = &prev_pass {
18941 let mut round_max_delta = 0f64;
18942 let mut round_nan = 0usize;
18943 let mut all_converged = true;
18944 let mut round_exact = true;
18945 for i in 0..n {
18946 if excluded[i] {
18947 continue;
18951 }
18952 let out = crate::engine::convergence::values_converged(
18953 &prev[i],
18954 &last_value[i],
18955 max_change,
18956 self.config.date_system,
18957 );
18958 if out.nan_converged {
18959 round_nan += 1;
18960 }
18961 if let Some(d) = out.abs_delta {
18962 round_max_delta = round_max_delta.max(d);
18963 if d != 0.0 {
18964 round_exact = false;
18965 }
18966 }
18967 if !out.converged {
18968 all_converged = false;
18969 }
18970 }
18971 iter_max_delta = round_max_delta;
18974 iter_nan_converged = round_nan;
18975 if all_converged {
18976 exact_fixed_point = round_exact;
18977 converged = true;
18978 break;
18979 }
18980 }
18981
18982 if passes >= max_iterations as usize {
18994 capped = true;
18995 break;
18996 }
18997
18998 check_cancel(cancel_flag)?;
18999 prev_pass = Some(last_value.clone());
19007 for x in pos.iter_mut() {
19008 *x = -1;
19009 }
19010 changed.fill(false);
19011 passes += 1;
19012 let mut p = 0i64;
19013 for i in 0..n {
19014 if excluded[i] {
19015 continue;
19016 }
19017 run_member!(i);
19018 pos[i] = p;
19019 p += 1;
19020 }
19021 continue;
19022 }
19023 }
19024 }
19025
19026 let mut stale: Vec<usize> = Vec::new();
19029 for i in 0..n {
19030 if excluded[i] {
19031 continue;
19032 }
19033 let is_stale = out_edges[i].iter().any(|&t| {
19034 let t = t as usize;
19035 changed[t] && (pos[i] < 0 || (pos[t] >= 0 && pos[i] < pos[t]))
19036 });
19037 if is_stale {
19038 stale.push(i);
19039 }
19040 }
19041 if stale.is_empty() {
19042 break; }
19044 if 1 + settle_passes >= cap {
19045 capped = true;
19047 for (i, m) in members.iter().enumerate() {
19048 if !excluded[i] {
19049 self.stamp_cycle_error(m.vertex, &circ_error, None);
19050 excluded[i] = true;
19051 last_value[i] = circ_error.clone();
19052 stamped += 1;
19053 }
19054 }
19055 break;
19056 }
19057
19058 check_cancel(cancel_flag)?;
19059 prev_pass = None;
19066 let topo_pos = analysis.topo_positions();
19067 stale.sort_unstable_by_key(|&i| topo_pos[i]);
19068 for x in pos.iter_mut() {
19069 *x = -1;
19070 }
19071 changed.fill(false);
19072 passes += 1;
19073 settle_passes += 1;
19074 for (p, i) in stale.into_iter().enumerate() {
19075 run_member!(i);
19076 pos[i] = p as i64;
19077 }
19078 }
19079
19080 self.live_cancellation_after_work(SCC_CANCELLED)?;
19086
19087 if iterating && !converged && !capped {
19092 converged = true;
19093 }
19094
19095 collector.clear_current();
19098 if let Some(d) = delta
19099 && d.mode != DeltaMode::Off
19100 {
19101 for (i, m) in members.iter().enumerate() {
19102 if let Some(cell) = m.cell
19103 && last_value[i] != snapshot[i]
19104 {
19105 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19106 }
19107 }
19108 }
19109
19110 if !self.retained_scc_members.is_empty() {
19128 for m in members.iter() {
19129 self.retained_scc_members.remove(&m.vertex);
19130 }
19131 }
19132 if iterating {
19133 let retain = converged
19134 && !capped
19135 && exact_fixed_point
19136 && iter_nan_converged == 0
19137 && members
19138 .iter()
19139 .all(|m| !self.graph.is_volatile(m.vertex) && !self.graph.is_dynamic(m.vertex));
19140 if retain {
19141 if self.retained_scc_members.is_empty() {
19142 self.retained_scc_config_fingerprint = self.retained_scc_config_fingerprint();
19143 self.retained_scc_function_epoch_seen =
19144 crate::function_registry::semantic_epoch();
19145 self.retained_scc_provider_revision_seen =
19146 self.resolver.planning_semantic_revision();
19147 }
19148 let scc_id = self.next_retained_scc_id;
19149 self.next_retained_scc_id = self.next_retained_scc_id.wrapping_add(1);
19150 for (i, m) in members.iter().enumerate() {
19151 self.retained_scc_members.insert(m.vertex, scc_id);
19152 if matches!(last_value[i], LiteralValue::Empty) {
19155 self.iterative_state_values.remove(&m.vertex);
19156 } else {
19157 self.iterative_state_values
19158 .insert(m.vertex, last_value[i].clone());
19159 }
19160 }
19161 } else {
19162 self.pending_iterative_redirty
19163 .extend(members.iter().map(|m| m.vertex));
19164 }
19165 } else if !self.iterative_state_values.is_empty() {
19166 for m in members.iter() {
19170 self.iterative_state_values.remove(&m.vertex);
19171 }
19172 }
19173
19174 {
19175 let t = &mut self.last_cycle_telemetry;
19176 t.static_sccs += 1;
19177 if witnessed_cycles == 0 && stamped == 0 && !capped {
19178 t.phantom_sccs += 1;
19179 }
19180 t.live_cycles_witnessed += witnessed_cycles;
19181 t.circ_cells_stamped += stamped;
19182 t.settle_passes_total += passes;
19183 t.max_passes_single_scc = t.max_passes_single_scc.max(passes);
19184 if iterating {
19185 t.iterated_sccs += 1;
19186 if converged {
19187 t.converged_sccs += 1;
19188 }
19189 t.max_abs_delta_at_stop = t.max_abs_delta_at_stop.max(iter_max_delta);
19190 t.nan_converged += iter_nan_converged;
19191 }
19192 if capped {
19193 t.capped_sccs += 1;
19194 }
19195 t.elapsed_ms += task_start.elapsed().as_millis();
19196 }
19197
19198 Ok(stamped)
19199 }
19200
19201 fn evaluate_vertex_recorded(
19209 &self,
19210 vertex_id: VertexId,
19211 ctx: &RecordingContext<'_, R>,
19212 collector: &LiveEdgeCollector,
19213 ) -> Result<LiteralValue, ExcelError> {
19214 if !self.graph.vertex_exists(vertex_id) {
19215 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
19216 .with_message(format!("Vertex not found: {vertex_id:?}")));
19217 }
19218
19219 let kind = self.graph.get_vertex_kind(vertex_id);
19220 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
19221
19222 match kind {
19223 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19224 let Some(view) = self.graph.formula_view(vertex_id) else {
19225 return Ok(LiteralValue::Number(0.0)); };
19227 let sheet_name = self.graph.sheet_name(sheet_id);
19228 let cell_ref = self
19229 .graph
19230 .get_cell_ref(vertex_id)
19231 .expect("cell ref for vertex");
19232 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19233 interpreter
19234 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
19235 .map(|cv| {
19236 let format = cv.format_id();
19237 self.record_derived_format(vertex_id, format);
19238 crate::engine::result_finalization::finalize_published_calc_result(
19239 cv,
19240 self.config.spill.max_spill_cells,
19241 )
19242 })
19243 }
19244 VertexKind::NamedScalar | VertexKind::NamedArray => {
19245 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
19246 ExcelError::new(ExcelErrorKind::Name)
19247 .with_message("Named range metadata missing".to_string())
19248 })?;
19249
19250 match &named_range.definition {
19251 NamedDefinition::Formula { ast, .. } => {
19252 let context_sheet = match named_range.scope {
19253 NameScope::Sheet(id) => id,
19254 NameScope::Workbook => sheet_id,
19255 };
19256 let sheet_name = self.graph.sheet_name(context_sheet);
19257 let cell_ref = self
19258 .graph
19259 .get_cell_ref(vertex_id)
19260 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
19261 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19262 if kind == VertexKind::NamedScalar {
19263 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
19264 } else {
19265 match interpreter.evaluate_ast(ast) {
19266 Ok(cv) => match cv.into_literal() {
19267 v @ LiteralValue::Array(_) => Ok(v),
19268 other => Ok(LiteralValue::Array(vec![vec![other]])),
19269 },
19270 Err(err) => Ok(LiteralValue::Error(err)),
19271 }
19272 }
19273 }
19274 NamedDefinition::Cell(cell_ref) => {
19275 collector.record_scalar(
19279 cell_ref.sheet_id,
19280 cell_ref.coord.row(),
19281 cell_ref.coord.col(),
19282 );
19283 self.evaluate_vertex_immutable(vertex_id)
19284 }
19285 NamedDefinition::Range(range_ref) => {
19286 if range_ref.start.sheet_id == range_ref.end.sheet_id {
19287 collector.record_rect(
19288 range_ref.start.sheet_id,
19289 range_ref.start.coord.row(),
19290 range_ref.start.coord.col(),
19291 range_ref.end.coord.row(),
19292 range_ref.end.coord.col(),
19293 );
19294 }
19295 self.evaluate_vertex_immutable(vertex_id)
19296 }
19297 NamedDefinition::Literal(_) => self.evaluate_vertex_immutable(vertex_id),
19298 }
19299 }
19300 _ => self.evaluate_vertex_immutable(vertex_id),
19301 }
19302 }
19303
19304 fn pending_spill_occupied(&self, anchor: CellRef, end_row: u32, end_col: u32) -> bool {
19306 let sheet = self.graph.sheet_name(anchor.sheet_id);
19307 let package = self
19308 .staged_formulas
19309 .get(sheet)
19310 .and_then(|staged| staged.deferred_package.as_ref());
19311 self.staged_formula_index.occupies_spill(
19312 sheet,
19313 (anchor.coord.row() + 1, anchor.coord.col() + 1),
19314 (end_row + 1, end_col + 1),
19315 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
19316 )
19317 }
19318
19319 fn remember_pending_spill(
19320 &mut self,
19321 vertex: VertexId,
19322 anchor: CellRef,
19323 region: Region,
19324 ) -> Result<(), ExcelError> {
19325 self.cancellation_checkpoint("pending spill occupancy")?;
19326 self.resource_checkpoint(1)?;
19327 if let Some(entry) = self
19328 .blocked_pending_spills
19329 .iter_mut()
19330 .find(|entry| entry.0 == vertex)
19331 {
19332 *entry = (vertex, anchor, region);
19333 return Ok(());
19334 }
19335 if self.blocked_pending_spills.len() == self.blocked_pending_spills.capacity() {
19336 let additional = self.blocked_pending_spills.capacity().max(1);
19339 let bytes =
19340 (additional as u64)
19341 .saturating_mul(std::mem::size_of::<(VertexId, CellRef, Region)>() as u64);
19342 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19343 ledger
19344 .reserve_retained(bytes)
19345 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19346 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
19347 }
19348 if self
19349 .blocked_pending_spills
19350 .try_reserve_exact(additional)
19351 .is_err()
19352 {
19353 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19354 ledger
19355 .release_retained(bytes)
19356 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19357 self.source_cache_accounted -= bytes;
19358 }
19359 return Err(crate::engine::ResourceLedgerError::Exhausted(
19360 formualizer_common::ResourceExhaustionDetail {
19361 reason: formualizer_common::ResourceExhaustionReason::RetainedMemory,
19362 limit: u64::MAX,
19363 observed: bytes,
19364 request_id: None,
19365 },
19366 )
19367 .into_excel_error());
19368 }
19369 }
19370 self.blocked_pending_spills.push((vertex, anchor, region));
19371 Ok(())
19372 }
19373
19374 fn invalidate_pending_spills(&mut self, scope: StructuralScope) {
19378 if let StructuralScope::RemovedSheet(sheet) = scope {
19379 self.blocked_pending_spills
19380 .retain(|entry| entry.1.sheet_id != sheet);
19381 return;
19382 }
19383 for &(vertex, anchor, region) in &self.blocked_pending_spills {
19384 let affected = match scope {
19385 StructuralScope::Cell { sheet, row, col } => {
19386 region.intersects(&Region::point(sheet, row, col))
19387 }
19388 StructuralScope::Region(changed) => region.intersects(&changed),
19389 StructuralScope::Sheet(sheet) | StructuralScope::RemovedSheet(sheet) => {
19390 region.sheet_id() == sheet
19391 }
19392 StructuralScope::OpaqueGlobal | StructuralScope::AllSheets => true,
19393 };
19394 if affected
19395 && self.graph.vertex_exists(vertex)
19396 && self.graph.get_cell_ref(vertex) == Some(anchor)
19397 && matches!(
19398 self.graph.get_vertex_kind(vertex),
19399 VertexKind::FormulaScalar | VertexKind::FormulaArray
19400 )
19401 {
19402 self.graph.mark_vertex_dirty(vertex);
19403 }
19404 }
19405 }
19406
19407 fn guard_pending_spill_commit(
19408 &mut self,
19409 anchor_vertex: VertexId,
19410 targets: &[CellRef],
19411 ) -> Result<(), ExcelError> {
19412 let Some(anchor) = self.graph.get_cell_ref(anchor_vertex) else {
19413 return Ok(());
19414 };
19415 let Some(last) = targets.last() else {
19416 return Ok(());
19417 };
19418 let occupied = self.pending_spill_occupied(anchor, last.coord.row(), last.coord.col());
19419 if (occupied
19420 || self
19421 .blocked_pending_spills
19422 .iter()
19423 .any(|entry| entry.0 == anchor_vertex))
19424 && let Err(error) = self.remember_pending_spill(
19425 anchor_vertex,
19426 anchor,
19427 Region::rect(
19428 anchor.sheet_id,
19429 anchor.coord.row(),
19430 last.coord.row(),
19431 anchor.coord.col(),
19432 last.coord.col(),
19433 ),
19434 )
19435 {
19436 self.spill_mgr.release_owner(anchor_vertex);
19437 return Err(error);
19438 }
19439 if occupied {
19440 self.spill_mgr.release_owner(anchor_vertex);
19441 return Err(ExcelError::new(ExcelErrorKind::Spill)
19442 .with_message("Spill blocked")
19443 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
19444 expected_rows: last.coord.row() - anchor.coord.row() + 1,
19445 expected_cols: last.coord.col() - anchor.coord.col() + 1,
19446 }));
19447 }
19448 Ok(())
19449 }
19450
19451 fn commit_spill_and_mirror(
19453 &mut self,
19454 anchor_vertex: VertexId,
19455 targets: &[CellRef],
19456 rows: Vec<Vec<LiteralValue>>,
19457 delta: Option<&mut DeltaCollector>,
19458 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19459 ) -> Result<(), ExcelError> {
19460 self.guard_pending_spill_commit(anchor_vertex, targets)?;
19461 let prev_spill_cells = self
19462 .graph
19463 .spill_cells_for_anchor(anchor_vertex)
19464 .map(|cells| cells.to_vec())
19465 .unwrap_or_default();
19466
19467 if let Some(delta) = delta
19468 && delta.mode != DeltaMode::Off
19469 {
19470 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
19471 targets.iter().copied().collect();
19472 let empty = LiteralValue::Empty;
19473
19474 for cell in prev_spill_cells.iter() {
19476 if target_set.contains(cell) {
19477 continue;
19478 }
19479 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19480 let old = self
19481 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19482 .unwrap_or(LiteralValue::Empty);
19483 if old != empty {
19484 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19485 }
19486 }
19487
19488 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
19490 let width = rows[0].len();
19491 for (idx, cell) in targets.iter().enumerate() {
19492 let r_off = idx / width;
19493 let c_off = idx % width;
19494 let new = rows
19495 .get(r_off)
19496 .and_then(|r| r.get(c_off))
19497 .cloned()
19498 .unwrap_or(LiteralValue::Empty);
19499 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19500 let old = self
19501 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19502 .unwrap_or(LiteralValue::Empty);
19503 if old != new {
19504 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19505 }
19506 }
19507 } else {
19508 for cell in targets.iter() {
19510 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19511 let old = self
19512 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19513 .unwrap_or(LiteralValue::Empty);
19514 if !matches!(old, LiteralValue::Empty) {
19515 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19516 }
19517 }
19518 }
19519 }
19520
19521 let arrow_sheets = &self.arrow_sheets;
19524 self.spill_mgr.commit_array_with_value_probe(
19525 &mut self.graph,
19526 anchor_vertex,
19527 targets,
19528 rows.clone(),
19529 overwritable_formulas,
19530 |g, cell| {
19531 let sheet_name = g.sheet_name(cell.sheet_id);
19532 let asheet = arrow_sheets.sheet(sheet_name)?;
19533 let r0 = cell.coord.row() as usize;
19534 let c0 = cell.coord.col() as usize;
19535 let v = asheet.get_cell_value(r0, c0);
19536 if matches!(v, LiteralValue::Empty) {
19537 None
19538 } else {
19539 Some(v)
19540 }
19541 },
19542 )?;
19543
19544 self.blocked_pending_spills
19545 .retain(|entry| entry.0 != anchor_vertex);
19546 if let Some(scope) = Self::structural_scope_from_cells(&prev_spill_cells) {
19547 self.record_structural_change(scope);
19548 }
19549 if let Some(scope) = Self::structural_scope_from_cells(targets) {
19550 self.record_structural_change(scope);
19551 }
19552
19553 if self.config.arrow_storage_enabled
19554 && self.config.delta_overlay_enabled
19555 && self.config.write_formula_overlay_enabled
19556 {
19557 if !prev_spill_cells.is_empty() {
19558 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
19559 targets.iter().copied().collect();
19560 let empty = LiteralValue::Empty;
19561 for cell in prev_spill_cells.iter() {
19562 if !target_set.contains(cell) {
19563 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19564 self.mirror_value_to_computed_overlay(
19565 &sheet_name,
19566 cell.coord.row() + 1,
19567 cell.coord.col() + 1,
19568 &empty,
19569 );
19570 }
19571 }
19572 }
19573
19574 for (idx, cell) in targets.iter().enumerate() {
19575 if rows.is_empty() || rows[0].is_empty() {
19576 break;
19577 }
19578 let width = rows[0].len();
19579 let r_off = idx / width;
19580 let c_off = idx % width;
19581 let v = rows[r_off][c_off].clone();
19582 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19583 self.mirror_value_to_computed_overlay(
19584 &sheet_name,
19585 cell.coord.row() + 1,
19586 cell.coord.col() + 1,
19587 &v,
19588 );
19589 }
19590 }
19591 Ok(())
19592 }
19593}
19594
19595#[cfg(test)]
19596#[path = "tests/authority_schedule_execution.rs"]
19597mod authority_schedule_execution;
19598
19599#[cfg(test)]
19600#[path = "tests/pending_spill.rs"]
19601mod pending_spill_tests;
19602
19603#[cfg(test)]
19604#[path = "tests/spill_batch_abort_192.rs"]
19605mod spill_batch_abort_192;
19606
19607use crate::engine::effects::Effect;
19612use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
19613
19614impl<R> Engine<R>
19615where
19616 R: EvaluationContext,
19617{
19618 pub(crate) fn plan_vertex_effects(
19625 &mut self,
19626 vertex_id: VertexId,
19627 computed_value: LiteralValue,
19628 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19629 ) -> Result<Vec<Effect>, ExcelError> {
19630 if self.freshness_armed() {
19633 if self.freshness_drop_stale(vertex_id) {
19634 return Ok(Vec::new());
19635 }
19636 let effects = self.plan_vertex_effects_unrecorded(
19637 vertex_id,
19638 computed_value,
19639 overwritable_formulas,
19640 )?;
19641 self.freshness_mark_committed(vertex_id);
19642 return Ok(effects);
19643 }
19644 self.plan_vertex_effects_unrecorded(vertex_id, computed_value, overwritable_formulas)
19645 }
19646
19647 fn plan_vertex_effects_unrecorded(
19648 &mut self,
19649 vertex_id: VertexId,
19650 computed_value: LiteralValue,
19651 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19652 ) -> Result<Vec<Effect>, ExcelError> {
19653 let kind = self.graph.get_vertex_kind(vertex_id);
19654 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
19655
19656 if !is_formula {
19660 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
19661 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
19662 && owner != vertex_id
19663 {
19664 return Ok(Vec::new());
19665 }
19666 return Ok(vec![Effect::WriteCell {
19668 vertex_id,
19669 value: computed_value,
19670 }]);
19671 }
19672
19673 match computed_value {
19674 LiteralValue::Array(rows) => {
19675 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
19676 }
19677 other => self.plan_scalar_effects(vertex_id, other),
19678 }
19679 }
19680
19681 fn plan_scalar_effects(
19683 &mut self,
19684 vertex_id: VertexId,
19685 value: LiteralValue,
19686 ) -> Result<Vec<Effect>, ExcelError> {
19687 if let LiteralValue::Error(error) = &value
19690 && error.kind == ExcelErrorKind::Spill
19691 && error.message.as_deref() == Some("SpillTooLarge")
19692 && let formualizer_common::ExcelErrorExtra::Spill {
19693 expected_rows,
19694 expected_cols,
19695 } = error.extra
19696 && u64::from(expected_rows).saturating_mul(u64::from(expected_cols))
19697 > u64::from(self.config.spill.max_spill_cells)
19698 {
19699 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
19700 return self.plan_spill_error_effects(
19701 vertex_id,
19702 "SpillTooLarge",
19703 expected_rows,
19704 expected_cols,
19705 );
19706 }
19707 if !matches!(&value, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Spill) {
19708 self.blocked_pending_spills
19709 .retain(|entry| entry.0 != vertex_id);
19710 }
19711 let has_spill = self
19712 .graph
19713 .spill_cells_for_anchor(vertex_id)
19714 .is_some_and(|c| !c.is_empty());
19715
19716 let mut effects = Vec::new();
19717 if has_spill {
19718 effects.push(Effect::SpillClear {
19719 anchor_vertex: vertex_id,
19720 });
19721 }
19722 effects.push(Effect::WriteCell { vertex_id, value });
19723 Ok(effects)
19724 }
19725
19726 fn plan_array_effects(
19728 &mut self,
19729 vertex_id: VertexId,
19730 rows: Vec<Vec<LiteralValue>>,
19731 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
19732 ) -> Result<Vec<Effect>, ExcelError> {
19733 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
19735
19736 let anchor = self
19737 .graph
19738 .get_cell_ref(vertex_id)
19739 .expect("cell ref for vertex");
19740 let sheet_id = anchor.sheet_id;
19741 let h = rows.len() as u32;
19742 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
19743
19744 let spill_cells = (h as u64).saturating_mul(w as u64);
19746 if spill_cells > self.config.spill.max_spill_cells as u64 {
19747 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
19748 }
19749
19750 const PACKED_MAX_ROW: u32 = 1_048_575;
19752 const PACKED_MAX_COL: u32 = 16_383;
19753 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
19754 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
19755 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
19756 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
19757 }
19758
19759 let mut targets = Vec::new();
19760 for r in 0..h {
19761 for c in 0..w {
19762 targets.push(self.graph.make_cell_ref_internal(
19763 sheet_id,
19764 anchor.coord.row() + r,
19765 anchor.coord.col() + c,
19766 ));
19767 }
19768 }
19769
19770 if h != 0 && w != 0 {
19771 let occupied = self.pending_spill_occupied(anchor, end_row, end_col);
19772 if occupied
19773 || self
19774 .blocked_pending_spills
19775 .iter()
19776 .any(|entry| entry.0 == vertex_id)
19777 {
19778 self.spill_mgr.release_owner(vertex_id);
19779 self.remember_pending_spill(
19780 vertex_id,
19781 anchor,
19782 Region::rect(
19783 sheet_id,
19784 anchor.coord.row(),
19785 end_row,
19786 anchor.coord.col(),
19787 end_col,
19788 ),
19789 )?;
19790 }
19791 if occupied {
19792 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
19793 }
19794 }
19795
19796 match self.spill_mgr.reserve(
19798 vertex_id,
19799 anchor,
19800 SpillShape { rows: h, cols: w },
19801 SpillMeta {
19802 epoch: self.recalc_epoch,
19803 config: self.config.spill,
19804 },
19805 ) {
19806 Ok(()) => {
19807 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
19809 vertex_id,
19810 &targets,
19811 overwritable_formulas,
19812 ) {
19813 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
19814 }
19815
19816 if !self.graph.value_cache_enabled() {
19820 let sheet_name = self.graph.sheet_name(sheet_id);
19821 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
19822 for cell in targets.iter() {
19823 if *cell == anchor {
19825 continue;
19826 }
19827 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
19829 continue;
19830 }
19831 if let Some(vid) = self.graph.get_vertex_id_for_address(cell)
19833 && vid != vertex_id
19834 {
19835 match self.graph.get_vertex_kind(vid) {
19836 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19837 continue;
19838 }
19839 _ => {}
19840 }
19841 }
19842
19843 let v = asheet.get_cell_value(
19844 cell.coord.row() as usize,
19845 cell.coord.col() as usize,
19846 );
19847 if !matches!(v, LiteralValue::Empty) {
19848 return self.plan_spill_error_effects(
19849 vertex_id,
19850 "BlockedByValue",
19851 h,
19852 w,
19853 );
19854 }
19855 }
19856 }
19857 }
19858
19859 let top_left = rows
19860 .first()
19861 .and_then(|r| r.first())
19862 .cloned()
19863 .unwrap_or(LiteralValue::Empty);
19864
19865 let mut effects = Vec::new();
19866 let has_prev = self
19868 .graph
19869 .spill_cells_for_anchor(vertex_id)
19870 .is_some_and(|c| !c.is_empty());
19871 if has_prev {
19872 effects.push(Effect::SpillClear {
19873 anchor_vertex: vertex_id,
19874 });
19875 }
19876 effects.push(Effect::SpillCommit {
19877 anchor_vertex: vertex_id,
19878 anchor_cell: anchor,
19879 target_cells: targets,
19880 values: rows,
19881 });
19882 effects.push(Effect::WriteCell {
19883 vertex_id,
19884 value: top_left,
19885 });
19886 Ok(effects)
19887 }
19888 Err(e) => {
19889 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
19890 self.plan_spill_error_effects(vertex_id, &msg, h, w)
19891 }
19892 }
19893 }
19894
19895 fn plan_spill_error_effects(
19897 &mut self,
19898 vertex_id: VertexId,
19899 message: &str,
19900 expected_rows: u32,
19901 expected_cols: u32,
19902 ) -> Result<Vec<Effect>, ExcelError> {
19903 self.spill_mgr.release_owner(vertex_id);
19904 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
19905 .with_message(message)
19906 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
19907 expected_rows,
19908 expected_cols,
19909 });
19910 let spill_val = LiteralValue::Error(spill_err);
19911
19912 let effects = vec![
19913 Effect::SpillClear {
19914 anchor_vertex: vertex_id,
19915 },
19916 Effect::WriteCell {
19917 vertex_id,
19918 value: spill_val,
19919 },
19920 ];
19921 Ok(effects)
19922 }
19923
19924 pub(crate) fn apply_effect(
19926 &mut self,
19927 effect: &Effect,
19928 delta: Option<&mut DeltaCollector>,
19929 log: Option<&mut ChangeLog>,
19930 ) -> Result<(), ExcelError> {
19931 self.apply_effect_with_computed_writes(effect, delta, log, None)
19932 }
19933
19934 fn apply_effect_with_computed_writes(
19935 &mut self,
19936 effect: &Effect,
19937 delta: Option<&mut DeltaCollector>,
19938 log: Option<&mut ChangeLog>,
19939 computed_writes: Option<&mut ComputedWriteBuffer>,
19940 ) -> Result<(), ExcelError> {
19941 match effect {
19942 Effect::WriteCell { vertex_id, value } => {
19943 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
19944 }
19945 Effect::SpillClear { anchor_vertex } => {
19946 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
19947 }
19948 Effect::SpillCommit {
19949 anchor_vertex,
19950 anchor_cell: _,
19951 target_cells,
19952 values,
19953 } => {
19954 self.apply_spill_commit(
19955 *anchor_vertex,
19956 target_cells,
19957 values.clone(),
19958 delta,
19959 log,
19960 computed_writes,
19961 )?;
19962 }
19963 }
19964 Ok(())
19965 }
19966
19967 fn apply_write_cell(
19969 &mut self,
19970 vertex_id: VertexId,
19971 value: &LiteralValue,
19972 delta: Option<&mut DeltaCollector>,
19973 mut computed_writes: Option<&mut ComputedWriteBuffer>,
19974 ) -> Result<(), ExcelError> {
19975 if let Some(d) = delta
19976 && d.mode != DeltaMode::Off
19977 {
19978 if let Some(buffer) = computed_writes.as_deref_mut() {
19979 self.flush_computed_write_buffer(buffer)?;
19980 }
19981 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
19982 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19983 let old = self
19984 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19985 .unwrap_or(LiteralValue::Empty);
19986 if old != *value {
19987 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19988 }
19989 }
19990 }
19991 self.graph.update_vertex_value_ref(vertex_id, value);
19992 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
19993 Ok(())
19994 }
19995
19996 fn apply_spill_clear(
19998 &mut self,
19999 anchor_vertex: VertexId,
20000 delta: Option<&mut DeltaCollector>,
20001 log: Option<&mut ChangeLog>,
20002 computed_writes: Option<&mut ComputedWriteBuffer>,
20003 ) -> Result<(), ExcelError> {
20004 if let Some(buffer) = computed_writes {
20005 self.flush_computed_write_buffer(buffer)?;
20006 }
20007
20008 let spill_cells = self
20009 .graph
20010 .spill_cells_for_anchor(anchor_vertex)
20011 .map(|cells| cells.to_vec())
20012 .unwrap_or_default();
20013 if spill_cells.is_empty() {
20014 return Ok(());
20015 }
20016
20017 let snapshot = if log.is_some() {
20019 self.snapshot_spill_for_anchor(anchor_vertex)
20020 } else {
20021 None
20022 };
20023
20024 if let Some(d) = delta
20026 && d.mode != DeltaMode::Off
20027 {
20028 let empty = LiteralValue::Empty;
20029 for cell in spill_cells.iter() {
20030 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20031 let old = self
20032 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20033 .unwrap_or(LiteralValue::Empty);
20034 if old != empty {
20035 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20036 }
20037 }
20038 }
20039
20040 self.graph.clear_spill_region(anchor_vertex);
20041 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
20042 self.record_structural_change(scope);
20043 }
20044
20045 if self.config.arrow_storage_enabled
20047 && self.config.delta_overlay_enabled
20048 && self.config.write_formula_overlay_enabled
20049 {
20050 let empty = LiteralValue::Empty;
20051 for cell in spill_cells.iter() {
20052 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
20053 self.mirror_value_to_computed_overlay(
20054 &sheet_name,
20055 cell.coord.row() + 1,
20056 cell.coord.col() + 1,
20057 &empty,
20058 );
20059 }
20060 }
20061
20062 if let Some(log) = log
20064 && let Some(old) = snapshot
20065 {
20066 log.record(ChangeEvent::SpillCleared {
20067 anchor: anchor_vertex,
20068 old,
20069 });
20070 }
20071 Ok(())
20072 }
20073
20074 fn apply_spill_commit(
20076 &mut self,
20077 anchor_vertex: VertexId,
20078 target_cells: &[CellRef],
20079 values: Vec<Vec<LiteralValue>>,
20080 delta: Option<&mut DeltaCollector>,
20081 log: Option<&mut ChangeLog>,
20082 computed_writes: Option<&mut ComputedWriteBuffer>,
20083 ) -> Result<(), ExcelError> {
20084 self.guard_pending_spill_commit(anchor_vertex, target_cells)?;
20085 if let Some(buffer) = computed_writes {
20086 self.flush_computed_write_buffer(buffer)?;
20087 }
20088
20089 let old_snapshot = if log.is_some() {
20091 self.snapshot_spill_for_anchor(anchor_vertex)
20092 } else {
20093 None
20094 };
20095
20096 self.commit_spill_and_mirror(
20098 anchor_vertex,
20099 target_cells,
20100 values.clone(),
20101 delta,
20102 None, )?;
20104
20105 if let Some(log) = log {
20107 log.record(ChangeEvent::SpillCommitted {
20108 anchor: anchor_vertex,
20109 old: old_snapshot,
20110 new: SpillSnapshot {
20111 target_cells: target_cells.to_vec(),
20112 values,
20113 },
20114 });
20115 }
20116 Ok(())
20117 }
20118
20119 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
20124 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
20125 if cells.is_empty() {
20126 return None;
20127 }
20128
20129 let max = self.config.spill.max_spill_cells as usize;
20130 let mut cells = cells;
20131 if cells.len() > max {
20132 cells.truncate(max);
20133 }
20134
20135 let first = *cells.first().expect("non-empty spill cells");
20136 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
20137 let row0 = first.coord.row();
20138 let col0 = first.coord.col();
20139
20140 let mut max_row = row0;
20141 let mut max_col = col0;
20142 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
20143 for cell in &cells {
20144 max_row = max_row.max(cell.coord.row());
20145 max_col = max_col.max(cell.coord.col());
20146 let v = self
20147 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20148 .unwrap_or(LiteralValue::Empty);
20149 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
20150 }
20151
20152 let rows = (max_row - row0 + 1) as usize;
20153 let cols = (max_col - col0 + 1) as usize;
20154 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
20155 for r in 0..rows {
20156 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
20157 for c in 0..cols {
20158 row.push(
20159 by_coord
20160 .get(&(row0 + r as u32, col0 + c as u32))
20161 .cloned()
20162 .unwrap_or(LiteralValue::Empty),
20163 );
20164 }
20165 values.push(row);
20166 }
20167
20168 Some(SpillSnapshot {
20169 target_cells: cells,
20170 values,
20171 })
20172 }
20173
20174 fn flush_before_range_dependent_vertex(
20175 &mut self,
20176 vertex_id: VertexId,
20177 computed_writes: &mut ComputedWriteBuffer,
20178 ) -> Result<(), ExcelError> {
20179 if self.graph.reads_compressed_range(vertex_id) {
20180 self.flush_computed_write_buffer(computed_writes)?;
20181 }
20182 Ok(())
20183 }
20184
20185 fn plan_vertex_effects_with_computed_flush(
20186 &mut self,
20187 vertex_id: VertexId,
20188 computed_value: LiteralValue,
20189 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
20190 computed_writes: &mut ComputedWriteBuffer,
20191 ) -> Result<Vec<Effect>, ExcelError> {
20192 if matches!(&computed_value, LiteralValue::Array(_)) {
20193 self.flush_computed_write_buffer(computed_writes)?;
20194 }
20195 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
20196 }
20197
20198 fn evaluate_small_layer_direct_effects(
20201 &mut self,
20202 layer: &super::scheduler::Layer,
20203 delta: Option<&mut DeltaCollector>,
20204 log: Option<&mut ChangeLog>,
20205 cancel_flag: Option<&AtomicBool>,
20206 cancel_check_every: usize,
20207 cancel_message: &'static str,
20208 ) -> Result<usize, ExcelError> {
20209 let cancel = cancel_flag.map(|flag| (flag, cancel_check_every, cancel_message));
20210 self.evaluate_layer_units(layer, delta, log, cancel, false)
20211 }
20212
20213 fn evaluate_layer_units(
20220 &mut self,
20221 layer: &super::scheduler::Layer,
20222 delta: Option<&mut DeltaCollector>,
20223 log: Option<&mut ChangeLog>,
20224 cancel: Option<(&AtomicBool, usize, &'static str)>,
20225 buffered: bool,
20226 ) -> Result<usize, ExcelError> {
20227 self.evaluate_layer_units_until(layer, delta, log, cancel, buffered, None)
20228 }
20229
20230 fn evaluate_layer_units_until(
20234 &mut self,
20235 layer: &super::scheduler::Layer,
20236 mut delta: Option<&mut DeltaCollector>,
20237 mut log: Option<&mut ChangeLog>,
20238 cancel: Option<(&AtomicBool, usize, &'static str)>,
20239 buffered: bool,
20240 stop_at: Option<crate::instant::FzInstant>,
20241 ) -> Result<usize, ExcelError> {
20242 let mut chain_values = None;
20245 if layer.sequential && !layer.runs.is_empty() {
20246 chain_values = match layer.runs.as_slice() {
20247 [run] if run.start == 0 && run.len as usize == layer.vertices.len() => {
20248 self.try_chain_lift(*run, &layer.vertices)
20249 }
20250 _ => None,
20251 };
20252 if chain_values.is_none() {
20253 let cells = super::scheduler::Layer {
20254 vertices: layer.vertices.clone(),
20255 runs: Vec::new(),
20256 sequential: true,
20257 };
20258 return self.evaluate_layer_units_until(&cells, delta, log, cancel, false, stop_at);
20259 }
20260 }
20261 let chained = chain_values.is_some();
20262 let buffered = buffered || chained;
20264 let track_unflushed = buffered
20271 && self.freshness_armed()
20272 && layer.vertices.iter().any(|&v| self.graph.is_dynamic(v));
20273 let mut committed_unit: &[VertexId] = &[];
20274 let mut computed_writes = ComputedWriteBuffer::default();
20275 let mut next_check = 0usize;
20276 let mut done = 0usize;
20277 for unit in layer_units(layer) {
20278 if done > 0
20279 && let Some(stop_at) = stop_at
20280 && crate::instant::FzInstant::now() >= stop_at
20281 {
20282 self.flush_computed_write_buffer(&mut computed_writes)?;
20283 if track_unflushed {
20284 self.freshness_flushed();
20285 }
20286 return Ok(done);
20287 }
20288 if let Some((flag, every, message)) = cancel
20289 && every > 0
20290 && done >= next_check
20291 {
20292 next_check = (done / every + 1) * every;
20293 if flag.load(Ordering::Relaxed) {
20294 if buffered {
20295 self.flush_computed_write_buffer(&mut computed_writes)?;
20296 }
20297 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
20298 .with_message(message.to_string()));
20299 }
20300 }
20301 if buffered && self.unit_reads_compressed_range(layer, unit) {
20302 self.flush_computed_write_buffer(&mut computed_writes)?;
20303 }
20304 if track_unflushed {
20308 if computed_writes.is_empty() {
20309 self.freshness_flushed();
20310 } else {
20311 self.freshness_note_unflushed(committed_unit);
20312 }
20313 committed_unit = unit_members(layer, unit);
20314 }
20315 let values = match (chain_values.take(), unit) {
20316 (Some(chain), LayerUnit::Run(run)) => {
20317 if let Err(e) = self.live_cancellation_after_work(UNIT_CANCELLED) {
20318 self.flush_computed_write_buffer(&mut computed_writes)?;
20319 return Err(e);
20320 }
20321 let members =
20322 &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20323 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20324 match self.commit_run_numbers(
20325 run,
20326 members,
20327 &chain,
20328 delta_active,
20329 Some(&mut computed_writes),
20330 ) {
20331 Ok(true) => {
20332 done += chain.len();
20333 continue;
20334 }
20335 Ok(false) => {}
20336 Err(e) => {
20337 self.flush_computed_write_buffer(&mut computed_writes)?;
20338 return Err(e);
20339 }
20340 }
20341 members
20342 .iter()
20343 .copied()
20344 .zip(chain.into_iter().map(LiteralValue::Number))
20345 .collect()
20346 }
20347 (_, unit) => self.evaluate_unit_immutable(layer, unit),
20348 };
20349 if let Err(e) = self.live_cancellation_after_work(UNIT_CANCELLED) {
20352 self.flush_computed_write_buffer(&mut computed_writes)?;
20353 return Err(e);
20354 }
20355 done += values.len();
20356 if let LayerUnit::Run(run) = unit {
20357 let members = &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20358 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20359 let committed = self.commit_run_scalars(
20360 run,
20361 members,
20362 &values,
20363 delta_active,
20364 buffered.then_some(&mut computed_writes),
20365 );
20366 match committed {
20367 Ok(true) => continue,
20368 Ok(false) => {}
20369 Err(e) => {
20370 self.flush_computed_write_buffer(&mut computed_writes)?;
20371 return Err(e);
20372 }
20373 }
20374 }
20375 let guarded = self.freshness_begin_batch_commit(&values);
20378 for (vertex_id, value) in values {
20379 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20380 let effects = if buffered {
20381 self.plan_vertex_effects_with_computed_flush(
20382 vertex_id,
20383 value,
20384 None,
20385 &mut computed_writes,
20386 )
20387 } else {
20388 self.plan_vertex_effects(vertex_id, value, None)
20389 };
20390 let effects = match effects {
20391 Ok(effects) => effects,
20392 Err(e) => {
20393 self.flush_computed_write_buffer(&mut computed_writes)?;
20394 return Err(e);
20395 }
20396 };
20397 for effect in &effects {
20398 if let Err(e) = self.apply_effect_with_computed_writes(
20399 effect,
20400 delta.as_deref_mut(),
20401 log.as_deref_mut(),
20402 buffered.then_some(&mut computed_writes),
20403 ) {
20404 self.flush_computed_write_buffer(&mut computed_writes)?;
20405 return Err(e);
20406 }
20407 }
20408 self.freshness_keep_redirtied(redirtied, vertex_id);
20409 }
20410 }
20411 self.flush_computed_write_buffer(&mut computed_writes)?;
20412 if track_unflushed {
20413 self.freshness_flushed();
20414 }
20415 #[cfg(debug_assertions)]
20418 if chained {
20419 for &v in &layer.vertices {
20420 let cell = self.graph.get_cell_ref(v);
20421 let (sheet, row, col) = cell
20422 .map(|c| {
20423 (
20424 self.graph.sheet_name(c.sheet_id).to_string(),
20425 c.coord.row() + 1,
20426 c.coord.col() + 1,
20427 )
20428 })
20429 .expect("chain member cell");
20430 let written = self.get_cell_value(&sheet, row, col);
20431 let oracle = self
20432 .evaluate_vertex_immutable(v)
20433 .unwrap_or_else(LiteralValue::Error);
20434 assert!(
20435 written
20436 .as_ref()
20437 .is_some_and(|w| same_value_bits(w, &oracle)),
20438 "chain member {sheet}!R{row}C{col}: {written:?} vs per-cell {oracle:?}"
20439 );
20440 }
20441 }
20442 #[cfg(not(debug_assertions))]
20443 let _ = chained;
20444 Ok(layer.vertices.len())
20445 }
20446
20447 fn evaluate_layer_sequential_effects(
20449 &mut self,
20450 layer: &super::scheduler::Layer,
20451 ) -> Result<usize, ExcelError> {
20452 let buffered = buffer_layer_writes(layer);
20453 self.evaluate_layer_units(layer, None, None, None, buffered)
20454 }
20455
20456 fn evaluate_layer_sequential_with_delta_effects(
20458 &mut self,
20459 layer: &super::scheduler::Layer,
20460 delta: &mut DeltaCollector,
20461 ) -> Result<usize, ExcelError> {
20462 let buffered = buffer_layer_writes(layer);
20463 self.evaluate_layer_units(layer, Some(delta), None, None, buffered)
20464 }
20465
20466 fn evaluate_layer_sequential_cancellable_effects(
20468 &mut self,
20469 layer: &super::scheduler::Layer,
20470 cancel_flag: &AtomicBool,
20471 ) -> Result<usize, ExcelError> {
20472 let buffered = buffer_layer_writes(layer);
20473 let cancel = (cancel_flag, 256, "Evaluation cancelled within layer");
20474 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
20475 }
20476
20477 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
20479 &mut self,
20480 layer: &super::scheduler::Layer,
20481 cancel_flag: &AtomicBool,
20482 ) -> Result<usize, ExcelError> {
20483 let buffered = buffer_layer_writes(layer);
20484 let cancel = (
20485 cancel_flag,
20486 128,
20487 "Demand-driven evaluation cancelled within layer",
20488 );
20489 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
20490 }
20491
20492 fn evaluate_layer_parallel_effects(
20494 &mut self,
20495 layer: &super::scheduler::Layer,
20496 min_chunk: u32,
20497 ) -> Result<usize, ExcelError> {
20498 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20499
20500 let phases = self.parallel_phases(layer);
20501
20502 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20503 let mut applied = 0usize;
20504
20505 for (units, group) in &phases {
20506 let group = &group[..];
20507 if group.is_empty() {
20508 continue;
20509 }
20510 let mut computed_writes = ComputedWriteBuffer::default();
20511
20512 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
20513 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, min_chunk));
20514
20515 let results = results.and_then(|results| {
20518 self.live_cancellation_after_work(GROUP_CANCELLED)
20519 .map(|()| results)
20520 });
20521 self.freshness_gate_group(group);
20524 match results {
20525 Ok(vertex_results) => {
20526 let (vertex_results, committed) = self.commit_parallel_runs(
20527 layer,
20528 units,
20529 vertex_results,
20530 false,
20531 &mut computed_writes,
20532 )?;
20533 applied = applied.saturating_add(committed);
20534 let guarded = self.freshness_begin_batch_commit(&vertex_results);
20539 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20540 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20541 for (vertex_id, result) in vertex_results {
20542 if matches!(result, LiteralValue::Array(_)) {
20543 arrays.push((vertex_id, result));
20544 } else {
20545 others.push((vertex_id, result));
20546 }
20547 }
20548 for (vertex_id, result) in arrays {
20549 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20550 let effects = match self.plan_vertex_effects_with_computed_flush(
20551 vertex_id,
20552 result,
20553 Some(&inflight),
20554 &mut computed_writes,
20555 ) {
20556 Ok(effects) => effects,
20557 Err(e) => {
20558 self.flush_computed_write_buffer(&mut computed_writes)?;
20559 return Err(e);
20560 }
20561 };
20562 for effect in &effects {
20563 if let Err(e) = self.apply_effect_with_computed_writes(
20564 effect,
20565 None,
20566 None,
20567 Some(&mut computed_writes),
20568 ) {
20569 self.flush_computed_write_buffer(&mut computed_writes)?;
20570 return Err(e);
20571 }
20572 }
20573 self.freshness_keep_redirtied(redirtied, vertex_id);
20574 applied = applied.saturating_add(1);
20575 }
20576 self.flush_computed_write_buffer(&mut computed_writes)?;
20578 for (vertex_id, result) in others {
20579 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20580 let effects = match self.plan_vertex_effects_with_computed_flush(
20581 vertex_id,
20582 result,
20583 Some(&inflight),
20584 &mut computed_writes,
20585 ) {
20586 Ok(effects) => effects,
20587 Err(e) => {
20588 self.flush_computed_write_buffer(&mut computed_writes)?;
20589 return Err(e);
20590 }
20591 };
20592 for effect in &effects {
20593 if let Err(e) = self.apply_effect_with_computed_writes(
20594 effect,
20595 None,
20596 None,
20597 Some(&mut computed_writes),
20598 ) {
20599 self.flush_computed_write_buffer(&mut computed_writes)?;
20600 return Err(e);
20601 }
20602 }
20603 self.freshness_keep_redirtied(redirtied, vertex_id);
20604 applied = applied.saturating_add(1);
20605 }
20606 self.flush_computed_write_buffer(&mut computed_writes)?;
20608 }
20609 Err(e) => {
20610 self.flush_computed_write_buffer(&mut computed_writes)?;
20611 return Err(e);
20612 }
20613 }
20614 }
20615
20616 Ok(applied)
20617 }
20618
20619 fn evaluate_layer_parallel_with_delta_effects(
20621 &mut self,
20622 layer: &super::scheduler::Layer,
20623 delta: &mut DeltaCollector,
20624 ) -> Result<usize, ExcelError> {
20625 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20626
20627 let phases = self.parallel_phases(layer);
20628
20629 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20630 let mut applied = 0usize;
20631
20632 for (units, group) in &phases {
20633 let group = &group[..];
20634 if group.is_empty() {
20635 continue;
20636 }
20637 let mut computed_writes = ComputedWriteBuffer::default();
20638 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
20639 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, 8));
20640
20641 let results = results.and_then(|results| {
20644 self.live_cancellation_after_work(GROUP_CANCELLED)
20645 .map(|()| results)
20646 });
20647 self.freshness_gate_group(group);
20650 match results {
20651 Ok(vertex_results) => {
20652 let (vertex_results, committed) = self.commit_parallel_runs(
20653 layer,
20654 units,
20655 vertex_results,
20656 delta.mode != DeltaMode::Off,
20657 &mut computed_writes,
20658 )?;
20659 applied = applied.saturating_add(committed);
20660 let guarded = self.freshness_begin_batch_commit(&vertex_results);
20663 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20664 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20665 for (vertex_id, result) in vertex_results {
20666 if matches!(result, LiteralValue::Array(_)) {
20667 arrays.push((vertex_id, result));
20668 } else {
20669 others.push((vertex_id, result));
20670 }
20671 }
20672 for (vertex_id, result) in arrays {
20673 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20674 let effects = match self.plan_vertex_effects_with_computed_flush(
20675 vertex_id,
20676 result,
20677 Some(&inflight),
20678 &mut computed_writes,
20679 ) {
20680 Ok(effects) => effects,
20681 Err(e) => {
20682 self.flush_computed_write_buffer(&mut computed_writes)?;
20683 return Err(e);
20684 }
20685 };
20686 for effect in &effects {
20687 if let Err(e) = self.apply_effect_with_computed_writes(
20688 effect,
20689 Some(delta),
20690 None,
20691 Some(&mut computed_writes),
20692 ) {
20693 self.flush_computed_write_buffer(&mut computed_writes)?;
20694 return Err(e);
20695 }
20696 }
20697 self.freshness_keep_redirtied(redirtied, vertex_id);
20698 applied = applied.saturating_add(1);
20699 }
20700 self.flush_computed_write_buffer(&mut computed_writes)?;
20701 for (vertex_id, result) in others {
20702 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20703 let effects = match self.plan_vertex_effects_with_computed_flush(
20704 vertex_id,
20705 result,
20706 Some(&inflight),
20707 &mut computed_writes,
20708 ) {
20709 Ok(effects) => effects,
20710 Err(e) => {
20711 self.flush_computed_write_buffer(&mut computed_writes)?;
20712 return Err(e);
20713 }
20714 };
20715 for effect in &effects {
20716 if let Err(e) = self.apply_effect_with_computed_writes(
20717 effect,
20718 Some(delta),
20719 None,
20720 Some(&mut computed_writes),
20721 ) {
20722 self.flush_computed_write_buffer(&mut computed_writes)?;
20723 return Err(e);
20724 }
20725 }
20726 self.freshness_keep_redirtied(redirtied, vertex_id);
20727 applied = applied.saturating_add(1);
20728 }
20729 self.flush_computed_write_buffer(&mut computed_writes)?;
20730 }
20731 Err(e) => {
20732 self.flush_computed_write_buffer(&mut computed_writes)?;
20733 return Err(e);
20734 }
20735 }
20736 }
20737
20738 Ok(applied)
20739 }
20740
20741 fn evaluate_layer_parallel_cancellable_effects(
20743 &mut self,
20744 layer: &super::scheduler::Layer,
20745 cancel_flag: &AtomicBool,
20746 ) -> Result<usize, ExcelError> {
20747 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
20748
20749 if cancel_flag.load(Ordering::Relaxed) {
20750 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
20751 .with_message("Parallel evaluation cancelled before starting".to_string()));
20752 }
20753
20754 let phases = self.parallel_phases(layer);
20755
20756 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
20757 let mut applied = 0usize;
20758
20759 for (units, group) in &phases {
20760 let group = &group[..];
20761 if group.is_empty() {
20762 continue;
20763 }
20764 let mut computed_writes = ComputedWriteBuffer::default();
20765
20766 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> = thread_pool
20767 .install(|| self.evaluate_units_parallel(layer, units, Some(cancel_flag), 8));
20768
20769 let results = results.and_then(|results| {
20772 self.live_cancellation_after_work(GROUP_CANCELLED)
20773 .map(|()| results)
20774 });
20775 self.freshness_gate_group(group);
20778 match results {
20779 Ok(vertex_results) => {
20780 let (vertex_results, committed) = self.commit_parallel_runs(
20781 layer,
20782 units,
20783 vertex_results,
20784 false,
20785 &mut computed_writes,
20786 )?;
20787 applied = applied.saturating_add(committed);
20788 let guarded = self.freshness_begin_batch_commit(&vertex_results);
20791 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
20792 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
20793 for (vertex_id, result) in vertex_results {
20794 if matches!(result, LiteralValue::Array(_)) {
20795 arrays.push((vertex_id, result));
20796 } else {
20797 others.push((vertex_id, result));
20798 }
20799 }
20800 for (vertex_id, result) in arrays {
20801 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20802 let effects = match self.plan_vertex_effects_with_computed_flush(
20803 vertex_id,
20804 result,
20805 Some(&inflight),
20806 &mut computed_writes,
20807 ) {
20808 Ok(effects) => effects,
20809 Err(e) => {
20810 self.flush_computed_write_buffer(&mut computed_writes)?;
20811 return Err(e);
20812 }
20813 };
20814 for effect in &effects {
20815 if let Err(e) = self.apply_effect_with_computed_writes(
20816 effect,
20817 None,
20818 None,
20819 Some(&mut computed_writes),
20820 ) {
20821 self.flush_computed_write_buffer(&mut computed_writes)?;
20822 return Err(e);
20823 }
20824 }
20825 self.freshness_keep_redirtied(redirtied, vertex_id);
20826 applied = applied.saturating_add(1);
20827 }
20828 self.flush_computed_write_buffer(&mut computed_writes)?;
20829 for (vertex_id, result) in others {
20830 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
20831 let effects = match self.plan_vertex_effects_with_computed_flush(
20832 vertex_id,
20833 result,
20834 Some(&inflight),
20835 &mut computed_writes,
20836 ) {
20837 Ok(effects) => effects,
20838 Err(e) => {
20839 self.flush_computed_write_buffer(&mut computed_writes)?;
20840 return Err(e);
20841 }
20842 };
20843 for effect in &effects {
20844 if let Err(e) = self.apply_effect_with_computed_writes(
20845 effect,
20846 None,
20847 None,
20848 Some(&mut computed_writes),
20849 ) {
20850 self.flush_computed_write_buffer(&mut computed_writes)?;
20851 return Err(e);
20852 }
20853 }
20854 self.freshness_keep_redirtied(redirtied, vertex_id);
20855 applied = applied.saturating_add(1);
20856 }
20857 self.flush_computed_write_buffer(&mut computed_writes)?;
20858 }
20859 Err(e) => {
20860 self.flush_computed_write_buffer(&mut computed_writes)?;
20861 return Err(e);
20862 }
20863 }
20864 }
20865
20866 Ok(applied)
20867 }
20868
20869 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
20876 self.observe_evaluation_resource_request(EvaluationRequestKind::FullLogged, |engine| {
20877 engine.evaluate_all_logged_unobserved(log)
20878 })
20879 }
20880
20881 fn evaluate_all_logged_unobserved(
20882 &mut self,
20883 log: &mut ChangeLog,
20884 ) -> Result<EvalResult, ExcelError> {
20885 self.observe_function_semantic_epoch()?;
20886 let _source_cache = self.source_cache_session();
20887 self.validate_deterministic_mode()?;
20888 if self.config.defer_graph_building {
20889 self.build_graph_all()?;
20890 }
20891 self.require_unified_authority()?;
20892 self.begin_evaluation_request();
20893 self.reset_virtual_dep_telemetry_if_disabled();
20894 let start = crate::instant::FzInstant::now();
20895 let mut computed_vertices = 0;
20896 let mut cycle_errors = 0;
20897
20898 let mut replan_iterations = 0;
20899 const MAX_REPLAN: usize = 5;
20900 let mut telemetry = self
20901 .config
20902 .enable_virtual_dep_telemetry
20903 .then(|| self.start_virtual_dep_telemetry());
20904
20905 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
20906
20907 let result = (|| -> Result<EvalResult, ExcelError> {
20908 loop {
20909 let to_evaluate = self.graph.get_evaluation_vertices();
20910 if to_evaluate.is_empty() {
20911 if let Some(t) = telemetry.as_mut()
20912 && t.bailout_reason.is_none()
20913 {
20914 t.bailout_reason = Some("no_work");
20915 }
20916 break;
20917 }
20918
20919 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
20920 if let Some(t) = telemetry.as_mut() {
20921 Self::accumulate_schedule_meta(t, &meta);
20922 }
20923
20924 self.begin_pass(&schedule);
20927 for (unit_index, &unit) in schedule.units.iter().enumerate() {
20928 match unit {
20929 ScheduleUnit::Cycle(i) => {
20930 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0
20941 {
20942 cycle_errors += 1;
20943 }
20944 }
20945 ScheduleUnit::Layer(i) => {
20946 computed_vertices +=
20947 self.evaluate_layer_logged(schedule.unit_layer(i), log)?;
20948 }
20949 }
20950 if self.stop_after_unit(&schedule, unit_index) {
20951 break;
20952 }
20953 }
20954
20955 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
20956 if let Some(t) = telemetry.as_mut() {
20957 t.changed_vdeps_total += changed_vertices.len();
20958 }
20959 self.resource_checkpoint(0)?;
20960 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
20961 if let Some(t) = telemetry.as_mut() {
20962 t.bailout_reason = Some("converged");
20963 }
20964 break;
20965 }
20966 if replan_iterations >= MAX_REPLAN {
20967 if let Some(mut t) = telemetry.take() {
20968 t.bailout_reason = Some("max_replan");
20969 t.replan_iterations = replan_iterations;
20970 self.last_virtual_dep_telemetry = t;
20971 }
20972 return Err(
20973 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
20974 );
20975 }
20976 replan_iterations += 1;
20977 }
20978
20979 if let Some(mut t) = telemetry {
20980 t.replan_iterations = replan_iterations;
20981 self.last_virtual_dep_telemetry = t;
20982 }
20983
20984 self.redirty_for_next_recalc();
20985 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
20986
20987 Ok(EvalResult {
20988 computed_vertices,
20989 cycle_errors,
20990 elapsed: start.elapsed(),
20991 })
20992 })();
20993 log.end_compound();
20994 result
20995 }
20996
20997 fn evaluate_layer_logged(
20999 &mut self,
21000 layer: &super::scheduler::Layer,
21001 log: &mut ChangeLog,
21002 ) -> Result<usize, ExcelError> {
21003 self.resource_checkpoint(layer.vertices.len() as u64)?;
21004 self.evaluate_layer_units(layer, None, Some(log), None, true)
21005 }
21006}