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 spill_blocker_waiters: SpillBlockerWaiters,
1081 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
1083 row_visibility_mask_cache: std::sync::RwLock<
1085 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
1086 >,
1087 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
1089 last_formula_ingest_report: Option<FormulaIngestReport>,
1091 formula_ingest_report_total: FormulaIngestReport,
1093 active_cancel_flag: Option<crate::engine::CancelToken>,
1095 active_evaluation_deadline: Option<Instant>,
1097
1098 action_depth: u32,
1103
1104 last_virtual_dep_telemetry: VirtualDepTelemetry,
1106 virtual_dep_fallback_activations: u64,
1107
1108 last_cycle_telemetry: CycleTelemetry,
1110
1111 next_evaluation_resource_request_id: u64,
1113 evaluation_resource_request_depth: usize,
1114 active_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1115 last_evaluation_resource_request: Option<EvaluationResourceRequestStats>,
1116 evaluation_resource_baseline: EvaluationResourceBaselineStats,
1117 evaluation_resource_request_started_at: Option<crate::instant::FzInstant>,
1118 evaluation_resource_budgets: crate::engine::EvaluationBudgets,
1119 evaluation_resource_config_diagnostic:
1120 Option<crate::engine::EvaluationResourceConfigDiagnostic>,
1121 active_resource_ledger: Option<ResourceLedger>,
1122 source_cache_footprints: Vec<std::sync::Weak<std::sync::atomic::AtomicU64>>,
1123 source_cache_accounted: u64,
1124
1125 pending_iterative_redirty: Vec<VertexId>,
1144 snapshot_evaluation_active: bool,
1146 snapshot_volatile_redirty_pending: bool,
1147 retained_scc_members: FxHashMap<VertexId, u64>,
1165 next_retained_scc_id: u64,
1166 retained_scc_config_fingerprint: u64,
1173 retained_scc_function_epoch_seen: u64,
1178 retained_scc_provider_revision_seen: Option<u64>,
1179 retained_scc_dirty_at_begin: Vec<(VertexId, u64)>,
1186
1187 iterative_state_values: FxHashMap<VertexId, LiteralValue>,
1200
1201 function_semantic_epoch_seen: u64,
1203 function_provider_revision_seen: Option<u64>,
1205
1206 #[cfg(feature = "tracing")]
1207 trace_evaluation_counters: TraceEvaluationCounters,
1208 #[cfg(test)]
1209 evaluation_request_begin_count_for_test: u64,
1210 #[cfg(any(test, feature = "test-support"))]
1211 before_prepared_span_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1212 #[cfg(test)]
1213 before_target_preparation_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1214 #[cfg(test)]
1215 before_target_planning_snapshot_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1216 #[cfg(test)]
1217 inject_target_semantic_stale_once_for_test: bool,
1218 #[cfg(test)]
1219 force_virtual_dep_changes_remaining_for_test: usize,
1220 freshness: freshness::Freshness,
1222 #[cfg(test)]
1223 fail_evaluation_commit_preflight_once_for_test: bool,
1224 #[cfg(test)]
1225 target_preparation_fault_for_test:
1226 Option<crate::engine::target_preparation::TargetPreparationFault>,
1227 #[cfg(test)]
1228 force_non_cycle_schedule_fallback_for_test: bool,
1229 #[cfg(test)]
1230 before_legacy_fallback_final_provider_sample_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1231 #[cfg(test)]
1232 after_eager_proposal_commit_hook: Option<Box<dyn FnOnce() + Send + Sync>>,
1233}
1234
1235impl<R: EvaluationContext> Engine<R> {
1238 pub(crate) fn ingest_pipeline(&mut self) -> crate::engine::ingest_pipeline::IngestPipeline<'_> {
1239 self.graph.ingest_pipeline(&self.resolver)
1240 }
1241}
1242
1243pub struct EngineAction<'a, R>
1244where
1245 R: EvaluationContext,
1246{
1247 engine: &'a mut Engine<R>,
1248 name: String,
1249 capture: Option<*mut MutationCapture>,
1252 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
1255 atomic_policy: bool,
1257}
1258
1259impl<'a, R> EngineAction<'a, R>
1260where
1261 R: EvaluationContext,
1262{
1263 #[inline]
1264 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
1265 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1266 let coord = crate::reference::Coord::from_excel(row, col, true, true);
1267 crate::reference::CellRef::new(sheet_id, coord)
1268 }
1269
1270 #[inline]
1271 pub fn name(&self) -> &str {
1272 &self.name
1273 }
1274
1275 #[inline]
1276 pub fn set_cell_value(
1277 &mut self,
1278 sheet: &str,
1279 row: u32,
1280 col: u32,
1281 value: LiteralValue,
1282 ) -> Result<(), crate::engine::EditorError> {
1283 if self.capture.is_some() {
1284 let old_value = self.engine.read_cell_value(sheet, row, col);
1285 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1286 let addr = self.addr_for(sheet, row, col);
1287 let Some(capture_ptr) = self.capture else {
1288 return Err(crate::engine::EditorError::TransactionFailed {
1289 reason: "action_with_logger: missing mutation capture".to_string(),
1290 });
1291 };
1292
1293 let old_comp = if self.arrow_undo.is_some() {
1296 self.engine.read_computed_overlay_cell(sheet, row, col)
1297 } else {
1298 None
1299 };
1300
1301 if self.engine.graph_admission_enabled() {
1302 let admission =
1303 self.engine
1304 .graph
1305 .preview_value_mutation(addr.sheet_id, row, col)?;
1306 self.engine.preflight_graph_admission(admission)?;
1307 }
1308 if old_formula.is_none() {
1309 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1310 }
1311
1312 let delta_old_sem = if old_formula.is_some() {
1313 None
1314 } else {
1315 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1316 };
1317
1318 let start_len = unsafe { (&*capture_ptr).len() };
1319
1320 let capture = unsafe { &mut *capture_ptr };
1322 self.engine.edit_with_capture(capture, |editor| {
1323 editor.set_cell_value_with_old_state(
1324 addr,
1325 value.clone(),
1326 old_value.clone(),
1327 old_formula.clone(),
1328 );
1329 })?;
1330 self.engine.record_structural_change(StructuralScope::Cell {
1331 sheet: addr.sheet_id,
1332 row: addr.coord.row(),
1333 col: addr.coord.col(),
1334 });
1335
1336 if let Some(undo_ptr) = self.arrow_undo {
1337 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1339 let undo = unsafe { &mut *undo_ptr };
1340 self.engine
1341 .record_spill_ops_into_arrow_undo(undo, new_events);
1342
1343 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
1345 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1346 let row0 = row.saturating_sub(1);
1347 let col0 = col.saturating_sub(1);
1348 let delta_new_sem = Some(value.clone());
1349 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
1350 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
1351 }
1352 Ok(())
1353 } else {
1354 self.engine
1355 .set_cell_value(sheet, row, col, value)
1356 .map_err(crate::engine::EditorError::from)
1357 }
1358 }
1359
1360 #[inline]
1361 pub fn set_cell_formula(
1362 &mut self,
1363 sheet: &str,
1364 row: u32,
1365 col: u32,
1366 ast: ASTNode,
1367 ) -> Result<(), crate::engine::EditorError> {
1368 if self.capture.is_some() {
1369 let old_value = self.engine.read_cell_value(sheet, row, col);
1370 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1371 let addr = self.addr_for(sheet, row, col);
1372 let Some(capture_ptr) = self.capture else {
1373 return Err(crate::engine::EditorError::TransactionFailed {
1374 reason: "action_with_logger: missing mutation capture".to_string(),
1375 });
1376 };
1377
1378 let admitted_formula = if self.engine.graph_admission_enabled() {
1379 let placement =
1380 CellRef::new(addr.sheet_id, Coord::from_excel(row, col, true, true));
1381 let ingested = self.engine.ingest_pipeline().ingest_formula(
1382 FormulaAstInput::Tree(ast.clone()),
1383 placement,
1384 None,
1385 )?;
1386 let admission = self.engine.graph.preview_formula_mutations(&[(
1387 addr.sheet_id,
1388 row,
1389 col,
1390 ingested.dep_plan.clone(),
1391 )])?;
1392 self.engine.preflight_graph_admission(admission)?;
1393 Some((ingested.ast_id, ingested.dep_plan))
1394 } else {
1395 None
1396 };
1397 if old_formula.is_none() {
1398 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
1399 }
1400 let delta_old = if self.arrow_undo.is_some() {
1401 if old_formula.is_some() {
1402 None
1403 } else {
1404 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
1405 }
1406 } else {
1407 None
1408 };
1409 let start_len = unsafe { (&*capture_ptr).len() };
1410
1411 let capture = unsafe { &mut *capture_ptr };
1413 self.engine.edit_with_capture(capture, |editor| {
1414 if let Some((ast_id, plan)) = admitted_formula {
1415 editor.set_cell_formula_with_prepared_plan(
1416 addr,
1417 ast.clone(),
1418 old_value,
1419 old_formula,
1420 ast_id,
1421 plan,
1422 );
1423 } else {
1424 editor.set_cell_formula_with_old_state(
1425 addr,
1426 ast.clone(),
1427 old_value,
1428 old_formula,
1429 );
1430 }
1431 })?;
1432 self.engine.record_structural_change(StructuralScope::Cell {
1433 sheet: addr.sheet_id,
1434 row: addr.coord.row(),
1435 col: addr.coord.col(),
1436 });
1437
1438 if let Some(undo_ptr) = self.arrow_undo {
1439 let new_events = &unsafe { (&*capture_ptr).events() }[start_len..];
1440 let undo = unsafe { &mut *undo_ptr };
1441 self.engine
1442 .record_spill_ops_into_arrow_undo(undo, new_events);
1443 let delta_new: Option<LiteralValue> = None;
1444 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1445 let row0 = row.saturating_sub(1);
1446 let col0 = col.saturating_sub(1);
1447 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
1448 }
1449 Ok(())
1450 } else {
1451 self.engine
1452 .set_cell_formula(sheet, row, col, ast)
1453 .map_err(crate::engine::EditorError::from)
1454 }
1455 }
1456
1457 #[inline]
1458 pub fn set_row_hidden(
1459 &mut self,
1460 sheet: &str,
1461 row_1based: u32,
1462 hidden: bool,
1463 source: RowVisibilitySource,
1464 ) -> Result<(), crate::engine::EditorError> {
1465 if self.capture.is_some() {
1466 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1467 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
1468 let old_hidden = self
1469 .engine
1470 .row_visibility
1471 .get(&sheet_id)
1472 .map(|state| state.is_row_hidden(row0, Some(source)))
1473 .unwrap_or(false);
1474 if old_hidden == hidden {
1475 return Ok(());
1476 }
1477
1478 let _ = self
1479 .engine
1480 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1481
1482 let Some(capture_ptr) = self.capture else {
1483 return Err(crate::engine::EditorError::TransactionFailed {
1484 reason: "action_with_logger: missing mutation capture".to_string(),
1485 });
1486 };
1487 unsafe { &mut *capture_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
1488 sheet_id,
1489 row0,
1490 source,
1491 old_hidden,
1492 new_hidden: hidden,
1493 });
1494
1495 Ok(())
1496 } else {
1497 self.engine
1498 .set_row_hidden(sheet, row_1based, hidden, source)
1499 }
1500 }
1501
1502 #[inline]
1503 pub fn set_rows_hidden(
1504 &mut self,
1505 sheet: &str,
1506 start_row_1based: u32,
1507 end_row_1based: u32,
1508 hidden: bool,
1509 source: RowVisibilitySource,
1510 ) -> Result<(), crate::engine::EditorError> {
1511 if self.capture.is_some() {
1512 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
1513 let (start_row0, end_row0) =
1514 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
1515
1516 let Some(capture_ptr) = self.capture else {
1517 return Err(crate::engine::EditorError::TransactionFailed {
1518 reason: "action_with_logger: missing mutation capture".to_string(),
1519 });
1520 };
1521 let capture = unsafe { &mut *capture_ptr };
1522
1523 for row0 in start_row0..=end_row0 {
1524 let old_hidden = self
1525 .engine
1526 .row_visibility
1527 .get(&sheet_id)
1528 .map(|state| state.is_row_hidden(row0, Some(source)))
1529 .unwrap_or(false);
1530 if old_hidden == hidden {
1531 continue;
1532 }
1533
1534 let _ = self
1535 .engine
1536 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
1537
1538 capture.record(crate::engine::ChangeEvent::SetRowVisibility {
1539 sheet_id,
1540 row0,
1541 source,
1542 old_hidden,
1543 new_hidden: hidden,
1544 });
1545 }
1546
1547 Ok(())
1548 } else {
1549 self.engine
1550 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
1551 }
1552 }
1553
1554 #[inline]
1555 pub fn insert_rows(
1556 &mut self,
1557 sheet: &str,
1558 before: u32,
1559 count: u32,
1560 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1561 if count == 0 {
1562 return Ok(crate::engine::ShiftSummary::default());
1563 }
1564 if self.capture.is_some() {
1565 let Some(capture_ptr) = self.capture else {
1566 return Err(crate::engine::EditorError::TransactionFailed {
1567 reason: "action_atomic: missing mutation capture".to_string(),
1568 });
1569 };
1570
1571 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1572 let before0 = before.saturating_sub(1);
1573 let occupancy = self.engine.structural_row_occupancy(sheet, sheet_id);
1574 let affected_region = Engine::<R>::structural_row_region(sheet_id, before0);
1575
1576 let summary = {
1578 let capture = unsafe { &mut *capture_ptr };
1579 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1580 Ok(crate::engine::ShiftSummary::default());
1581 self.engine.edit_with_capture(capture, |editor| {
1582 editor.set_structural_occupancy(occupancy);
1583 out = editor.insert_rows(sheet_id, before0, count);
1584 })?;
1585 out?
1586 };
1587
1588 self.engine.ensure_arrow_sheet(sheet);
1590 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1591 asheet.insert_rows(before0 as usize, count as usize);
1592 }
1593 self.engine
1594 .purge_derived_formats_after_row(sheet_id, before0);
1595 self.engine
1596 .shift_row_visibility_insert(sheet_id, before0, count);
1597 self.engine.mark_moved_formula_vertices_dirty(&summary);
1598 self.engine
1599 .clear_computed_overlay_after_row(sheet, before0 as usize);
1600 self.engine
1601 .record_structural_change(StructuralScope::Region(affected_region));
1602 if let Some(undo_ptr) = self.arrow_undo {
1603 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
1604 }
1605 Ok(summary)
1606 } else {
1607 self.engine.insert_rows(sheet, before, count)
1608 }
1609 }
1610
1611 #[inline]
1612 pub fn delete_rows(
1613 &mut self,
1614 sheet: &str,
1615 start: u32,
1616 count: u32,
1617 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1618 if count == 0 {
1619 return Ok(crate::engine::ShiftSummary::default());
1620 }
1621 if self.atomic_policy {
1622 return Err(crate::engine::EditorError::TransactionUnsupported {
1623 reason:
1624 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
1625 .to_string(),
1626 });
1627 }
1628 self.engine.delete_rows(sheet, start, count)
1629 }
1630
1631 #[inline]
1632 pub fn insert_columns(
1633 &mut self,
1634 sheet: &str,
1635 before: u32,
1636 count: u32,
1637 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1638 if count == 0 {
1639 return Ok(crate::engine::ShiftSummary::default());
1640 }
1641 if self.capture.is_some() {
1642 let Some(capture_ptr) = self.capture else {
1643 return Err(crate::engine::EditorError::TransactionFailed {
1644 reason: "action_atomic: missing mutation capture".to_string(),
1645 });
1646 };
1647
1648 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
1649 let before0 = before.saturating_sub(1);
1650 let occupancy = self.engine.structural_column_occupancy();
1651 let affected_region = Engine::<R>::structural_col_region(sheet_id, before0);
1652
1653 let summary = {
1654 let capture = unsafe { &mut *capture_ptr };
1655 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
1656 Ok(crate::engine::ShiftSummary::default());
1657 self.engine.edit_with_capture(capture, |editor| {
1658 editor.set_structural_occupancy(occupancy);
1659 out = editor.insert_columns(sheet_id, before0, count);
1660 })?;
1661 out?
1662 };
1663
1664 self.engine.ensure_arrow_sheet(sheet);
1665 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
1666 asheet.insert_columns(before0 as usize, count as usize);
1667 }
1668 self.engine
1669 .purge_derived_formats_after_col(sheet_id, before0);
1670 self.engine.mark_moved_formula_vertices_dirty(&summary);
1671 self.engine
1672 .clear_computed_overlay_after_col(sheet, before0 as usize);
1673 self.engine
1674 .record_structural_change(StructuralScope::Region(affected_region));
1675 if let Some(undo_ptr) = self.arrow_undo {
1676 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
1677 }
1678 Ok(summary)
1679 } else {
1680 self.engine.insert_columns(sheet, before, count)
1681 }
1682 }
1683
1684 #[inline]
1685 pub fn delete_columns(
1686 &mut self,
1687 sheet: &str,
1688 start: u32,
1689 count: u32,
1690 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
1691 if count == 0 {
1692 return Ok(crate::engine::ShiftSummary::default());
1693 }
1694 if self.atomic_policy {
1695 return Err(crate::engine::EditorError::TransactionUnsupported {
1696 reason:
1697 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
1698 .to_string(),
1699 });
1700 }
1701 self.engine.delete_columns(sheet, start, count)
1702 }
1703
1704 #[inline]
1709 pub fn action<T>(
1710 &mut self,
1711 name: impl AsRef<str>,
1712 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
1713 ) -> Result<T, crate::engine::EditorError> {
1714 self.engine.action(name, f)
1715 }
1716}
1717
1718struct ActionDepthGuard<'a, R> {
1719 engine: *mut Engine<R>,
1720 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
1721}
1722
1723impl<'a, R> Drop for ActionDepthGuard<'a, R> {
1724 fn drop(&mut self) {
1725 unsafe {
1728 let e = &mut *self.engine;
1729 e.action_depth = e.action_depth.saturating_sub(1);
1730 }
1731 }
1732}
1733
1734#[derive(Default)]
1735struct SourceCache {
1736 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
1737 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
1738}
1739
1740#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1741struct VisibilityMaskCacheKey {
1742 sheet_id: SheetId,
1743 start_row0: u32,
1744 end_row0: u32,
1745 mode: VisibilityMaskMode,
1746 version: u64,
1747}
1748
1749#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1750enum StructuralScope {
1751 Cell { sheet: SheetId, row: u32, col: u32 },
1752 Region(Region),
1753 Sheet(SheetId),
1754 RemovedSheet(SheetId),
1755 OpaqueGlobal,
1756 AllSheets,
1757}
1758
1759#[derive(Default)]
1762struct SpillBlockerWaiters {
1763 by_cell: FxHashMap<CellRef, Vec<VertexId>>,
1764 by_anchor: FxHashMap<VertexId, CellRef>,
1765}
1766
1767impl SpillBlockerWaiters {
1768 const REGION_PROBE_CELLS: u64 = 4096;
1770
1771 fn is_empty(&self) -> bool {
1772 self.by_anchor.is_empty()
1773 }
1774
1775 fn wait(&mut self, anchor: VertexId, blocker: CellRef) {
1776 match self.by_anchor.insert(anchor, blocker) {
1777 Some(old) if old == blocker => return,
1778 Some(old) => self.unlink(anchor, old),
1779 None => {}
1780 }
1781 self.by_cell.entry(blocker).or_default().push(anchor);
1782 }
1783
1784 fn forget(&mut self, anchor: VertexId) {
1785 if self.by_anchor.is_empty() {
1786 return;
1787 }
1788 if let Some(old) = self.by_anchor.remove(&anchor) {
1789 self.unlink(anchor, old);
1790 }
1791 }
1792
1793 fn unlink(&mut self, anchor: VertexId, cell: CellRef) {
1794 if let Some(waiters) = self.by_cell.get_mut(&cell) {
1795 waiters.retain(|&v| v != anchor);
1796 if waiters.is_empty() {
1797 self.by_cell.remove(&cell);
1798 }
1799 }
1800 }
1801
1802 fn take_affected(&mut self, scope: StructuralScope) -> Vec<VertexId> {
1804 let cells: Vec<CellRef> = match scope {
1805 StructuralScope::Cell { sheet, row, col } => {
1806 let cell = CellRef::new(sheet, Coord::new(row, col, true, true));
1807 if self.by_cell.contains_key(&cell) {
1808 vec![cell]
1809 } else {
1810 Vec::new()
1811 }
1812 }
1813 StructuralScope::Region(region) => {
1814 let (rows, cols) = region.axis_ranges();
1815 let (r0, r1) = rows.query_bounds();
1816 let (c0, c1) = cols.query_bounds();
1817 let area = u64::from(r1.saturating_sub(r0) + 1)
1818 .saturating_mul(u64::from(c1.saturating_sub(c0) + 1));
1819 if area <= Self::REGION_PROBE_CELLS {
1820 let sheet = region.sheet_id();
1821 (r0..=r1)
1822 .flat_map(|r| (c0..=c1).map(move |c| (r, c)))
1823 .map(|(r, c)| CellRef::new(sheet, Coord::new(r, c, true, true)))
1824 .filter(|cell| self.by_cell.contains_key(cell))
1825 .collect()
1826 } else {
1827 self.by_cell
1828 .keys()
1829 .filter(|cell| {
1830 region.intersects(&Region::point(
1831 cell.sheet_id,
1832 cell.coord.row(),
1833 cell.coord.col(),
1834 ))
1835 })
1836 .copied()
1837 .collect()
1838 }
1839 }
1840 StructuralScope::Sheet(sheet) | StructuralScope::RemovedSheet(sheet) => self
1841 .by_cell
1842 .keys()
1843 .filter(|cell| cell.sheet_id == sheet)
1844 .copied()
1845 .collect(),
1846 StructuralScope::OpaqueGlobal | StructuralScope::AllSheets => {
1847 self.by_cell.keys().copied().collect()
1848 }
1849 };
1850 let mut woken = Vec::new();
1851 for cell in cells {
1852 if let Some(waiters) = self.by_cell.remove(&cell) {
1853 for anchor in waiters {
1854 self.by_anchor.remove(&anchor);
1855 woken.push(anchor);
1856 }
1857 }
1858 }
1859 woken
1860 }
1861}
1862
1863#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1864enum LoggedEditImpact {
1865 NoOp,
1866 DataOnly,
1867 Topology,
1868}
1869
1870#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1871enum LoggedEditDirection {
1872 Original,
1873 InverseReplay,
1874 ForwardReplay,
1875}
1876
1877#[derive(Clone, Copy)]
1878struct InvalidationBaseline {
1879 snapshot_id: u64,
1880 topology_epoch: u64,
1881}
1882
1883struct SourceCacheSession {
1884 cache: Arc<std::sync::RwLock<SourceCache>>,
1885}
1886
1887impl Drop for SourceCacheSession {
1888 fn drop(&mut self) {
1889 if let Ok(mut g) = self.cache.write() {
1890 *g = SourceCache::default();
1891 }
1892 }
1893}
1894
1895#[derive(Debug)]
1896#[non_exhaustive]
1897pub struct EvalResult {
1898 pub computed_vertices: usize,
1899 pub cycle_errors: usize,
1900 pub elapsed: std::time::Duration,
1901}
1902
1903#[derive(Clone, Debug, PartialEq, Eq)]
1904#[non_exhaustive]
1905pub struct TableMetadata {
1906 pub name: String,
1907 pub sheet: String,
1908 pub start_row: u32,
1909 pub start_col: u32,
1910 pub end_row: u32,
1911 pub end_col: u32,
1912 pub header_row: bool,
1913 pub headers: Vec<String>,
1914 pub totals_row: bool,
1915}
1916
1917#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1925#[non_exhaustive]
1926pub struct EngineBaselineStats {
1927 pub graph_vertex_count: usize,
1928 pub graph_formula_vertex_count: usize,
1929 pub graph_edge_count: usize,
1930 pub dirty_vertex_count: usize,
1931 pub evaluation_vertex_count: usize,
1932 pub formula_ast_root_count: usize,
1933 pub formula_ast_node_count: usize,
1934 pub staged_formula_count: usize,
1935 pub formula_plane_active_span_count: usize,
1936 pub formula_plane_producer_result_entries: usize,
1937 pub formula_plane_consumer_read_entries: usize,
1938 pub formula_plane_mixed_topology_cache_builds: u64,
1939 pub formula_plane_mixed_topology_cache_hits: u64,
1940 pub formula_plane_mixed_topology_cache_overflows: u64,
1941 pub formula_plane_dirty_pending_events: usize,
1942 pub formula_plane_dirty_region_events_recorded: u64,
1943 pub formula_plane_dirty_span_region_events_recorded: u64,
1944 pub formula_plane_dirty_whole_span_seeds_recorded: u64,
1945 pub formula_plane_dirty_global_invalidations: u64,
1946 pub formula_plane_structural_span_candidates: u64,
1947 pub formula_plane_cycle_member_span_demotions: u64,
1948 pub formula_plane_array_result_span_demotions: u64,
1949 pub retained_scc_members: usize,
1952}
1953
1954#[derive(Debug, Clone, Default)]
1955#[non_exhaustive]
1956pub struct VirtualDepTelemetry {
1957 pub candidate_vertices_total: usize,
1958 pub vdeps_vertices_total: usize,
1959 pub vdeps_edges_total: usize,
1960 pub builder_elapsed_ms_total: u128,
1961 pub schedule_virtual_passes: usize,
1962 pub schedule_static_passes: usize,
1963 pub schedule_cache_hits: usize,
1964 pub schedule_cache_misses: usize,
1965 pub reused_schedule_vertices_total: usize,
1966 pub replan_iterations: usize,
1967 pub changed_vdeps_total: usize,
1968 pub bailout_reason: Option<&'static str>,
1969 pub fallback_mode_activations: u64,
1970}
1971
1972#[derive(Debug, Clone, Default, PartialEq)]
1981#[non_exhaustive]
1982pub struct CycleTelemetry {
1983 pub static_sccs: usize,
1985 pub phantom_sccs: usize,
1987 pub live_cycles_witnessed: usize,
1989 pub circ_cells_stamped: usize,
1991 pub settle_passes_total: usize,
1994 pub max_passes_single_scc: usize,
1996 pub iterated_sccs: usize,
1999 pub converged_sccs: usize,
2002 pub capped_sccs: usize,
2008 pub max_abs_delta_at_stop: f64,
2012 pub nan_converged: usize,
2015 pub reused_sccs: usize,
2020 pub reused_scc_members: usize,
2022 pub elapsed_ms: u128,
2024}
2025
2026#[derive(Debug, Clone, Copy)]
2027struct ScheduleBuildMeta {
2028 candidate_vertices: usize,
2029 vdeps_vertices: usize,
2030 vdeps_edges: usize,
2031 builder_elapsed_ms: u128,
2032 used_virtual_schedule: bool,
2033 schedule_cache_hit: bool,
2034 schedule_cache_eligible: bool,
2035}
2036
2037#[cfg(any(test, feature = "benchmark_internal"))]
2038#[doc(hidden)]
2039#[derive(Debug, Clone, Default)]
2040pub struct RecalcReuseProbe {
2041 pub schedule_requests: usize,
2042 pub schedule_cache_hits: usize,
2043 pub schedule_cache_misses: usize,
2044 pub schedule_cache_ineligible: usize,
2045 pub schedule_builds: usize,
2046 pub schedule_base_restrictions: usize,
2048 pub schedule_shared_handles: usize,
2049 pub schedule_retained_bytes: usize,
2050 pub legacy_target_requests: usize,
2051 pub target_schedule_builds: usize,
2052 pub demand_builds: usize,
2053 pub demand_vertices: usize,
2054 pub demand_clean_formulas: usize,
2055 pub demand_explicit_edges: usize,
2056 pub demand_virtual_builder_calls: usize,
2057}
2058
2059#[cfg(any(test, feature = "benchmark_internal"))]
2060fn schedule_probe_retained_bytes(schedule: &crate::engine::Schedule) -> usize {
2061 fn vector_bytes<T>(values: &Vec<T>) -> usize {
2062 values.capacity() * std::mem::size_of::<T>()
2063 }
2064
2065 [
2066 vector_bytes(&schedule.units),
2067 vector_bytes(&schedule.layers),
2068 vector_bytes(&schedule.cycles),
2069 ]
2070 .into_iter()
2071 .chain(
2072 schedule
2073 .layers
2074 .iter()
2075 .map(|layer| vector_bytes(&layer.vertices)),
2076 )
2077 .chain(schedule.cycles.iter().map(vector_bytes))
2078 .sum()
2079}
2080
2081#[derive(Debug, Clone)]
2082struct CachedScheduleEntry {
2083 topology_epoch: u64,
2084 authority_revision: (u64, u64),
2087 candidate_vertices: VertexIdRuns,
2090 schedule: Arc<crate::engine::scheduler::Schedule>,
2091}
2092
2093#[derive(Debug, Clone, Default)]
2096struct VertexIdRuns(Vec<(u32, u32)>);
2097
2098impl VertexIdRuns {
2099 fn from_slice(ids: &[VertexId]) -> Self {
2100 let mut runs: Vec<(u32, u32)> = Vec::new();
2101 for v in ids {
2102 match runs.last_mut() {
2103 Some((first, len)) if first.checked_add(*len) == Some(v.0) => *len += 1,
2104 _ => runs.push((v.0, 1)),
2105 }
2106 }
2107 runs.shrink_to_fit();
2108 Self(runs)
2109 }
2110
2111 fn equals(&self, ids: &[VertexId]) -> bool {
2112 let mut rest = ids;
2113 for &(first, len) in &self.0 {
2114 let len = len as usize;
2115 if rest.len() < len {
2116 return false;
2117 }
2118 let (head, tail) = rest.split_at(len);
2119 if head
2120 .iter()
2121 .enumerate()
2122 .any(|(i, v)| v.0 != first.wrapping_add(i as u32))
2123 {
2124 return false;
2125 }
2126 rest = tail;
2127 }
2128 rest.is_empty()
2129 }
2130
2131 fn heap_bytes(&self) -> usize {
2132 self.0.capacity() * std::mem::size_of::<(u32, u32)>()
2133 }
2134
2135 fn len(&self) -> usize {
2137 self.0.iter().map(|&(_, len)| len as usize).sum()
2138 }
2139}
2140
2141#[cfg(test)]
2142mod vertex_id_runs_tests {
2143 use super::{VertexId, VertexIdRuns};
2144
2145 #[test]
2146 fn runs_compare_like_the_list() {
2147 let ids = |v: &[u32]| v.iter().map(|&i| VertexId(i)).collect::<Vec<_>>();
2148 let list = ids(&[5, 6, 7, 2, 3, 9, 10, 10]);
2149 let runs = VertexIdRuns::from_slice(&list);
2150 assert_eq!(runs.0, vec![(5, 3), (2, 2), (9, 2), (10, 1)]);
2151 assert!(runs.equals(&list));
2152 assert!(!runs.equals(&list[..7]));
2153 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 11])));
2154 assert!(!runs.equals(&ids(&[5, 6, 7, 2, 3, 9, 10, 10, 11])));
2155 assert!(VertexIdRuns::from_slice(&[]).equals(&[]));
2156 assert!(!VertexIdRuns::from_slice(&[]).equals(&list));
2157 let edge = ids(&[u32::MAX - 1, u32::MAX, 0]);
2158 assert!(VertexIdRuns::from_slice(&edge).equals(&edge));
2159 }
2160}
2161
2162enum EvaluationSchedule {
2164 Owned(crate::engine::scheduler::Schedule),
2165 Shared(Arc<crate::engine::scheduler::Schedule>),
2166}
2167
2168impl std::ops::Deref for EvaluationSchedule {
2169 type Target = crate::engine::scheduler::Schedule;
2170
2171 fn deref(&self) -> &Self::Target {
2172 match self {
2173 Self::Owned(schedule) => schedule,
2174 Self::Shared(schedule) => schedule,
2175 }
2176 }
2177}
2178
2179type ScheduleBuildOutput = (
2180 crate::engine::scheduler::Schedule,
2181 FxHashMap<VertexId, Vec<VertexId>>,
2182 ScheduleBuildMeta,
2183);
2184
2185type EvaluationScheduleBuildOutput = (
2186 EvaluationSchedule,
2187 FxHashMap<VertexId, Vec<VertexId>>,
2188 ScheduleBuildMeta,
2189);
2190
2191#[derive(Debug)]
2193pub struct RecalcPlan {
2194 key: RecalcPlanKey,
2195 kind: RecalcPlanKind,
2196}
2197
2198#[derive(Debug)]
2199struct RecalcPlanKey {
2200 engine_token: Arc<()>,
2201 revisions: PlanningRevisionSnapshot,
2202}
2203
2204#[derive(Clone, Debug, PartialEq, Eq)]
2205struct PlanningRevisionSnapshot {
2206 engine_topology_epoch: u64,
2207 graph_topology_revision: u64,
2208 staged: u64,
2209 symbols: u64,
2210 semantic: u64,
2211 provider: Option<u64>,
2212 deterministic_mode: crate::engine::DeterministicMode,
2213 budgets: crate::engine::EvaluationBudgets,
2214}
2215
2216#[derive(Debug)]
2217enum RecalcPlanKind {
2218 CompatibilityFull {
2219 schedule: crate::engine::Schedule,
2220 has_dynamic_refs: bool,
2221 },
2222 Target {
2223 targets: Vec<crate::engine::EvaluationTarget>,
2224 scope: crate::engine::PrepareScope,
2225 topology: RecalcTopology,
2226 dynamic_policy: DynamicPlanPolicy,
2227 },
2228}
2229
2230#[derive(Debug)]
2231enum RecalcTopology {
2232 RunLocalRecipe,
2233 Workbook,
2234}
2235
2236#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2237enum DynamicPlanPolicy {
2238 BoundedTargetReplan,
2239}
2240
2241impl RecalcPlan {
2242 pub fn layer_count(&self) -> usize {
2245 match &self.kind {
2246 RecalcPlanKind::CompatibilityFull { schedule, .. } => schedule.layers.len(),
2247 RecalcPlanKind::Target { .. } => 0,
2248 }
2249 }
2250
2251 pub fn has_dynamic_refs(&self) -> bool {
2252 match &self.kind {
2253 RecalcPlanKind::CompatibilityFull {
2254 has_dynamic_refs, ..
2255 } => *has_dynamic_refs,
2256 RecalcPlanKind::Target { .. } => false,
2257 }
2258 }
2259
2260 #[cfg(test)]
2261 pub(crate) fn force_stale_reasons_for_test(
2262 &mut self,
2263 reasons: &[formualizer_common::PlanStaleReason],
2264 ) {
2265 use formualizer_common::PlanStaleReason;
2266 for reason in reasons {
2267 match reason {
2268 PlanStaleReason::Engine => {
2269 self.key.engine_token = Arc::new(());
2270 }
2271 PlanStaleReason::Provider => {
2272 self.key.revisions.provider = Some(
2273 self.key
2274 .revisions
2275 .provider
2276 .unwrap_or_default()
2277 .wrapping_add(1),
2278 );
2279 }
2280 PlanStaleReason::Semantic => {
2281 self.key.revisions.semantic = self.key.revisions.semantic.wrapping_add(1);
2282 }
2283 PlanStaleReason::Budget => {
2284 let current = self.key.revisions.budgets.work.max_work_units;
2285 self.key.revisions.budgets.work.max_work_units =
2286 Some(current.unwrap_or_default().wrapping_add(1));
2287 }
2288 PlanStaleReason::Staged => {
2289 self.key.revisions.staged = self.key.revisions.staged.wrapping_add(1);
2290 }
2291 PlanStaleReason::Symbols => {
2292 self.key.revisions.symbols = self.key.revisions.symbols.wrapping_add(1);
2293 }
2294 PlanStaleReason::Graph => {
2295 self.key.revisions.graph_topology_revision =
2296 self.key.revisions.graph_topology_revision.wrapping_add(1);
2297 }
2298 _ => {}
2299 }
2300 }
2301 }
2302}
2303
2304#[cfg(any(test, feature = "test-support"))]
2305pub(crate) mod criteria_mask_test_hooks {
2306 use std::cell::Cell;
2307
2308 thread_local! {
2309 static MASK_CALLS_ROWS: Cell<(usize, usize)> = const { Cell::new((0, 0)) };
2310 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
2311 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
2312 }
2313
2314 pub(crate) fn take_mask_work() -> (usize, usize) {
2315 MASK_CALLS_ROWS.with(|c| c.replace((0, 0)))
2316 }
2317
2318 pub(crate) fn note_mask(rows: usize) {
2319 MASK_CALLS_ROWS.with(|c| {
2320 let (calls, work) = c.get();
2321 c.set((calls + 1, work + rows));
2322 });
2323 }
2324
2325 pub fn reset_text_segment_counters() {
2326 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
2327 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
2328 }
2329
2330 pub fn text_segment_counters() -> (usize, usize) {
2331 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
2332 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
2333 (a, b)
2334 }
2335
2336 pub(crate) fn inc_total() {
2337 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
2338 }
2339 pub(crate) fn inc_all_null() {
2340 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
2341 }
2342}
2343
2344#[cfg(test)]
2345pub(crate) mod visibility_mask_test_hooks {
2346 use std::cell::Cell;
2347
2348 thread_local! {
2349 static HITS: Cell<usize> = const { Cell::new(0) };
2350 static MISSES: Cell<usize> = const { Cell::new(0) };
2351 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
2352 }
2353
2354 pub fn reset() {
2355 HITS.with(|c| c.set(0));
2356 MISSES.with(|c| c.set(0));
2357 EVICTIONS.with(|c| c.set(0));
2358 }
2359
2360 pub fn counters() -> (usize, usize, usize) {
2361 let hits = HITS.with(|c| c.get());
2362 let misses = MISSES.with(|c| c.get());
2363 let evictions = EVICTIONS.with(|c| c.get());
2364 (hits, misses, evictions)
2365 }
2366
2367 pub(crate) fn inc_hit() {
2368 HITS.with(|c| c.set(c.get() + 1));
2369 }
2370
2371 pub(crate) fn inc_miss() {
2372 MISSES.with(|c| c.set(c.get() + 1));
2373 }
2374
2375 pub(crate) fn inc_eviction() {
2376 EVICTIONS.with(|c| c.set(c.get() + 1));
2377 }
2378}
2379
2380fn is_numeric_text_equality(pred: &crate::args::CriteriaPredicate) -> bool {
2381 match pred {
2382 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(text)) => {
2383 text.trim().parse::<f64>().is_ok_and(f64::is_finite)
2384 }
2385 _ => false,
2386 }
2387}
2388
2389fn fill_numeric_equality_nulls(
2394 view: &RangeView<'_>,
2395 row_start: usize,
2396 row_len: usize,
2397 col_in_view: usize,
2398 mask: &arrow_array::BooleanArray,
2399 n: f64,
2400 ne: bool,
2401) -> Option<arrow_array::BooleanArray> {
2402 use arrow_array::Array as _;
2403 let texts = view.slice_lowered_text(row_start, row_len);
2404 let text = texts.get(col_in_view).and_then(|t| t.as_ref());
2405 match text {
2406 None => {
2407 if !ne {
2408 return Some(mask.clone());
2410 }
2411 let nulls = arrow::compute::is_null(mask).ok()?;
2412 crate::compute_prelude::boolean::or_kleene(mask, &nulls).ok()
2413 }
2414 Some(text) => {
2415 if text.len() != mask.len() {
2416 return None;
2417 }
2418 let mut out = arrow_array::builder::BooleanBuilder::with_capacity(mask.len());
2419 for i in 0..mask.len() {
2420 if mask.is_valid(i) {
2421 out.append_value(mask.value(i));
2422 } else if text.is_valid(i) {
2423 let equal = crate::locale::Locale::invariant()
2424 .parse_number_invariant(text.value(i))
2425 .is_some_and(|x| (x - n).abs() < 1e-12);
2426 out.append_value(equal != ne);
2427 } else {
2428 out.append_value(ne);
2429 }
2430 }
2431 Some(out.finish())
2432 }
2433 }
2434}
2435
2436fn compute_criteria_mask(
2437 view: &RangeView<'_>,
2438 col_in_view: usize,
2439 pred: &crate::args::CriteriaPredicate,
2440) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
2441 use crate::compute_prelude::{boolean, cmp, concat_arrays};
2442 use arrow::compute::kernels::comparison::{ilike, nilike};
2443 use arrow_array::{
2444 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
2445 };
2446
2447 fn apply_numeric_pred(
2449 chunk: &Float64Array,
2450 pred: &crate::args::CriteriaPredicate,
2451 ) -> Option<BooleanArray> {
2452 match pred {
2453 crate::args::CriteriaPredicate::Gt(n) => {
2454 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
2455 }
2456 crate::args::CriteriaPredicate::Ge(n) => {
2457 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2458 }
2459 crate::args::CriteriaPredicate::Lt(n) => {
2460 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
2461 }
2462 crate::args::CriteriaPredicate::Le(n) => {
2463 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
2464 }
2465 crate::args::CriteriaPredicate::Eq(v) => match v {
2466 formualizer_common::LiteralValue::Number(x) => {
2467 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
2468 }
2469 formualizer_common::LiteralValue::Int(i) => {
2470 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2471 }
2472 _ => None,
2473 },
2474 crate::args::CriteriaPredicate::Ne(v) => match v {
2475 formualizer_common::LiteralValue::Number(x) => {
2476 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
2477 }
2478 formualizer_common::LiteralValue::Int(i) => {
2479 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
2480 }
2481 _ => None,
2482 },
2483 _ => None,
2484 }
2485 }
2486
2487 let is_numeric_pred = matches!(
2489 pred,
2490 crate::args::CriteriaPredicate::Gt(_)
2491 | crate::args::CriteriaPredicate::Ge(_)
2492 | crate::args::CriteriaPredicate::Lt(_)
2493 | crate::args::CriteriaPredicate::Le(_)
2494 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
2495 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
2496 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
2497 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
2498 );
2499
2500 if is_numeric_pred {
2504 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2505 let number = |v: &formualizer_common::LiteralValue| match v {
2509 formualizer_common::LiteralValue::Number(n) => Some(*n),
2510 formualizer_common::LiteralValue::Int(i) => Some(*i as f64),
2511 _ => None,
2512 };
2513 let equality = match pred {
2514 crate::args::CriteriaPredicate::Eq(v) => number(v).map(|n| (n, false)),
2515 crate::args::CriteriaPredicate::Ne(v) => number(v).map(|n| (n, true)),
2516 _ => None,
2517 };
2518 for res in view.numbers_slices() {
2519 let (rs, rl, cols_seg) = res.ok()?;
2520 if col_in_view < cols_seg.len() {
2521 let chunk = cols_seg[col_in_view].as_ref();
2522 let mut mask = apply_numeric_pred(chunk, pred)?;
2523 if let Some((n, ne)) = equality
2524 && mask.null_count() > 0
2525 {
2526 mask = fill_numeric_equality_nulls(view, rs, rl, col_in_view, &mask, n, ne)?;
2527 }
2528 bool_parts.push(mask);
2529 }
2530 }
2531
2532 if bool_parts.is_empty() {
2533 return None;
2534 } else if bool_parts.len() == 1 {
2535 return Some(std::sync::Arc::new(bool_parts.remove(0)));
2536 } else {
2537 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2539 .iter()
2540 .map(|a| a as &dyn arrow_array::Array)
2541 .collect();
2542 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2543 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2544 return Some(std::sync::Arc::new(ba));
2545 }
2546 }
2547
2548 if is_numeric_text_equality(pred)
2552 || matches!(pred, crate::args::CriteriaPredicate::TextLike { .. })
2553 {
2554 for tags in view.type_tags_slices() {
2555 let (_, _, cols) = tags.ok()?;
2556 let tags = cols.get(col_in_view)?;
2557 if tags.values().iter().any(|tag| {
2558 *tag == crate::arrow_store::TypeTag::Empty as u8
2559 || *tag == crate::arrow_store::TypeTag::Number as u8
2560 || *tag == crate::arrow_store::TypeTag::Boolean as u8
2561 }) {
2562 let mut mask = BooleanBuilder::new();
2563 for chunk in view.iter_row_chunks() {
2564 let chunk = chunk.ok()?;
2565 for row in chunk.row_start..chunk.row_start + chunk.row_len {
2566 mask.append_value(crate::builtins::criteria_match(
2567 pred,
2568 &view.get_cell(row, col_in_view),
2569 ));
2570 }
2571 }
2572 return Some(std::sync::Arc::new(mask.finish()));
2573 }
2574 }
2575 }
2576
2577 if matches!(pred, crate::args::CriteriaPredicate::TextLike { pattern, .. }
2581 if pattern.contains(['~', '%', '_', '\\']))
2582 {
2583 return None;
2584 }
2585
2586 if matches!(
2590 pred,
2591 crate::args::CriteriaPredicate::IsBlank | crate::args::CriteriaPredicate::NotBlank
2592 ) {
2593 let want_blank = matches!(pred, crate::args::CriteriaPredicate::IsBlank);
2594 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2595 for tags in view.type_tags_slices() {
2596 let (_, _, cols) = tags.ok()?;
2597 let tags = cols.get(col_in_view)?;
2598 let mut bb = BooleanBuilder::with_capacity(tags.len());
2599 for i in 0..tags.len() {
2600 let blank = tags.value(i) == crate::arrow_store::TypeTag::Empty as u8;
2601 bb.append_value(blank == want_blank);
2602 }
2603 bool_parts.push(bb.finish());
2604 }
2605 return match bool_parts.len() {
2606 0 => None,
2607 1 => Some(std::sync::Arc::new(bool_parts.remove(0))),
2608 _ => {
2609 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2610 .iter()
2611 .map(|a| a as &dyn arrow_array::Array)
2612 .collect();
2613 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2614 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2615 Some(std::sync::Arc::new(ba))
2616 }
2617 };
2618 }
2619
2620 let (text_kind, text_pat, empty_special) = match pred {
2621 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
2622 (0u8, t.to_lowercase(), t.is_empty())
2623 }
2624 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
2625 (1u8, t.to_lowercase(), false)
2626 }
2627 crate::args::CriteriaPredicate::TextLike {
2628 pattern,
2629 case_insensitive,
2630 } => {
2631 let p = if *case_insensitive {
2632 pattern.to_lowercase()
2633 } else {
2634 pattern.clone()
2635 };
2636 (2u8, p.replace('*', "%").replace('?', "_"), false)
2637 }
2638 _ => return None,
2639 };
2640
2641 let text_pat_is_empty = text_pat.is_empty();
2642 let ne_matches_blank = text_kind == 1 && !text_pat_is_empty;
2643 let pat = StringArray::new_scalar(text_pat);
2644 let mut bool_parts: Vec<BooleanArray> = Vec::new();
2645
2646 let mut tag_slices = view.type_tags_slices();
2647 for res in view.iter_row_chunks() {
2648 let cs = res.ok()?;
2649 if cs.row_len == 0 {
2650 continue;
2651 }
2652 #[cfg(test)]
2653 criteria_mask_test_hooks::inc_total();
2654
2655 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
2656 if col_in_view >= slices.len() {
2657 return None;
2658 }
2659
2660 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
2661 if empty_special || (text_kind == 1 && text_pat_is_empty) {
2662 let (tag_start, tag_len, tags) = tag_slices.next()?.ok()?;
2663 if tag_start != cs.row_start || tag_len != cs.row_len {
2664 return None;
2665 }
2666 let tags = tags.get(col_in_view)?;
2667 let strings = seg_opt.and_then(|a| a.as_any().downcast_ref::<StringArray>());
2671 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2672 for i in 0..cs.row_len {
2673 let blank = tags.value(i) == crate::arrow_store::TypeTag::Empty as u8
2674 || (tags.value(i) == crate::arrow_store::TypeTag::Text as u8
2675 && strings.is_some_and(|s| s.is_valid(i) && s.value(i).is_empty()));
2676 bb.append_value(if text_kind == 0 { blank } else { !blank });
2677 }
2678 #[cfg(test)]
2679 if seg_opt.is_none() {
2680 criteria_mask_test_hooks::inc_all_null();
2681 }
2682 bool_parts.push(bb.finish());
2683 continue;
2684 }
2685 let seg = match seg_opt {
2686 Some(s) => s,
2687 None => {
2688 #[cfg(test)]
2689 criteria_mask_test_hooks::inc_all_null();
2690 if (text_kind == 0 && empty_special) || ne_matches_blank {
2691 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
2693 bb.append_n(cs.row_len, true);
2694 bool_parts.push(bb.finish());
2695 } else {
2696 bool_parts.push(BooleanArray::new_null(cs.row_len));
2698 }
2699 continue;
2700 }
2701 };
2702
2703 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
2704 let mut m = match text_kind {
2705 0 => ilike(seg_sa, &pat).ok()?,
2706 1 => nilike(seg_sa, &pat).ok()?,
2707 2 => ilike(seg_sa, &pat).ok()?,
2708 _ => return None,
2709 };
2710
2711 if ((text_kind == 0 && empty_special) || ne_matches_blank) && seg_sa.null_count() > 0 {
2717 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
2719 for i in 0..seg_sa.len() {
2720 bb.append_value(seg_sa.is_null(i));
2721 }
2722 let nulls = bb.finish();
2723 m = boolean::or_kleene(&m, &nulls).ok()?;
2724 }
2725
2726 bool_parts.push(m);
2727 }
2728
2729 if bool_parts.is_empty() {
2730 None
2731 } else if bool_parts.len() == 1 {
2732 Some(std::sync::Arc::new(bool_parts.remove(0)))
2733 } else {
2734 let anys: Vec<&dyn arrow_array::Array> = bool_parts
2735 .iter()
2736 .map(|a| a as &dyn arrow_array::Array)
2737 .collect();
2738 let conc: ArrayRef = concat_arrays(&anys).ok()?;
2739 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
2740 Some(std::sync::Arc::new(ba))
2741 }
2742}
2743
2744#[derive(Debug, Clone)]
2745pub struct LayerInfo {
2746 pub vertex_count: usize,
2747 pub parallel_eligible: bool,
2748 pub sample_cells: Vec<String>, }
2750
2751#[derive(Debug, Clone)]
2752pub struct EvalPlan {
2753 pub total_vertices_to_evaluate: usize,
2754 pub layers: Vec<LayerInfo>,
2755 pub cycles_detected: usize,
2756 pub dirty_count: usize,
2757 pub volatile_count: usize,
2758 pub parallel_enabled: bool,
2759 pub estimated_parallel_layers: usize,
2760 pub target_cells: Vec<String>,
2761}
2762
2763#[inline]
2766fn plane_mode_ignored() -> bool {
2767 true
2768}
2769
2770#[cfg(feature = "test-support")]
2773#[doc(hidden)]
2774pub fn formula_plane_mode_ignored_for_test() -> bool {
2775 plane_mode_ignored()
2776}
2777
2778impl<R> Engine<R>
2779where
2780 R: EvaluationContext,
2781{
2782 pub fn new(resolver: R, config: EvalConfig) -> Self {
2789 let config = if plane_mode_ignored() {
2794 EvalConfig {
2795 formula_plane_mode: FormulaPlaneMode::Off,
2796 ..config
2797 }
2798 } else {
2799 config
2800 };
2801 if let Err(msg) = config.cycle.validate() {
2802 panic!("invalid CycleConfig: {msg}");
2803 }
2804 crate::builtins::load_builtins();
2805 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2806 &config.evaluation_budgets,
2807 config.max_vertices,
2808 config.max_memory_mb,
2809 config.max_eval_time,
2810 );
2811
2812 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2813 #[cfg(feature = "system-clock")]
2814 {
2815 Arc::new(crate::timezone::SystemClock::new(
2816 crate::timezone::TimeZoneSpec::default(),
2817 ))
2818 }
2819 #[cfg(not(feature = "system-clock"))]
2820 {
2821 Arc::new(crate::timezone::FixedClock::new(
2822 chrono::DateTime::UNIX_EPOCH,
2823 crate::timezone::TimeZoneSpec::Utc,
2824 ))
2825 }
2826 });
2827
2828 let thread_pool = if config.enable_parallel {
2830 let mut builder = ThreadPoolBuilder::new();
2831 if let Some(max_threads) = config.max_threads {
2832 builder = builder.num_threads(max_threads);
2833 }
2834
2835 match builder.build() {
2836 Ok(pool) => Some(Arc::new(pool)),
2837 Err(_) => {
2838 None
2840 }
2841 }
2842 } else {
2843 None
2844 };
2845
2846 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
2848 let function_provider_revision_seen = resolver.planning_semantic_revision();
2849 let mut engine = Self {
2850 graph: DependencyGraph::new_with_config(config.clone()),
2851 resolver,
2852 config,
2853 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
2854 clock: crate::timezone::SnapshotClock::new(clock),
2855 thread_pool,
2856 recalc_epoch: 0,
2857 snapshot_id: std::sync::atomic::AtomicU64::new(1),
2858 topology_epoch: 0,
2859 cached_static_schedule: None,
2860 recent_schedules: Vec::new(),
2861 base_schedule: None,
2862 #[cfg(any(test, feature = "benchmark_internal"))]
2863 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
2864 spill_mgr: ShimSpillManager::default(),
2865 arrow_sheets: SheetStore::default(),
2866 format_registry: crate::format::FormatRegistry::default(),
2867 derived_formats: Default::default(),
2868 #[cfg(test)]
2869 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
2870 #[cfg(test)]
2871 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
2872 #[cfg(test)]
2873 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
2874 #[cfg(test)]
2875 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
2876 #[cfg(test)]
2877 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
2878 #[cfg(test)]
2879 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
2880 #[cfg(test)]
2881 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
2882 #[cfg(test)]
2883 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
2884 compressed_at_build: None,
2885 #[cfg(test)]
2886 computed_overlay_set_explicit_entry_operations_for_test: 0,
2887 #[cfg(test)]
2888 computed_overlay_stale_clear_range_effects_for_test: 0,
2889 #[cfg(test)]
2890 computed_overlay_stale_clear_offset_attempts_for_test: 0,
2891 #[cfg(test)]
2892 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
2893 has_edited: false,
2894 overlay_compactions: 0,
2895 computed_overlay_bytes_estimate: 0,
2896 computed_overlay_mirroring_disabled: false,
2897 force_materialize_range_views: false,
2898 row_bounds_cache: std::sync::RwLock::new(None),
2899 used_axis_bounds_cache: std::sync::RwLock::new(None),
2900 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
2901 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
2902 source_formula_token: Arc::new(()),
2903 recalc_plan_token: Arc::new(()),
2904 staged_formulas: std::collections::HashMap::new(),
2905 staged_formula_index: StagedFormulaIndex::default(),
2906 blocked_pending_spills: Vec::new(),
2907 spill_blocker_waiters: SpillBlockerWaiters::default(),
2908 row_visibility: FxHashMap::default(),
2909 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
2910 formula_parse_diagnostics: Vec::new(),
2911 last_formula_ingest_report: None,
2912 formula_ingest_report_total: FormulaIngestReport::default(),
2913 active_cancel_flag: None,
2914 active_evaluation_deadline: None,
2915 action_depth: 0,
2916 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
2917 virtual_dep_fallback_activations: 0,
2918 last_cycle_telemetry: CycleTelemetry::default(),
2919 next_evaluation_resource_request_id: 1,
2920 evaluation_resource_request_depth: 0,
2921 active_evaluation_resource_request: None,
2922 last_evaluation_resource_request: None,
2923 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
2924 evaluation_resource_request_started_at: None,
2925 evaluation_resource_budgets: resolved_resources.budgets,
2926 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
2927 active_resource_ledger: None,
2928 source_cache_footprints: Vec::new(),
2929 source_cache_accounted: 0,
2930 pending_iterative_redirty: Vec::new(),
2931 snapshot_evaluation_active: false,
2932 snapshot_volatile_redirty_pending: false,
2933 retained_scc_members: FxHashMap::default(),
2934 next_retained_scc_id: 0,
2935 retained_scc_config_fingerprint: 0,
2936 retained_scc_function_epoch_seen: 0,
2937 retained_scc_provider_revision_seen: None,
2938 retained_scc_dirty_at_begin: Vec::new(),
2939 iterative_state_values: FxHashMap::default(),
2940 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
2941 function_provider_revision_seen,
2942 #[cfg(feature = "tracing")]
2943 trace_evaluation_counters: TraceEvaluationCounters::default(),
2944 #[cfg(test)]
2945 evaluation_request_begin_count_for_test: 0,
2946 #[cfg(any(test, feature = "test-support"))]
2947 before_prepared_span_commit_hook: None,
2948 #[cfg(test)]
2949 before_target_preparation_commit_hook: None,
2950 #[cfg(test)]
2951 before_target_planning_snapshot_hook: None,
2952 #[cfg(test)]
2953 inject_target_semantic_stale_once_for_test: false,
2954 #[cfg(test)]
2955 force_virtual_dep_changes_remaining_for_test: 0,
2956 freshness: Default::default(),
2957 #[cfg(test)]
2958 fail_evaluation_commit_preflight_once_for_test: false,
2959 #[cfg(test)]
2960 target_preparation_fault_for_test: None,
2961 #[cfg(test)]
2962 force_non_cycle_schedule_fallback_for_test: false,
2963 #[cfg(test)]
2964 before_legacy_fallback_final_provider_sample_hook: None,
2965 #[cfg(test)]
2966 after_eager_proposal_commit_hook: None,
2967 };
2968 engine.config.arrow_storage_enabled = true;
2970 engine.config.delta_overlay_enabled = true;
2971 engine.config.write_formula_overlay_enabled = true;
2972 let default_sheet = engine.graph.default_sheet_name().to_string();
2973 engine.ensure_arrow_sheet(&default_sheet);
2974 engine
2975 }
2976
2977 pub fn with_thread_pool(
2982 resolver: R,
2983 config: EvalConfig,
2984 thread_pool: Arc<rayon::ThreadPool>,
2985 ) -> Self {
2986 if let Err(msg) = config.cycle.validate() {
2987 panic!("invalid CycleConfig: {msg}");
2988 }
2989 crate::builtins::load_builtins();
2990 let resolved_resources = crate::engine::resource_ledger::resolve_evaluation_budgets(
2991 &config.evaluation_budgets,
2992 config.max_vertices,
2993 config.max_memory_mb,
2994 config.max_eval_time,
2995 );
2996 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
2997 #[cfg(feature = "system-clock")]
2998 {
2999 Arc::new(crate::timezone::SystemClock::new(
3000 crate::timezone::TimeZoneSpec::default(),
3001 ))
3002 }
3003 #[cfg(not(feature = "system-clock"))]
3004 {
3005 Arc::new(crate::timezone::FixedClock::new(
3006 chrono::DateTime::UNIX_EPOCH,
3007 crate::timezone::TimeZoneSpec::Utc,
3008 ))
3009 }
3010 });
3011 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
3013 let function_provider_revision_seen = resolver.planning_semantic_revision();
3014 let mut engine = Self {
3015 graph: DependencyGraph::new_with_config(config.clone()),
3016 resolver,
3017 config,
3018 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
3019 clock: crate::timezone::SnapshotClock::new(clock),
3020 thread_pool: Some(thread_pool),
3021 recalc_epoch: 0,
3022 snapshot_id: std::sync::atomic::AtomicU64::new(1),
3023 topology_epoch: 0,
3024 cached_static_schedule: None,
3025 recent_schedules: Vec::new(),
3026 base_schedule: None,
3027 #[cfg(any(test, feature = "benchmark_internal"))]
3028 recalc_reuse_probe: std::sync::Mutex::new(RecalcReuseProbe::default()),
3029 spill_mgr: ShimSpillManager::default(),
3030 arrow_sheets: SheetStore::default(),
3031 format_registry: crate::format::FormatRegistry::default(),
3032 derived_formats: Default::default(),
3033 #[cfg(test)]
3034 derived_format_operations_for_test: std::sync::atomic::AtomicU64::new(0),
3035 #[cfg(test)]
3036 family_members_for_test: std::sync::atomic::AtomicU64::new(0),
3037 #[cfg(test)]
3038 invariant_bound_members_for_test: std::sync::atomic::AtomicU64::new(0),
3039 #[cfg(test)]
3040 lifted_members_for_test: std::sync::atomic::AtomicU64::new(0),
3041 #[cfg(test)]
3042 chained_members_for_test: std::sync::atomic::AtomicU64::new(0),
3043 #[cfg(test)]
3044 lane_clean_reads_for_test: std::sync::atomic::AtomicU64::new(0),
3045 #[cfg(test)]
3046 criteria_kernel_members_for_test: std::sync::atomic::AtomicU64::new(0),
3047 #[cfg(test)]
3048 memo_hits_for_test: std::sync::atomic::AtomicU64::new(0),
3049 compressed_at_build: None,
3050 #[cfg(test)]
3051 computed_overlay_set_explicit_entry_operations_for_test: 0,
3052 #[cfg(test)]
3053 computed_overlay_stale_clear_range_effects_for_test: 0,
3054 #[cfg(test)]
3055 computed_overlay_stale_clear_offset_attempts_for_test: 0,
3056 #[cfg(test)]
3057 computed_format_vector_allocations_for_test: std::sync::atomic::AtomicU64::new(0),
3058 has_edited: false,
3059 overlay_compactions: 0,
3060 computed_overlay_bytes_estimate: 0,
3061 computed_overlay_mirroring_disabled: false,
3062 force_materialize_range_views: false,
3063 row_bounds_cache: std::sync::RwLock::new(None),
3064 used_axis_bounds_cache: std::sync::RwLock::new(None),
3065 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
3066 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
3067 source_formula_token: Arc::new(()),
3068 recalc_plan_token: Arc::new(()),
3069 staged_formulas: std::collections::HashMap::new(),
3070 staged_formula_index: StagedFormulaIndex::default(),
3071 blocked_pending_spills: Vec::new(),
3072 spill_blocker_waiters: SpillBlockerWaiters::default(),
3073 row_visibility: FxHashMap::default(),
3074 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
3075 formula_parse_diagnostics: Vec::new(),
3076 last_formula_ingest_report: None,
3077 formula_ingest_report_total: FormulaIngestReport::default(),
3078 active_cancel_flag: None,
3079 active_evaluation_deadline: None,
3080 action_depth: 0,
3081 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
3082 virtual_dep_fallback_activations: 0,
3083 last_cycle_telemetry: CycleTelemetry::default(),
3084 next_evaluation_resource_request_id: 1,
3085 evaluation_resource_request_depth: 0,
3086 active_evaluation_resource_request: None,
3087 last_evaluation_resource_request: None,
3088 evaluation_resource_baseline: EvaluationResourceBaselineStats::default(),
3089 evaluation_resource_request_started_at: None,
3090 evaluation_resource_budgets: resolved_resources.budgets,
3091 evaluation_resource_config_diagnostic: resolved_resources.diagnostic,
3092 active_resource_ledger: None,
3093 source_cache_footprints: Vec::new(),
3094 source_cache_accounted: 0,
3095 pending_iterative_redirty: Vec::new(),
3096 snapshot_evaluation_active: false,
3097 snapshot_volatile_redirty_pending: false,
3098 retained_scc_members: FxHashMap::default(),
3099 next_retained_scc_id: 0,
3100 retained_scc_config_fingerprint: 0,
3101 retained_scc_function_epoch_seen: 0,
3102 retained_scc_provider_revision_seen: None,
3103 retained_scc_dirty_at_begin: Vec::new(),
3104 iterative_state_values: FxHashMap::default(),
3105 function_semantic_epoch_seen: crate::function_registry::semantic_epoch(),
3106 function_provider_revision_seen,
3107 #[cfg(feature = "tracing")]
3108 trace_evaluation_counters: TraceEvaluationCounters::default(),
3109 #[cfg(test)]
3110 evaluation_request_begin_count_for_test: 0,
3111 #[cfg(any(test, feature = "test-support"))]
3112 before_prepared_span_commit_hook: None,
3113 #[cfg(test)]
3114 before_target_preparation_commit_hook: None,
3115 #[cfg(test)]
3116 before_target_planning_snapshot_hook: None,
3117 #[cfg(test)]
3118 inject_target_semantic_stale_once_for_test: false,
3119 #[cfg(test)]
3120 force_virtual_dep_changes_remaining_for_test: 0,
3121 freshness: Default::default(),
3122 #[cfg(test)]
3123 fail_evaluation_commit_preflight_once_for_test: false,
3124 #[cfg(test)]
3125 target_preparation_fault_for_test: None,
3126 #[cfg(test)]
3127 force_non_cycle_schedule_fallback_for_test: false,
3128 #[cfg(test)]
3129 before_legacy_fallback_final_provider_sample_hook: None,
3130 #[cfg(test)]
3131 after_eager_proposal_commit_hook: None,
3132 };
3133 engine.config.arrow_storage_enabled = true;
3135 engine.config.delta_overlay_enabled = true;
3136 engine.config.write_formula_overlay_enabled = true;
3137 let default_sheet = engine.graph.default_sheet_name().to_string();
3138 engine.ensure_arrow_sheet(&default_sheet);
3139 engine
3140 }
3141
3142 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
3143 &self.workbook_load_limits
3144 }
3145
3146 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
3147 self.workbook_load_limits = limits;
3148 }
3149
3150 fn clear_source_cache(&self) {
3151 if let Ok(mut g) = self.source_cache.write() {
3152 *g = SourceCache::default();
3153 }
3154 }
3155
3156 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
3157 &self.last_virtual_dep_telemetry
3158 }
3159
3160 pub fn last_cycle_telemetry(&self) -> &CycleTelemetry {
3164 &self.last_cycle_telemetry
3165 }
3166
3167 pub fn last_evaluation_resource_request_stats(
3169 &self,
3170 ) -> Option<&EvaluationResourceRequestStats> {
3171 self.last_evaluation_resource_request.as_ref()
3172 }
3173
3174 pub fn evaluation_resource_baseline_stats(&self) -> EvaluationResourceBaselineStats {
3176 self.evaluation_resource_baseline
3177 }
3178
3179 pub fn evaluation_resource_budgets(&self) -> &crate::engine::EvaluationBudgets {
3180 &self.evaluation_resource_budgets
3181 }
3182
3183 pub fn evaluation_resource_config_diagnostic(
3185 &self,
3186 ) -> Option<&crate::engine::EvaluationResourceConfigDiagnostic> {
3187 self.evaluation_resource_config_diagnostic.as_ref()
3188 }
3189
3190 pub fn reset_evaluation_resource_telemetry(&mut self) {
3192 self.evaluation_resource_baseline = EvaluationResourceBaselineStats::default();
3193 self.last_evaluation_resource_request = None;
3194 }
3195
3196 fn reconcile_source_cache_footprints(&mut self) -> Result<(), ExcelError> {
3199 self.blocked_pending_spills.retain(|&(vertex, anchor, _)| {
3200 self.graph.vertex_exists(vertex)
3201 && self.graph.get_cell_ref(vertex) == Some(anchor)
3202 && matches!(
3203 self.graph.get_vertex_kind(vertex),
3204 VertexKind::FormulaScalar | VertexKind::FormulaArray
3205 )
3206 });
3207 if self.blocked_pending_spills.is_empty() {
3208 self.blocked_pending_spills = Vec::new();
3209 }
3210 let mut bytes = (self.blocked_pending_spills.capacity()
3211 * std::mem::size_of::<(VertexId, CellRef, Region)>()) as u64;
3212 self.source_cache_footprints.retain(|weak| {
3213 let Some(footprint) = weak.upgrade() else {
3214 return false;
3215 };
3216 bytes = bytes.saturating_add(footprint.load(std::sync::atomic::Ordering::Acquire));
3217 true
3218 });
3219 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3220 ledger
3221 .release_retained(self.source_cache_accounted)
3222 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3223 ledger.observe_retained(bytes);
3225 self.source_cache_accounted = bytes;
3226 ledger
3227 .reserve_retained(0)
3228 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3229 }
3230 Ok(())
3231 }
3232
3233 fn duration_ns(duration: std::time::Duration) -> u64 {
3234 u64::try_from(duration.as_nanos()).unwrap_or(u64::MAX)
3235 }
3236
3237 fn observe_evaluation_resource_request<T>(
3238 &mut self,
3239 kind: EvaluationRequestKind,
3240 evaluate: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3241 ) -> Result<T, ExcelError> {
3242 let outermost = self.evaluation_resource_request_depth == 0;
3243 if outermost && self.snapshot_volatile_redirty_pending {
3244 self.snapshot_volatile_redirty_pending = false;
3245 self.graph.redirty_volatiles();
3246 }
3247 #[cfg(feature = "tracing")]
3248 if outermost {
3249 self.trace_evaluation_counters = TraceEvaluationCounters::default();
3250 }
3251 #[cfg(feature = "tracing")]
3252 let request_kind = if matches!(
3253 kind,
3254 EvaluationRequestKind::Full
3255 | EvaluationRequestKind::FullWithDelta
3256 | EvaluationRequestKind::FullCancellable
3257 | EvaluationRequestKind::FullLogged
3258 ) {
3259 "full"
3260 } else {
3261 "targeted"
3262 };
3263 #[cfg(feature = "tracing")]
3264 let _request_span = outermost.then(|| {
3265 crate::engine::trace::fz_span!(
3266 tracing::Level::INFO,
3267 "evaluate",
3268 "evaluate.request",
3269 kind = request_kind,
3270 mode = ?FormulaPlaneMode::Off
3271 )
3272 });
3273 if outermost {
3274 let request_id = self.next_evaluation_resource_request_id;
3275 self.next_evaluation_resource_request_id = request_id
3276 .checked_add(1)
3277 .expect("evaluation resource request ID exhausted");
3278 self.active_evaluation_resource_request = Some(EvaluationResourceRequestStats::new(
3279 request_id,
3280 kind,
3281 FormulaPlaneMode::Off,
3282 self.staged_formula_count(),
3283 ));
3284 self.evaluation_resource_baseline.record_started(request_id);
3285 self.evaluation_resource_request_started_at = Some(crate::instant::FzInstant::now());
3286 self.source_cache_accounted = 0;
3287 self.active_resource_ledger = Some(ResourceLedger::new(
3288 Some(request_id),
3289 self.evaluation_resource_budgets.clone(),
3290 ));
3291 }
3292 self.evaluation_resource_request_depth =
3293 self.evaluation_resource_request_depth.saturating_add(1);
3294 let result = if outermost {
3295 self.reconcile_source_cache_footprints()
3296 .and_then(|()| self.resource_checkpoint(0))
3297 .and_then(|()| evaluate(self))
3298 } else {
3299 evaluate(self)
3300 };
3301 if outermost && result.is_err() {
3302 self.freshness_abort_pass();
3303 }
3304 self.evaluation_resource_request_depth =
3305 self.evaluation_resource_request_depth.saturating_sub(1);
3306
3307 let reconciliation = if outermost {
3308 self.reconcile_source_cache_footprints()
3309 } else {
3310 Ok(())
3311 };
3312 let result = result.and_then(|value| reconciliation.map(|()| value));
3313 if outermost {
3314 let total_ns = self
3315 .evaluation_resource_request_started_at
3316 .take()
3317 .map(|start| Self::duration_ns(start.elapsed()))
3318 .unwrap_or(0);
3319 let mut stats = self
3320 .active_evaluation_resource_request
3321 .take()
3322 .expect("outer evaluation resource request has active stats");
3323 let mut ledger = self
3324 .active_resource_ledger
3325 .take()
3326 .expect("outer evaluation resource request has active ledger");
3327 ledger.release_all_scratch();
3328 stats.ledger.update(ledger.snapshot());
3329 stats.outcome = match &result {
3330 Ok(_) => EvaluationRequestOutcome::Success,
3331 Err(error) if error.kind == ExcelErrorKind::Cancelled => {
3332 EvaluationRequestOutcome::Cancelled
3333 }
3334 Err(_) => EvaluationRequestOutcome::Error,
3335 };
3336 if stats.dirty_lease == FormulaDirtyLeaseOutcome::Acquired {
3337 stats.dirty_lease = if stats.outcome == EvaluationRequestOutcome::Cancelled {
3338 FormulaDirtyLeaseOutcome::RetainedOnCancellation
3339 } else {
3340 FormulaDirtyLeaseOutcome::RetainedOnError
3341 };
3342 }
3343 stats.phases.total_ns = total_ns;
3344 let attributed = stats
3345 .phases
3346 .staged_prepare_ns
3347 .saturating_add(stats.phases.topology_ns)
3348 .saturating_add(stats.phases.materialization_ns);
3349 stats.phases.evaluation_ns = total_ns.saturating_sub(attributed);
3350 self.evaluation_resource_baseline.record_finished(&stats);
3351 self.last_evaluation_resource_request = Some(stats);
3352 crate::engine::trace::fz_event!(
3353 tracing::Level::INFO,
3354 "evaluate",
3355 "evaluate.summary",
3356 computed_vertices = self.trace_evaluation_counters.computed_vertices,
3357 cycles = self.trace_evaluation_counters.cycles,
3358 cancelled = matches!(
3359 &result,
3360 Err(error) if error.kind == ExcelErrorKind::Cancelled
3361 )
3362 );
3363 }
3364 result
3365 }
3366
3367 pub fn set_evaluation_resource_budgets(&mut self, budgets: crate::engine::EvaluationBudgets) {
3368 self.evaluation_resource_budgets = budgets.clone();
3369 self.config.evaluation_budgets = budgets.clone();
3370 self.graph.set_evaluation_budgets(budgets);
3371 }
3372
3373 #[cfg(test)]
3374 pub(crate) fn set_evaluation_budgets_for_test(
3375 &mut self,
3376 budgets: crate::engine::EvaluationBudgets,
3377 ) {
3378 self.set_evaluation_resource_budgets(budgets);
3379 }
3380
3381 fn preflight_evaluation_commit_window(
3382 &mut self,
3383 bounded_writes: usize,
3384 ) -> Result<crate::instant::FzInstant, ExcelError> {
3385 #[cfg(test)]
3386 if std::mem::take(&mut self.fail_evaluation_commit_preflight_once_for_test) {
3387 return Err(crate::engine::ResourceLedgerError::Exhausted(
3388 formualizer_common::ResourceExhaustionDetail {
3389 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3390 limit: 0,
3391 observed: 1,
3392 request_id: self
3393 .active_evaluation_resource_request
3394 .as_ref()
3395 .map(|stats| stats.request_id),
3396 },
3397 )
3398 .into_excel_error());
3399 }
3400 let estimate = std::time::Duration::from_nanos(
3401 u64::try_from(bounded_writes)
3402 .unwrap_or(u64::MAX)
3403 .saturating_mul(100),
3404 );
3405 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3406 ledger
3407 .preflight_commit_window(estimate)
3408 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
3409 }
3410 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3411 stats.evaluation_commit_preflight_count =
3412 stats.evaluation_commit_preflight_count.saturating_add(1);
3413 stats.evaluation_commit_estimated_ns = stats
3414 .evaluation_commit_estimated_ns
3415 .saturating_add(Self::duration_ns(estimate));
3416 }
3417 Ok(crate::instant::FzInstant::now())
3418 }
3419
3420 fn observe_evaluation_commit_window(&mut self, started: crate::instant::FzInstant) {
3421 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3422 stats.evaluation_commit_actual_ns = stats
3423 .evaluation_commit_actual_ns
3424 .saturating_add(Self::duration_ns(started.elapsed()));
3425 }
3426 }
3427
3428 fn live_cancellation_after_work(&self, message: &'static str) -> Result<(), ExcelError> {
3440 if self
3441 .active_cancel_flag
3442 .as_ref()
3443 .is_some_and(|cancel| cancel.is_cancelled())
3444 {
3445 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(message));
3446 }
3447 Ok(())
3448 }
3449
3450 fn cancellation_checkpoint(&self, message: &'static str) -> Result<(), ExcelError> {
3451 if self
3452 .active_cancel_flag
3453 .as_ref()
3454 .is_some_and(|cancel| cancel.is_cancelled())
3455 {
3456 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(message));
3457 }
3458 if self
3459 .active_evaluation_deadline
3460 .is_some_and(|deadline| Instant::now() >= deadline)
3461 {
3462 return Err(crate::engine::ResourceLedgerError::Exhausted(
3463 formualizer_common::ResourceExhaustionDetail {
3464 reason: formualizer_common::ResourceExhaustionReason::Deadline,
3465 limit: 0,
3466 observed: 1,
3467 request_id: self
3468 .active_evaluation_resource_request
3469 .as_ref()
3470 .map(|request| request.request_id),
3471 },
3472 )
3473 .into_excel_error()
3474 .with_message(message));
3475 }
3476 Ok(())
3477 }
3478
3479 fn resource_checkpoint(&mut self, work_units: u64) -> Result<(), ExcelError> {
3480 let Some(ledger) = self.active_resource_ledger.as_mut() else {
3481 return Ok(());
3482 };
3483 ledger
3484 .charge_work(work_units)
3485 .and_then(|()| ledger.checkpoint_deadline())
3486 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3487 }
3488
3489 fn charge_bounded_work(&mut self, mut work_units: u64) -> Result<(), ExcelError> {
3490 if work_units == 0 {
3491 return self.resource_checkpoint(0);
3492 }
3493 while work_units > 0 {
3494 let chunk = work_units.min(256);
3495 self.resource_checkpoint(chunk)?;
3496 work_units -= chunk;
3497 }
3498 Ok(())
3499 }
3500
3501 fn reserve_request_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3502 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3503 ledger.observe_scratch(bytes);
3506 }
3507 Ok(())
3508 }
3509
3510 fn release_request_scratch(&mut self, bytes: u64) {
3511 if let Some(ledger) = self.active_resource_ledger.as_mut() {
3512 let released = ledger.release_scratch(bytes);
3513 debug_assert!(
3514 released.is_ok(),
3515 "request scratch release exceeded the outstanding reservation"
3516 );
3517 }
3518 }
3519
3520 fn reserve_topology_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3521 self.active_resource_ledger
3522 .as_mut()
3523 .map_or(Ok(()), |ledger| ledger.reserve_schedule_discovery(bytes))
3524 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3525 }
3526
3527 fn reserve_graph_source_scratch(&mut self, bytes: u64) -> Result<(), ExcelError> {
3528 self.active_resource_ledger
3529 .as_mut()
3530 .map_or(Ok(()), |ledger| ledger.reserve_graph_source(bytes))
3531 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3532 }
3533
3534 fn with_request_scratch<T>(
3535 &mut self,
3536 bytes: u64,
3537 work: impl FnOnce(&mut Self) -> Result<T, ExcelError>,
3538 ) -> Result<T, ExcelError> {
3539 self.reserve_request_scratch(bytes)?;
3540 let result = work(self);
3541 self.release_request_scratch(bytes);
3542 result
3543 }
3544
3545 fn observe_staged_preparation(
3546 &mut self,
3547 selected: usize,
3548 retained: usize,
3549 elapsed: std::time::Duration,
3550 ) {
3551 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3552 stats.staged_selected = stats.staged_selected.saturating_add(selected as u64);
3553 stats.staged_retained = retained as u64;
3554 stats.phases.staged_prepare_ns = stats
3555 .phases
3556 .staged_prepare_ns
3557 .saturating_add(Self::duration_ns(elapsed));
3558 }
3559 }
3560
3561 fn graph_admission_enabled(&self) -> bool {
3562 crate::engine::resource_ledger::graph_admission_enabled(&self.evaluation_resource_budgets)
3563 }
3564
3565 fn preflight_graph_admission(
3566 &mut self,
3567 usage: crate::engine::resource_ledger::GraphAdmission,
3568 ) -> Result<(), ExcelError> {
3569 let request_id = self
3570 .active_evaluation_resource_request
3571 .as_ref()
3572 .map(|stats| stats.request_id);
3573 crate::engine::resource_ledger::preflight_graph_admission(
3574 &self.evaluation_resource_budgets,
3575 usage,
3576 request_id,
3577 )
3578 .map_err(crate::engine::ResourceLedgerError::into_excel_error)
3579 }
3580
3581 fn prepared_legacy_admission(
3582 &mut self,
3583 plan: &PreparedLegacyGraphPlan,
3584 materialization_cells: u64,
3585 ) -> Result<(), ExcelError> {
3586 if !self.graph_admission_enabled() {
3587 return Ok(());
3588 }
3589 let stats = self.graph.baseline_stats();
3590 let added_edges = plan.planned_edge_count().ok_or_else(|| {
3591 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3592 })?;
3593 let removed_edges = plan.removed_edge_count().ok_or_else(|| {
3594 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3595 })?;
3596 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
3597 final_vertices: stats
3598 .graph_vertex_count
3599 .checked_add(plan.new_vertex_count())
3600 .ok_or_else(|| {
3601 ExcelError::new(ExcelErrorKind::NImpl)
3602 .with_message("graph vertex count overflow")
3603 })?,
3604 final_edges: stats
3605 .graph_edge_count
3606 .checked_sub(removed_edges)
3607 .and_then(|count| count.checked_add(added_edges))
3608 .ok_or_else(|| {
3609 ExcelError::new(ExcelErrorKind::NImpl).with_message("graph edge count overflow")
3610 })?,
3611 materialization_cells,
3612 added_vertices: plan.new_vertex_count(),
3613 added_edges,
3614 })
3615 }
3616
3617 fn observe_target_admission_failure(
3618 &mut self,
3619 reason: formualizer_common::ResourceExhaustionReason,
3620 ) {
3621 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3622 stats.target_admission_failure = Some(reason);
3623 }
3624 }
3625
3626 fn observe_target_preparation_report(
3627 &mut self,
3628 report: &crate::engine::PreparedTargetGraphReport,
3629 ) {
3630 let reason_bit = |reason: crate::engine::OpaqueReason| -> u64 {
3631 let index = match reason {
3632 crate::engine::OpaqueReason::DynamicReference => 0,
3633 crate::engine::OpaqueReason::RuntimeTextReference => 1,
3634 crate::engine::OpaqueReason::UnknownFunction => 2,
3635 crate::engine::OpaqueReason::UnknownCustomFunction => 3,
3636 crate::engine::OpaqueReason::UnresolvedCrossSheetBinding => 4,
3637 crate::engine::OpaqueReason::UnresolvedName => 5,
3638 crate::engine::OpaqueReason::UnresolvedTable => 6,
3639 crate::engine::OpaqueReason::FormulaName => 7,
3640 crate::engine::OpaqueReason::DeferredSourcePackage => 8,
3641 crate::engine::OpaqueReason::UnsupportedSourceSemantics => 9,
3642 crate::engine::OpaqueReason::UncertainDefaultSheetBinding => 10,
3643 };
3644 1u64 << index
3645 };
3646 if let Some(stats) = self.active_evaluation_resource_request.as_mut() {
3647 stats.staged_selected = report.selected_staged_cells as u64;
3648 stats.staged_retained = report.retained_staged_cells as u64;
3649 stats.target_requested = report.requested_targets as u64;
3650 stats.target_normalized_regions = report.normalized_regions as u64;
3651 stats.target_scope_level = match &report.widened_scope {
3652 crate::engine::PrepareScope::Exact => 0,
3653 crate::engine::PrepareScope::Sheets(_) => 1,
3654 crate::engine::PrepareScope::Workbook => 2,
3655 };
3656 stats.target_widening_reason_bits = report
3657 .widening_reasons
3658 .iter()
3659 .copied()
3660 .fold(0, |bits, reason| bits | reason_bit(reason));
3661 stats.graph_source_scratch_estimated = report.estimated_scratch_bytes;
3662 stats.graph_source_scratch_observed = report.observed_scratch_bytes;
3663 stats.target_commit_estimated_work = report.estimated_commit_work;
3664 stats.target_commit_actual_work = report.actual_commit_work;
3665 stats.target_commit_window_ns = Self::duration_ns(report.commit_window);
3666 stats.phases.staged_prepare_ns = stats
3667 .phases
3668 .staged_prepare_ns
3669 .saturating_add(Self::duration_ns(report.commit_window));
3670 }
3671 }
3672
3673 fn begin_evaluation_request(&mut self) {
3677 self.freshness_begin_request();
3678 #[cfg(test)]
3679 {
3680 self.evaluation_request_begin_count_for_test = self
3681 .evaluation_request_begin_count_for_test
3682 .saturating_add(1);
3683 }
3684 self.last_cycle_telemetry = CycleTelemetry::default();
3685 self.graph.authority_sync();
3686 self.pending_iterative_redirty.clear();
3689 self.reconcile_retained_sccs_at_request_begin();
3690 self.clock.refresh();
3694 }
3695
3696 fn redirty_for_next_recalc(&mut self) {
3705 if self.snapshot_evaluation_active {
3706 self.snapshot_volatile_redirty_pending = true;
3707 } else {
3708 self.graph.redirty_volatiles();
3709 }
3710 let pending = std::mem::take(&mut self.pending_iterative_redirty);
3711 let dirty_at_begin = std::mem::take(&mut self.retained_scc_dirty_at_begin);
3712 for (vertex, scc) in dirty_at_begin {
3713 if self.retained_scc_members.get(&vertex) == Some(&scc) {
3714 self.retained_scc_members.remove(&vertex);
3720 if !self.graph.is_dirty(vertex) {
3721 self.iterative_state_values.remove(&vertex);
3722 }
3723 }
3724 }
3725 if !self.iterative_state_values.is_empty() || !self.retained_scc_members.is_empty() {
3726 let graph = &self.graph;
3727 self.iterative_state_values
3728 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3729 self.retained_scc_members
3730 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3731 }
3732 for &vertex in &pending {
3737 if !self.graph.is_live_formula_vertex(vertex) {
3738 continue;
3739 }
3740 if let Some(cell) = self.graph.get_cell_ref(vertex) {
3741 let sheet_name = self.graph.sheet_name(cell.sheet_id);
3742 match self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1) {
3743 Some(value) if !matches!(value, LiteralValue::Empty) => {
3744 self.iterative_state_values.insert(vertex, value);
3745 }
3746 _ => {
3747 self.iterative_state_values.remove(&vertex);
3748 }
3749 }
3750 }
3751 }
3752 if !pending.is_empty() {
3753 self.graph.redirty_iterative_members(&pending);
3754 }
3755 }
3756
3757 fn retained_scc_config_fingerprint(&self) -> u64 {
3762 use std::hash::{Hash, Hasher};
3763 let mut hasher = rustc_hash::FxHasher::default();
3764 let config = &self.config;
3765 std::mem::discriminant(&config.cycle.detection).hash(&mut hasher);
3766 match config.cycle.policy {
3767 CyclePolicy::Error => 0u8.hash(&mut hasher),
3768 CyclePolicy::Iterate {
3769 max_iterations,
3770 max_change,
3771 } => {
3772 1u8.hash(&mut hasher);
3773 max_iterations.hash(&mut hasher);
3774 max_change.to_bits().hash(&mut hasher);
3775 }
3776 }
3777 config.date_system.hash(&mut hasher);
3778 config.workbook_seed.hash(&mut hasher);
3779 std::mem::discriminant(&config.volatile_level).hash(&mut hasher);
3780 format!("{:?}", config.deterministic_mode).hash(&mut hasher);
3783 config.range_expansion_limit.hash(&mut hasher);
3784 config.max_open_ended_rows.hash(&mut hasher);
3785 config.max_open_ended_cols.hash(&mut hasher);
3786 hasher.finish()
3787 }
3788
3789 fn reconcile_retained_sccs_at_request_begin(&mut self) {
3795 self.retained_scc_dirty_at_begin.clear();
3796 if self.retained_scc_members.is_empty() {
3797 return;
3798 }
3799 let graph = &self.graph;
3802 self.retained_scc_members
3803 .retain(|vertex, _| graph.is_live_formula_vertex(*vertex));
3804 if self.retained_scc_config_fingerprint() != self.retained_scc_config_fingerprint {
3805 let members: Vec<VertexId> = self.retained_scc_members.keys().copied().collect();
3806 self.retained_scc_members.clear();
3807 self.graph.mark_dirty_many(&members);
3808 return;
3809 }
3810 let changes =
3811 crate::function_registry::semantic_changes_since(self.retained_scc_function_epoch_seen);
3812 let global_changed = changes.epoch != self.retained_scc_function_epoch_seen;
3813 let provider_revision = self.resolver.planning_semantic_revision();
3814 let provider_changed = provider_revision != self.retained_scc_provider_revision_seen;
3815 if global_changed || provider_changed {
3816 let changed: BTreeSet<(String, String)> = changes.keys.into_iter().collect();
3817 let affected: Vec<VertexId> = self
3818 .retained_scc_members
3819 .keys()
3820 .copied()
3821 .filter(|&vertex| {
3822 let Some(ast) = self.graph.get_formula(vertex) else {
3823 return true;
3824 };
3825 (global_changed
3826 && (!changes.complete || Self::ast_uses_changed_function(&ast, &changed)))
3827 || (provider_changed && Self::ast_contains_function(&ast))
3828 })
3829 .collect();
3830 for vertex in &affected {
3831 self.retained_scc_members.remove(vertex);
3832 }
3833 if !affected.is_empty() {
3834 self.graph.mark_dirty_many(&affected);
3835 }
3836 self.retained_scc_function_epoch_seen = changes.epoch;
3837 self.retained_scc_provider_revision_seen = provider_revision;
3838 }
3839 let mut dirty_sccs: FxHashSet<u64> = FxHashSet::default();
3840 let mut all_sccs: FxHashSet<u64> = FxHashSet::default();
3841 for (&vertex, &scc) in &self.retained_scc_members {
3842 all_sccs.insert(scc);
3843 if self.graph.is_dirty(vertex) {
3844 dirty_sccs.insert(scc);
3845 self.retained_scc_dirty_at_begin.push((vertex, scc));
3846 }
3847 }
3848 let reused_members = self
3849 .retained_scc_members
3850 .values()
3851 .filter(|scc| !dirty_sccs.contains(scc))
3852 .count();
3853 let t = &mut self.last_cycle_telemetry;
3854 t.reused_sccs = all_sccs.len() - dirty_sccs.len();
3855 t.reused_scc_members = reused_members;
3856 }
3857
3858 pub fn virtual_dep_fallback_activations(&self) -> u64 {
3859 self.virtual_dep_fallback_activations
3860 }
3861
3862 #[cfg(test)]
3863 pub(crate) fn lookup_index_flights_built_for_test(&self) -> usize {
3864 self.lookup_index_cache
3865 .flights_built
3866 .load(std::sync::atomic::Ordering::Relaxed)
3867 }
3868
3869 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
3870 self.lookup_index_cache.report()
3871 }
3872
3873 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
3874 let start_row = view.start_row();
3875 let end_row = view.end_row();
3876 let start_col = view.start_col();
3877 let end_col = view.end_col();
3878 for row in start_row..=end_row {
3879 let Ok(row_u32) = u32::try_from(row) else {
3880 return true;
3881 };
3882 for col in start_col..=end_col {
3883 let Ok(col_u32) = u32::try_from(col) else {
3884 return true;
3885 };
3886 let cell_ref = self
3887 .graph
3888 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
3889 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
3890 && self.graph.is_volatile(vertex_id)
3891 {
3892 return true;
3893 }
3894 }
3895 }
3896 false
3897 }
3898
3899 fn build_lookup_index_impl(
3900 &self,
3901 view: &RangeView<'_>,
3902 axis: LookupAxis,
3903 ) -> Option<Arc<LookupIndex>> {
3904 let (rows, cols) = view.dims();
3905 if rows == 0 || cols == 0 {
3906 self.lookup_index_cache.note_skipped_tiny();
3907 return None;
3908 }
3909 let len = match axis {
3910 LookupAxis::ColumnInView(col) => {
3911 if col >= cols {
3912 self.lookup_index_cache.note_skipped_tiny();
3913 return None;
3914 }
3915 rows
3916 }
3917 LookupAxis::RowInView(row) => {
3918 if row >= rows {
3919 self.lookup_index_cache.note_skipped_tiny();
3920 return None;
3921 }
3922 cols
3923 }
3924 };
3925 if len < 64 {
3926 self.lookup_index_cache.note_skipped_tiny();
3927 return None;
3928 }
3929
3930 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
3931 let key = LookupIndexKey {
3932 sheet_id,
3933 start_row: u32::try_from(view.start_row()).ok()?,
3934 start_col: u32::try_from(view.start_col()).ok()?,
3935 end_row: u32::try_from(view.end_row()).ok()?,
3936 end_col: u32::try_from(view.end_col()).ok()?,
3937 axis,
3938 snapshot_id: self.data_snapshot_id(),
3939 };
3940 if let Some(index) = self.lookup_index_cache.get(&key) {
3941 return Some(index);
3942 }
3943 if self
3944 .lookup_index_cache
3945 .would_exceed_cap(estimate_bytes(len, 0))
3946 {
3947 self.lookup_index_cache.note_skipped_cap();
3948 return None;
3949 }
3950 if !self.lookup_index_cache.should_build(key) {
3951 return None;
3952 }
3953 self.lookup_index_cache.single_flight(key, || {
3955 if let Some(index) = self.lookup_index_cache.recheck(&key) {
3956 return Some(index);
3957 }
3958 if self.lookup_index_cache.is_known_volatile(&key) {
3959 self.lookup_index_cache.note_skipped_volatile();
3960 return None;
3961 }
3962 if self.lookup_view_contains_volatile(view, sheet_id) {
3963 self.lookup_index_cache.note_volatile_key(key);
3964 self.lookup_index_cache.note_skipped_volatile();
3965 return None;
3966 }
3967 #[cfg(test)]
3968 self.lookup_index_cache
3969 .flights_built
3970 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
3971 match LookupIndex::build(view, axis, self.config.date_system).ok()? {
3972 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
3973 BuildOutcome::ErrorInLookupAxis => {
3974 self.lookup_index_cache.note_skipped_error();
3975 None
3976 }
3977 BuildOutcome::Degenerate => {
3978 self.lookup_index_cache.note_skipped_tiny();
3979 None
3980 }
3981 }
3982 })
3983 }
3984
3985 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
3986 if !self.config.enable_virtual_dep_telemetry {
3987 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
3988 fallback_mode_activations: self.virtual_dep_fallback_activations,
3989 ..VirtualDepTelemetry::default()
3990 };
3991 }
3992 }
3993
3994 fn source_cache_session(&self) -> SourceCacheSession {
3995 self.clear_source_cache();
3996 SourceCacheSession {
3997 cache: self.source_cache.clone(),
3998 }
3999 }
4000
4001 fn resolve_source_scalar_cached(
4002 &self,
4003 name: &str,
4004 version: Option<u64>,
4005 ) -> Result<LiteralValue, ExcelError> {
4006 let key = (name.to_string(), version);
4007 if let Ok(mut g) = self.source_cache.write() {
4008 if let Some(v) = g.scalars.get(&key) {
4009 return Ok(v.clone());
4010 }
4011
4012 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
4013 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
4014 ExcelError::new(ExcelErrorKind::Ref)
4015 .with_message(format!("Unresolved source scalar: {name}"))
4016 } else {
4017 err
4018 }
4019 })?;
4020 g.scalars.insert(key, v.clone());
4021 Ok(v)
4022 } else {
4023 self.resolver.resolve_source_scalar(name).map_err(|err| {
4024 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
4025 ExcelError::new(ExcelErrorKind::Ref)
4026 .with_message(format!("Unresolved source scalar: {name}"))
4027 } else {
4028 err
4029 }
4030 })
4031 }
4032 }
4033
4034 fn resolve_source_table_cached(
4035 &self,
4036 name: &str,
4037 version: Option<u64>,
4038 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
4039 let key = (name.to_string(), version);
4040 if let Ok(mut g) = self.source_cache.write() {
4041 if let Some(t) = g.tables.get(&key) {
4042 return Ok(t.clone());
4043 }
4044
4045 let t = self.resolver.resolve_source_table(name).map_err(|err| {
4046 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
4047 ExcelError::new(ExcelErrorKind::Ref)
4048 .with_message(format!("Unresolved source table: {name}"))
4049 } else {
4050 err
4051 }
4052 })?;
4053 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
4054 g.tables.insert(key, t.clone());
4055 Ok(t)
4056 } else {
4057 self.resolver
4058 .resolve_source_table(name)
4059 .map_err(|err| {
4060 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
4061 ExcelError::new(ExcelErrorKind::Ref)
4062 .with_message(format!("Unresolved source table: {name}"))
4063 } else {
4064 err
4065 }
4066 })
4067 .map(Arc::from)
4068 }
4069 }
4070
4071 fn source_table_to_range_view(
4072 &self,
4073 table: &dyn crate::traits::Table,
4074 spec: &Option<formualizer_parse::parser::TableSpecifier>,
4075 ) -> Result<RangeView<'static>, ExcelError> {
4076 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
4077
4078 let owned = match spec {
4079 Some(TableSpecifier::Column(c)) => {
4080 let c = c.trim();
4081 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
4082 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
4083 "Complex structured references not yet supported".to_string(),
4084 ));
4085 }
4086 table.get_column(c)?.materialise().into_owned()
4087 }
4088 Some(TableSpecifier::ColumnRange(start, end)) => {
4089 let cols = table.columns();
4090 let start = start.trim();
4091 let end = end.trim();
4092 let start_key = start.to_lowercase();
4093 let end_key = end.to_lowercase();
4094 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
4095 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
4096 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
4097 if si > ei {
4098 std::mem::swap(&mut si, &mut ei);
4099 }
4100 let h = table.data_height();
4101 let w = ei - si + 1;
4102 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
4103 for (offset, ci) in (si..=ei).enumerate() {
4104 let cname = &cols[ci];
4105 let col_range = table.get_column(cname)?;
4106 let (rh, _) = col_range.dimensions();
4107 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
4108 row[offset] = col_range.get(r, 0)?;
4109 }
4110 }
4111 rows
4112 } else {
4113 return Err(ExcelError::new(ExcelErrorKind::Ref)
4114 .with_message("Column range refers to unknown column(s)".to_string()));
4115 }
4116 }
4117 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
4118 | Some(TableSpecifier::Headers) => table
4119 .headers_row()
4120 .map(|r| r.materialise().into_owned())
4121 .unwrap_or_default(),
4122 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
4123 | Some(TableSpecifier::Totals) => table
4124 .totals_row()
4125 .map(|r| r.materialise().into_owned())
4126 .unwrap_or_default(),
4127 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
4128 table
4129 .data_body()
4130 .map(|r| r.materialise().into_owned())
4131 .unwrap_or_default()
4132 }
4133 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
4134 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
4135 if let Some(h) = table.headers_row() {
4136 out.extend(h.iter_rows());
4137 }
4138 if let Some(body) = table.data_body() {
4139 out.extend(body.iter_rows());
4140 }
4141 if let Some(tr) = table.totals_row() {
4142 out.extend(tr.iter_rows());
4143 }
4144 out
4145 }
4146 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
4147 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
4148 "@ (This Row) requires table-aware context; not yet supported".to_string(),
4149 ));
4150 }
4151 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
4152 return Err(ExcelError::new(ExcelErrorKind::NImpl)
4153 .with_message("Complex structured references not yet supported".to_string()));
4154 }
4155 None => {
4156 return Err(ExcelError::new(ExcelErrorKind::NImpl)
4157 .with_message("Table reference without specifier is unsupported".to_string()));
4158 }
4159 };
4160
4161 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
4162 }
4163
4164 pub fn default_sheet_id(&self) -> SheetId {
4165 self.graph.default_sheet_id()
4166 }
4167
4168 pub fn default_sheet_name(&self) -> &str {
4169 self.graph.default_sheet_name()
4170 }
4171
4172 pub fn set_workbook_seed(&mut self, seed: u64) {
4174 self.config.workbook_seed = seed;
4175 }
4176
4177 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
4179 self.config.volatile_level = level;
4180 }
4181
4182 pub fn set_temporal_egress(&mut self, policy: crate::engine::TemporalEgress) {
4184 self.config.temporal_egress = policy;
4185 }
4186
4187 pub fn temporal_egress(&self) -> crate::engine::TemporalEgress {
4188 self.config.temporal_egress
4189 }
4190
4191 pub fn set_deterministic_mode(
4193 &mut self,
4194 mode: crate::engine::DeterministicMode,
4195 ) -> Result<(), ExcelError> {
4196 let clock = mode.build_clock()?;
4197 self.config.deterministic_mode = mode;
4198 self.clock = crate::timezone::SnapshotClock::new(clock);
4199 Ok(())
4200 }
4201
4202 pub fn set_clock(&mut self, clock: Arc<dyn crate::timezone::ClockProvider>) {
4210 self.clock = crate::timezone::SnapshotClock::new(clock);
4211 }
4212
4213 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
4214 self.config.deterministic_mode.validate()
4215 }
4216
4217 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
4218 self.graph.sheet_id(name)
4219 }
4220
4221 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
4222 self.add_sheet(name)
4223 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
4224 }
4225
4226 pub fn sheet_name(&self, id: SheetId) -> &str {
4227 self.graph.sheet_name(id)
4228 }
4229
4230 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
4231 let id = self.graph.add_sheet(name)?;
4232 self.ensure_arrow_sheet(name);
4233 self.mark_topology_edited();
4234 Ok(id)
4235 }
4236
4237 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
4238 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
4239 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
4240 })?;
4241 if new_name.is_empty() || new_name.len() > 255 {
4242 return Err(ExcelError::new(ExcelErrorKind::Value).with_message("Invalid sheet name"));
4243 }
4244 if self.graph.sheet_id(new_name).is_some() {
4245 return Err(ExcelError::new(ExcelErrorKind::Value)
4246 .with_message(format!("Sheet '{new_name}' already exists")));
4247 }
4248 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
4249
4250 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
4251 let mut copied_sheet = source_sheet;
4252 copied_sheet.name = Arc::<str>::from(new_name);
4253 self.arrow_sheets.sheets.push(copied_sheet);
4254 } else {
4255 self.ensure_arrow_sheet(new_name);
4256 }
4257
4258 let duplicated_formulas = self
4259 .graph
4260 .formula_vertices()
4261 .into_iter()
4262 .filter(|vertex| {
4263 self.graph
4264 .get_cell_ref(*vertex)
4265 .is_some_and(|cell| cell.sheet_id == new_id)
4266 })
4267 .collect::<Vec<_>>();
4268 self.graph.mark_vertices_dirty_batch(&duplicated_formulas);
4269 self.mark_topology_edited();
4270 Ok(new_id)
4271 }
4272
4273 fn ensure_arrow_sheet(&mut self, name: &str) {
4274 if self.arrow_sheets.sheet(name).is_some() {
4275 return;
4276 }
4277 self.arrow_sheets
4278 .sheets
4279 .push(crate::arrow_store::ArrowSheet {
4280 name: std::sync::Arc::<str>::from(name),
4281 date_system: self.config.date_system,
4282 columns: Vec::new(),
4283 nrows: 0,
4284 chunk_starts: Vec::new(),
4285 chunk_rows: 32 * 1024,
4286 });
4287 }
4288
4289 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
4290 let name = self.graph.sheet_name(sheet_id).to_string();
4291 self.purge_derived_formats_for_sheet(sheet_id);
4292 self.graph.remove_sheet(sheet_id)?;
4293 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
4294 self.clear_all_computed_overlays();
4298 self.mark_all_formula_vertices_dirty();
4299 self.clear_staged_formulas_for_sheet(&name);
4300 if self.row_visibility.remove(&sheet_id).is_some() {
4301 self.invalidate_row_visibility_mask_cache();
4302 }
4303 self.record_structural_change(StructuralScope::RemovedSheet(sheet_id));
4304 self.mark_topology_edited();
4305 Ok(())
4306 }
4307
4308 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
4310 if let Some(asheet) = self
4311 .arrow_sheets
4312 .sheets
4313 .iter_mut()
4314 .find(|s| s.name.as_ref() == target_name)
4315 {
4316 asheet.name = std::sync::Arc::<str>::from(new_name);
4317 return true;
4318 }
4319 false
4320 }
4321
4322 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
4323 let old_name = self.graph.sheet_name(sheet_id).to_string();
4324
4325 self.rename_sheet_in_arrow_store(&old_name, new_name);
4328
4329 match self.graph.rename_sheet(sheet_id, new_name) {
4331 Ok(_) => {
4332 self.rename_staged_formula_sheet(&old_name, new_name);
4333 let sheet_vertices: Vec<VertexId> = self
4335 .graph
4336 .grid_vertices_in_sheet(sheet_id)
4337 .map(|(id, _)| id)
4338 .collect();
4339 for v_id in sheet_vertices {
4340 self.graph.mark_vertex_dirty(v_id);
4341 }
4342 self.mark_topology_edited();
4344 Ok(())
4345 }
4346 Err(e) => {
4347 self.rename_sheet_in_arrow_store(new_name, &old_name);
4349 Err(e)
4350 }
4351 }
4352 }
4353
4354 pub fn named_ranges_iter(
4355 &self,
4356 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
4357 self.graph.named_ranges_iter()
4358 }
4359
4360 pub fn sheet_named_ranges_iter(
4361 &self,
4362 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
4363 self.graph.sheet_named_ranges_iter()
4364 }
4365
4366 pub fn resolve_name_entry(
4367 &self,
4368 name: &str,
4369 current_sheet: SheetId,
4370 ) -> Option<&crate::engine::named_range::NamedRange> {
4371 self.graph.resolve_name_entry(name, current_sheet)
4372 }
4373
4374 pub(crate) fn name_query_scope(
4384 &self,
4385 scope_sheet: Option<&str>,
4386 ) -> Result<NameScope, ExcelError> {
4387 match scope_sheet {
4388 None => Ok(NameScope::Workbook),
4389 Some(sheet) => self
4390 .graph
4391 .sheet_id(sheet)
4392 .map(NameScope::Sheet)
4393 .ok_or_else(|| {
4394 ExcelError::new(ExcelErrorKind::Ref)
4395 .with_message(format!("name scope sheet not found: {sheet}"))
4396 }),
4397 }
4398 }
4399
4400 fn resolve_sheet_locator(
4413 &self,
4414 locator: &crate::reference::SharedSheetLocator<'_>,
4415 context_sheet: SheetId,
4416 ) -> Result<SheetId, ExcelError> {
4417 self.graph
4418 .sheet_reg()
4419 .resolve_locator(locator, context_sheet)
4420 }
4421
4422 pub fn has_name(&self, name: &str, scope_sheet: Option<&str>) -> bool {
4423 let Ok(scope) = self.name_query_scope(scope_sheet) else {
4424 return false;
4425 };
4426 self.graph
4427 .resolve_name_entry_in_scope(name, scope)
4428 .is_some()
4429 }
4430
4431 pub fn resolved_name_value(
4435 &self,
4436 name: &str,
4437 scope_sheet: Option<&str>,
4438 ) -> Option<LiteralValue> {
4439 let scope = self.name_query_scope(scope_sheet).ok()?;
4440 let entry = self.graph.resolve_name_entry_in_scope(name, scope)?;
4441 self.graph.get_value(entry.vertex)
4442 }
4443
4444 pub fn table_metadata(&self, name: &str) -> Option<TableMetadata> {
4445 let entry = self.graph.resolve_table_entry(name)?;
4446 Some(TableMetadata {
4447 name: entry.name.clone(),
4448 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
4449 start_row: entry.range.start.coord.row() + 1,
4450 start_col: entry.range.start.coord.col() + 1,
4451 end_row: entry.range.end.coord.row() + 1,
4452 end_col: entry.range.end.coord.col() + 1,
4453 header_row: entry.header_row,
4454 headers: entry.headers.clone(),
4455 totals_row: entry.totals_row,
4456 })
4457 }
4458
4459 pub fn tables(&self) -> Vec<TableMetadata> {
4461 self.graph
4462 .table_names()
4463 .into_iter()
4464 .filter_map(|name| self.table_metadata(&name))
4465 .collect()
4466 }
4467
4468 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4469 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
4470
4471 for (name, named) in self.graph.named_ranges_iter() {
4472 out.push(crate::engine::named_range::NamedRangeSnapshot {
4473 name: name.clone(),
4474 scope: NameScope::Workbook,
4475 definition: named.definition.clone(),
4476 });
4477 }
4478
4479 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
4480 out.push(crate::engine::named_range::NamedRangeSnapshot {
4481 name: name.clone(),
4482 scope: NameScope::Sheet(*sheet_id),
4483 definition: named.definition.clone(),
4484 });
4485 }
4486
4487 out.sort_by(|a, b| {
4488 let a_scope = match a.scope {
4489 NameScope::Workbook => (0u8, 0u32),
4490 NameScope::Sheet(id) => (1u8, u32::from(id)),
4491 };
4492 let b_scope = match b.scope {
4493 NameScope::Workbook => (0u8, 0u32),
4494 NameScope::Sheet(id) => (1u8, u32::from(id)),
4495 };
4496 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
4497 });
4498
4499 out
4500 }
4501
4502 pub fn named_ranges_snapshot_for_sheet(
4503 &self,
4504 sheet_id: SheetId,
4505 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
4506 self.named_ranges_snapshot()
4507 .into_iter()
4508 .filter(|entry| match entry.scope {
4509 NameScope::Workbook => true,
4510 NameScope::Sheet(id) => id == sheet_id,
4511 })
4512 .collect()
4513 }
4514
4515 pub fn define_name(
4516 &mut self,
4517 name: &str,
4518 definition: NamedDefinition,
4519 scope: NameScope,
4520 ) -> Result<(), ExcelError> {
4521 self.graph.validate_define_name(name, scope)?;
4522 self.graph.define_name(name, definition, scope)?;
4523 self.record_structural_change(StructuralScope::AllSheets);
4524
4525 self.mark_topology_edited();
4526
4527 Ok(())
4528 }
4529
4530 pub fn update_name(
4531 &mut self,
4532 name: &str,
4533 definition: NamedDefinition,
4534 scope: NameScope,
4535 ) -> Result<(), ExcelError> {
4536 self.graph.validate_existing_name(name, scope)?;
4537 self.graph.update_name(name, definition, scope)?;
4538 self.record_structural_change(StructuralScope::AllSheets);
4539
4540 self.mark_topology_edited();
4541
4542 Ok(())
4543 }
4544
4545 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
4546 self.graph.validate_existing_name(name, scope)?;
4547 self.graph.delete_name(name, scope)?;
4548 self.record_structural_change(StructuralScope::AllSheets);
4549
4550 self.mark_topology_edited();
4551
4552 Ok(())
4553 }
4554
4555 pub fn define_table(
4556 &mut self,
4557 name: &str,
4558 range: crate::reference::RangeRef,
4559 header_row: bool,
4560 headers: Vec<String>,
4561 totals_row: bool,
4562 ) -> Result<(), ExcelError> {
4563 self.graph
4564 .define_table(name, range, header_row, headers, totals_row)?;
4565 self.record_structural_change(StructuralScope::AllSheets);
4566 self.mark_topology_edited();
4567 Ok(())
4568 }
4569
4570 pub fn define_source_scalar(
4571 &mut self,
4572 name: &str,
4573 version: Option<u64>,
4574 ) -> Result<(), ExcelError> {
4575 self.graph.define_source_scalar(name, version)?;
4576 self.record_structural_change(StructuralScope::OpaqueGlobal);
4577 self.mark_topology_edited();
4578 Ok(())
4579 }
4580
4581 pub fn define_source_table(
4582 &mut self,
4583 name: &str,
4584 version: Option<u64>,
4585 ) -> Result<(), ExcelError> {
4586 self.graph.define_source_table(name, version)?;
4587 self.record_structural_change(StructuralScope::OpaqueGlobal);
4588 self.mark_topology_edited();
4589 Ok(())
4590 }
4591
4592 pub fn set_source_scalar_version(
4593 &mut self,
4594 name: &str,
4595 version: Option<u64>,
4596 ) -> Result<(), ExcelError> {
4597 self.graph.set_source_scalar_version(name, version)?;
4598 Ok(())
4599 }
4600
4601 pub fn set_source_table_version(
4602 &mut self,
4603 name: &str,
4604 version: Option<u64>,
4605 ) -> Result<(), ExcelError> {
4606 self.graph.set_source_table_version(name, version)?;
4607 Ok(())
4608 }
4609
4610 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
4611 self.graph.invalidate_source(name)?;
4612 Ok(())
4613 }
4614
4615 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
4616 self.graph.get_value(vertex)
4617 }
4618
4619 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
4620 self.graph.get_cell_value(sheet, row, col)
4621 }
4622
4623 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
4624 self.graph.get_vertex_for_cell(cell)
4625 }
4626
4627 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
4628 self.graph.get_evaluation_vertices()
4629 }
4630
4631 pub fn baseline_stats(&self) -> EngineBaselineStats {
4633 let graph = self.graph.baseline_stats();
4634 EngineBaselineStats {
4635 graph_vertex_count: graph.graph_vertex_count,
4636 graph_formula_vertex_count: graph.graph_formula_vertex_count,
4637 graph_edge_count: graph.graph_edge_count,
4638 dirty_vertex_count: graph.dirty_vertex_count,
4639 evaluation_vertex_count: graph.evaluation_vertex_count,
4640 formula_ast_root_count: graph.formula_ast_root_count,
4641 formula_ast_node_count: graph.formula_ast_node_count,
4642 staged_formula_count: self.staged_formula_count(),
4643 formula_plane_active_span_count: 0,
4644 formula_plane_producer_result_entries: 0,
4645 formula_plane_consumer_read_entries: 0,
4646 formula_plane_mixed_topology_cache_builds: 0,
4647 formula_plane_mixed_topology_cache_hits: 0,
4648 formula_plane_mixed_topology_cache_overflows: 0,
4649 formula_plane_dirty_pending_events: 0,
4650 formula_plane_dirty_region_events_recorded: 0,
4651 formula_plane_dirty_span_region_events_recorded: 0,
4652 formula_plane_dirty_whole_span_seeds_recorded: 0,
4653 formula_plane_dirty_global_invalidations: 0,
4654 formula_plane_structural_span_candidates: 0,
4655 formula_plane_cycle_member_span_demotions: 0,
4656 formula_plane_array_result_span_demotions: 0,
4657 retained_scc_members: self.retained_scc_members.len(),
4658 }
4659 }
4660
4661 pub(crate) fn inspection_mutation_revision(&self) -> u64 {
4663 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
4664 }
4665
4666 #[cfg(test)]
4667 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
4668 self.used_axis_bounds_cache
4669 .read()
4670 .ok()
4671 .and_then(|guard| {
4672 guard.as_ref().map(|cache| {
4673 (
4674 cache.row_hits.load(Ordering::Relaxed),
4675 cache.row_misses.load(Ordering::Relaxed),
4676 cache.col_hits.load(Ordering::Relaxed),
4677 cache.col_misses.load(Ordering::Relaxed),
4678 )
4679 })
4680 })
4681 .unwrap_or((0, 0, 0, 0))
4682 }
4683
4684 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
4685 self.graph.set_first_load_assume_new(enabled);
4686 }
4687
4688 pub fn first_load_assume_new(&self) -> bool {
4689 self.graph.first_load_assume_new()
4690 }
4691
4692 pub fn reset_ensure_touched(&mut self) {
4693 self.graph.reset_ensure_touched();
4694 }
4695
4696 pub fn finalize_sheet_index(&mut self, sheet: &str) {
4697 self.graph.finalize_sheet_index(sheet);
4698 }
4699
4700 pub fn action<T>(
4709 &mut self,
4710 name: impl AsRef<str>,
4711 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4712 ) -> Result<T, crate::engine::EditorError> {
4713 if self.action_depth != 0 {
4714 return Err(crate::engine::EditorError::TransactionFailed {
4715 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
4716 .to_string(),
4717 });
4718 }
4719
4720 self.action_depth = 1;
4721 let engine_ptr: *mut Engine<R> = self;
4722 let _guard = ActionDepthGuard {
4723 engine: engine_ptr,
4724 _marker: std::marker::PhantomData,
4725 };
4726
4727 let mut tx = EngineAction {
4728 engine: self,
4729 name: name.as_ref().to_string(),
4730 capture: None,
4731 arrow_undo: None,
4732 atomic_policy: false,
4733 };
4734 f(&mut tx)
4735 }
4736
4737 pub fn action_atomic<T>(
4741 &mut self,
4742 name: impl Into<String>,
4743 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4744 ) -> Result<T, crate::engine::EditorError> {
4745 let (v, _j) = self.action_atomic_journal(name, f)?;
4746 Ok(v)
4747 }
4748
4749 pub fn action_atomic_journal<T>(
4751 &mut self,
4752 name: impl Into<String>,
4753 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4754 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4755 if self.action_depth != 0 {
4756 return Err(crate::engine::EditorError::TransactionFailed {
4757 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4758 .to_string(),
4759 });
4760 }
4761
4762 self.action_depth = 1;
4763 let engine_ptr: *mut Engine<R> = self;
4764 let _guard = ActionDepthGuard {
4765 engine: engine_ptr,
4766 _marker: std::marker::PhantomData,
4767 };
4768
4769 let name_str = name.into();
4770 let mut capture = MutationCapture::new(Default::default());
4771 let start_len = capture.len();
4772 self.action_atomic_impl(&mut capture, start_len, true, name_str, f)
4773 }
4774
4775 fn action_atomic_impl<T>(
4776 &mut self,
4777 capture: &mut MutationCapture,
4778 start_len: usize,
4779 expand_runs: bool,
4780 name: String,
4781 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4782 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
4783 let invalidation_baseline = self.invalidation_baseline();
4784 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
4785 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
4786
4787 let capture_ptr: *mut MutationCapture = capture;
4788 let mut tx = EngineAction {
4789 engine: self,
4790 name: name.clone(),
4791 capture: Some(capture_ptr),
4792 arrow_undo: Some(arrow_ptr),
4793 atomic_policy: true,
4794 };
4795
4796 let res = f(&mut tx);
4797
4798 let capture_ref = unsafe { &*capture_ptr };
4805 let has_runs = capture_ref.lazy_len() > 0;
4806 let graph_events: Vec<crate::engine::ChangeEvent> = if expand_runs || res.is_err() {
4807 capture_ref.expanded_events_from(start_len, 0)
4808 } else {
4809 capture_ref.events()[start_len..].to_vec()
4810 };
4811 let graph_batch = crate::engine::GraphUndoBatch {
4812 events: graph_events,
4813 };
4814 let affected_cells = arrow_undo.ops.len();
4815 let journal = crate::engine::ActionJournal {
4816 name,
4817 graph: graph_batch,
4818 arrow: arrow_undo,
4819 affected_cells,
4820 };
4821
4822 match res {
4823 Ok(v) => {
4824 if !journal.graph.is_empty() || !journal.arrow.is_empty() || has_runs {
4825 for event in &journal.graph.events {
4826 self.record_change_for_event(event);
4827 }
4828 let mut impact = Self::classify_change_events(
4829 &journal.graph.events,
4830 LoggedEditDirection::Original,
4831 )
4832 .max(Self::classify_arrow_undo(&journal.arrow));
4833 if has_runs {
4834 impact = impact.max(LoggedEditImpact::Topology);
4835 }
4836 self.apply_logged_edit_impact(impact, invalidation_baseline);
4837 }
4838 Ok((v, journal))
4839 }
4840 Err(e) => {
4841 if let Err(rb) = self.rollback_from_action_journal(&journal, invalidation_baseline)
4842 {
4843 return Err(crate::engine::EditorError::TransactionFailed {
4844 reason: format!(
4845 "Engine::action_atomic rollback failed after error '{e}': {rb}"
4846 ),
4847 });
4848 }
4849 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
4850 for event in &journal.graph.events {
4851 self.record_change_for_event(event);
4852 }
4853 }
4854 Err(e)
4855 }
4856 }
4857 }
4858
4859 pub fn action_with_logger<T>(
4866 &mut self,
4867 log: &mut crate::engine::ChangeLog,
4868 name: impl AsRef<str>,
4869 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
4870 ) -> Result<T, crate::engine::EditorError> {
4871 if self.action_depth != 0 {
4872 return Err(crate::engine::EditorError::TransactionFailed {
4873 reason: "Nested Engine::action calls are not supported (deterministic rule)"
4874 .to_string(),
4875 });
4876 }
4877
4878 self.action_depth = 1;
4879 let engine_ptr: *mut Engine<R> = self;
4880 let _guard = ActionDepthGuard {
4881 engine: engine_ptr,
4882 _marker: std::marker::PhantomData,
4883 };
4884
4885 let name_str = name.as_ref().to_string();
4886 let mut capture = MutationCapture::new(log.current_meta());
4887 let start_len = capture.len();
4888 capture.begin_compound(name_str.clone());
4889
4890 let res = self.action_atomic_impl(&mut capture, start_len, false, name_str, f);
4893 capture.close_compounds();
4894
4895 match res {
4896 Ok((v, _journal)) => {
4897 log.publish_capture(capture);
4898 Ok(v)
4899 }
4900 Err(e) => {
4901 log.discard_capture(capture);
4903 Err(e)
4904 }
4905 }
4906 }
4907
4908 fn rollback_from_action_journal(
4909 &mut self,
4910 journal: &crate::engine::ActionJournal,
4911 invalidation_baseline: InvalidationBaseline,
4912 ) -> Result<(), crate::engine::EditorError> {
4913 self.invalidate_for_action_journal(
4916 journal,
4917 LoggedEditDirection::InverseReplay,
4918 invalidation_baseline,
4919 );
4920 journal.graph.undo(&mut self.graph)?;
4922 self.apply_inverse_row_visibility_events(&journal.graph.events);
4924 self.apply_arrow_undo_batch(&journal.arrow, true);
4926 Ok(())
4927 }
4928
4929 fn rollback_from_change_events(
4930 &mut self,
4931 events: &[crate::engine::ChangeEvent],
4932 invalidation_baseline: InvalidationBaseline,
4933 ) -> Result<(), crate::engine::EditorError> {
4934 use crate::engine::ChangeEvent;
4935
4936 self.invalidate_for_change_events(
4939 events,
4940 LoggedEditDirection::InverseReplay,
4941 invalidation_baseline,
4942 );
4943
4944 self.graph
4946 .authority_set_replay(crate::engine::authority::history::Replay::Undo);
4947 let rolled_back = (|| {
4948 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
4949 let mut compound_stack: Vec<usize> = Vec::new();
4950 for (i, ev) in events.iter().enumerate().rev() {
4951 match ev {
4952 ChangeEvent::CompoundEnd { depth } => {
4953 compound_stack.push(*depth);
4954 if let Some(description) =
4955 crate::engine::graph::editor::change_log::compound_start_description(
4956 i,
4957 |j| &events[j],
4958 )
4959 {
4960 editor.inverse_compound_end(description);
4961 }
4962 }
4963 ChangeEvent::CompoundStart { depth, .. } => {
4964 if compound_stack.last() == Some(depth) {
4965 compound_stack.pop();
4966 }
4967 editor.apply_inverse(ev.clone())?;
4968 }
4969 ChangeEvent::SetRowVisibility { .. } => {
4970 }
4972 _ => {
4973 editor.apply_inverse(ev.clone())?;
4974 }
4975 }
4976 }
4977 Ok::<_, crate::engine::EditorError>(())
4978 })();
4979 self.graph
4980 .authority_set_replay(crate::engine::authority::history::Replay::Forward);
4981 rolled_back?;
4982
4983 for ev in events.iter().rev() {
4985 self.apply_inverse_row_visibility_event(ev);
4986 }
4987
4988 for ev in events.iter().rev() {
4990 self.mirror_inverse_change_to_arrow(ev);
4991 }
4992
4993 Ok(())
4994 }
4995
4996 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
4997 let sheet_id = self.graph.sheet_id(sheet)?;
4998 let coord = Coord::from_excel(row, col, true, true);
4999 let cell = CellRef::new(sheet_id, coord);
5000 let vid = self.graph.get_vertex_for_cell(&cell)?;
5001 self.graph.get_formula(vid)
5002 }
5003
5004 pub fn define_name_with_logger(
5005 &mut self,
5006 log: &mut crate::engine::ChangeLog,
5007 name: &str,
5008 definition: NamedDefinition,
5009 scope: NameScope,
5010 ) -> Result<(), crate::engine::EditorError> {
5011 self.graph
5012 .validate_define_name(name, scope)
5013 .map_err(crate::engine::EditorError::Excel)?;
5014
5015 {
5016 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
5017 editor.define_name(name, definition, scope)?;
5018 }
5019 self.record_structural_change(StructuralScope::AllSheets);
5020
5021 self.mark_topology_edited();
5022
5023 Ok(())
5024 }
5025
5026 pub fn update_name_with_logger(
5027 &mut self,
5028 log: &mut crate::engine::ChangeLog,
5029 name: &str,
5030 definition: NamedDefinition,
5031 scope: NameScope,
5032 ) -> Result<(), crate::engine::EditorError> {
5033 self.graph
5034 .validate_existing_name(name, scope)
5035 .map_err(crate::engine::EditorError::Excel)?;
5036 {
5037 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
5038 editor.update_name(name, definition, scope)?;
5039 }
5040 self.record_structural_change(StructuralScope::AllSheets);
5041
5042 self.mark_topology_edited();
5043
5044 Ok(())
5045 }
5046
5047 pub fn delete_name_with_logger(
5048 &mut self,
5049 log: &mut crate::engine::ChangeLog,
5050 name: &str,
5051 scope: NameScope,
5052 ) -> Result<(), crate::engine::EditorError> {
5053 self.graph
5054 .validate_existing_name(name, scope)
5055 .map_err(crate::engine::EditorError::Excel)?;
5056 {
5057 let mut editor = crate::engine::VertexEditor::with_logger(&mut self.graph, log);
5058 editor.delete_name(name, scope)?;
5059 }
5060 self.record_structural_change(StructuralScope::AllSheets);
5061
5062 self.mark_topology_edited();
5063
5064 Ok(())
5065 }
5066
5067 pub fn edit_with_logger<T>(
5068 &mut self,
5069 log: &mut crate::engine::ChangeLog,
5070 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
5071 ) -> Result<T, crate::engine::EditorError> {
5072 let mut capture = MutationCapture::new(log.current_meta());
5073 let result = self.edit_with_capture(&mut capture, f);
5074 capture.close_compounds();
5075 match result {
5076 Ok(value) => {
5077 log.publish_capture(capture);
5078 Ok(value)
5079 }
5080 Err(error) => {
5081 log.discard_capture(capture);
5082 Err(error)
5083 }
5084 }
5085 }
5086
5087 fn edit_with_capture<T>(
5088 &mut self,
5089 capture: &mut MutationCapture,
5090 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
5091 ) -> Result<T, crate::engine::EditorError> {
5092 let invalidation_baseline = self.invalidation_baseline();
5093 let start_len = capture.len();
5094 let lazy_start = capture.lazy_len();
5095
5096 struct ArrowSpillReader<'a> {
5099 sheets: &'a crate::arrow_store::SheetStore,
5100 }
5101 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
5102 fn read_cell_value(
5103 &self,
5104 sheet: &str,
5105 row: u32,
5106 col: u32,
5107 ) -> Option<formualizer_common::LiteralValue> {
5108 use formualizer_common::LiteralValue;
5109 let asheet = self.sheets.sheet(sheet)?;
5110 let r0 = row.saturating_sub(1) as usize;
5111 let c0 = col.saturating_sub(1) as usize;
5112 let v = asheet.get_cell_value(r0, c0);
5113 if matches!(v, LiteralValue::Empty) {
5114 None
5115 } else {
5116 Some(v)
5117 }
5118 }
5119 }
5120
5121 let ret = {
5122 let spill_reader = ArrowSpillReader {
5123 sheets: &self.arrow_sheets,
5124 };
5125 let mut editor = crate::engine::VertexEditor::with_capture_and_spill_reader(
5126 &mut self.graph,
5127 capture,
5128 &spill_reader,
5129 );
5130 f(&mut editor)
5131 };
5132
5133 let new_events = capture.events()[start_len..].to_vec();
5137 let new_runs = capture.lazy_len() > lazy_start;
5138 if new_events.iter().any(|event| {
5139 matches!(
5140 event,
5141 ChangeEvent::DefineName { .. }
5142 | ChangeEvent::UpdateName { .. }
5143 | ChangeEvent::DeleteName { .. }
5144 )
5145 }) {
5146 let all = capture.expanded_events_from(start_len, lazy_start);
5147 self.rollback_from_change_events(&all, invalidation_baseline)?;
5148 return Err(crate::engine::EditorError::TransactionUnsupported {
5149 reason: "name mutations must use Engine's prepared logged-name APIs".to_string(),
5150 });
5151 }
5152
5153 self.clear_logged_cell_format_states(&new_events);
5156 for ev in &new_events {
5157 self.mirror_forward_change_to_arrow(ev);
5158 }
5159 for ev in &new_events {
5160 self.record_change_for_event(ev);
5161 }
5162
5163 if self.action_depth == 0 {
5166 let mut impact =
5167 Self::classify_change_events(&new_events, LoggedEditDirection::Original);
5168 if new_runs {
5169 impact = impact.max(LoggedEditImpact::Topology);
5170 }
5171 self.apply_logged_edit_impact(impact, invalidation_baseline);
5172 }
5173
5174 Ok(ret)
5175 }
5176
5177 pub(crate) fn preflight_replay_admission(
5178 &mut self,
5179 events: &[ChangeEvent],
5180 forward: bool,
5181 ) -> Result<(), crate::engine::EditorError> {
5182 if !self.graph_admission_enabled() {
5183 return Ok(());
5184 }
5185 let mut vertex_delta = 0i128;
5186 let mut edge_delta = 0i128;
5187 let mut added_vertices = 0usize;
5188 let mut added_edges = 0usize;
5189 let mut formula_cells = BTreeSet::new();
5190 for event in events {
5191 match event {
5192 ChangeEvent::AddVertex {
5193 formula,
5194 coord,
5195 sheet_id,
5196 ..
5197 } => {
5198 let delta = if forward { 1 } else { -1 };
5199 vertex_delta += delta;
5200 if forward {
5201 added_vertices = added_vertices.saturating_add(1);
5202 if formula.is_some() {
5203 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
5204 }
5205 }
5206 }
5207 ChangeEvent::RemoveVertex {
5208 old_formula,
5209 coord,
5210 sheet_id,
5211 ..
5212 } => {
5213 let delta = if forward { -1 } else { 1 };
5214 vertex_delta += delta;
5215 if !forward {
5216 added_vertices = added_vertices.saturating_add(1);
5217 if old_formula.is_some()
5218 && let (Some(sheet_id), Some(coord)) = (sheet_id, coord)
5219 {
5220 formula_cells.insert((*sheet_id, coord.row(), coord.col()));
5221 }
5222 }
5223 }
5224 ChangeEvent::EdgeAdded { .. } => {
5225 let delta = if forward { 1 } else { -1 };
5226 edge_delta += delta;
5227 if forward {
5228 added_edges = added_edges.saturating_add(1);
5229 }
5230 }
5231 ChangeEvent::EdgeRemoved { .. } => {
5232 let delta = if forward { -1 } else { 1 };
5233 edge_delta += delta;
5234 if !forward {
5235 added_edges = added_edges.saturating_add(1);
5236 }
5237 }
5238 ChangeEvent::SetFormula {
5239 addr, old_formula, ..
5240 } if (forward || old_formula.is_some()) => {
5241 formula_cells.insert((addr.sheet_id, addr.coord.row(), addr.coord.col()));
5242 }
5243 _ => {}
5244 }
5245 }
5246 let stats = self.graph.baseline_stats();
5247 let final_vertices = i128::try_from(stats.graph_vertex_count)
5248 .ok()
5249 .and_then(|count| count.checked_add(vertex_delta))
5250 .and_then(|count| usize::try_from(count).ok())
5251 .ok_or_else(|| {
5252 crate::engine::EditorError::Excel(
5253 ExcelError::new(ExcelErrorKind::NImpl)
5254 .with_message("replay vertex count overflow"),
5255 )
5256 })?;
5257 let final_edges = i128::try_from(stats.graph_edge_count)
5258 .ok()
5259 .and_then(|count| count.checked_add(edge_delta))
5260 .and_then(|count| usize::try_from(count).ok())
5261 .ok_or_else(|| {
5262 crate::engine::EditorError::Excel(
5263 ExcelError::new(ExcelErrorKind::NImpl)
5264 .with_message("replay edge count overflow"),
5265 )
5266 })?;
5267 self.preflight_graph_admission(crate::engine::resource_ledger::GraphAdmission {
5268 final_vertices,
5269 final_edges,
5270 materialization_cells: formula_cells.len() as u64,
5271 added_vertices,
5272 added_edges,
5273 })
5274 .map_err(crate::engine::EditorError::Excel)
5275 }
5276
5277 pub fn undo_logged(
5282 &mut self,
5283 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5284 log: &mut crate::engine::ChangeLog,
5285 ) -> Result<(), crate::engine::EditorError> {
5286 let pending_events = log
5287 .last_group_indices()
5288 .into_iter()
5289 .map(|index| log.events()[index].clone())
5290 .collect::<Vec<_>>();
5291 self.preflight_replay_admission(&pending_events, false)?;
5292 let invalidation_baseline = self.invalidation_baseline();
5293 self.invalidate_for_change_events(
5296 &pending_events,
5297 LoggedEditDirection::InverseReplay,
5298 invalidation_baseline,
5299 );
5300 let batch = undo.undo(&mut self.graph, log)?;
5301 for item in batch.iter().rev() {
5302 self.apply_inverse_row_visibility_event(&item.event);
5303 self.apply_inverse_staged_formula_event(&item.event);
5304 }
5305 if !batch.is_empty() {
5306 let events = batch
5307 .iter()
5308 .map(|item| item.event.clone())
5309 .collect::<Vec<_>>();
5310 self.clear_logged_cell_format_states(&events);
5311 }
5312 self.mirror_undo_batch_to_arrow(&batch);
5313 if !batch.is_empty() {
5314 for item in &batch {
5315 self.record_change_for_event(&item.event);
5316 }
5317 }
5318 crate::engine::trace::fz_event!(
5319 tracing::Level::INFO,
5320 "history",
5321 "history.replay",
5322 op = "undo",
5323 events_replayed = batch.len(),
5324 ownership_resyncs = batch.len()
5325 );
5326 Ok(())
5327 }
5328
5329 pub fn redo_logged(
5330 &mut self,
5331 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5332 log: &mut crate::engine::ChangeLog,
5333 ) -> Result<(), crate::engine::EditorError> {
5334 let pending_events = undo.pending_redo_events();
5335 self.preflight_replay_admission(&pending_events, true)?;
5336 let invalidation_baseline = self.invalidation_baseline();
5337 self.invalidate_for_change_events(
5338 &pending_events,
5339 LoggedEditDirection::ForwardReplay,
5340 invalidation_baseline,
5341 );
5342 let batch = undo.redo(&mut self.graph, log)?;
5343 for item in &batch {
5344 self.apply_forward_row_visibility_event(&item.event);
5345 self.apply_forward_staged_formula_event(&item.event);
5346 }
5347 if !batch.is_empty() {
5348 let events = batch
5349 .iter()
5350 .map(|item| item.event.clone())
5351 .collect::<Vec<_>>();
5352 self.clear_logged_cell_format_states(&events);
5353 }
5354 self.mirror_redo_batch_to_arrow(&batch);
5355 if !batch.is_empty() {
5356 for item in &batch {
5357 self.record_change_for_event(&item.event);
5358 }
5359 }
5360 crate::engine::trace::fz_event!(
5361 tracing::Level::INFO,
5362 "history",
5363 "history.replay",
5364 op = "redo",
5365 events_replayed = batch.len(),
5366 ownership_resyncs = batch.len()
5367 );
5368 Ok(())
5369 }
5370
5371 pub fn undo_action(
5375 &mut self,
5376 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5377 ) -> Result<(), crate::engine::EditorError> {
5378 let Some(journal) = undo.pop_undo_action() else {
5379 return Ok(());
5380 };
5381 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, false) {
5382 undo.push_done_action(journal);
5383 return Err(error);
5384 }
5385 let invalidation_baseline = self.invalidation_baseline();
5386
5387 self.invalidate_for_action_journal(
5388 &journal,
5389 LoggedEditDirection::InverseReplay,
5390 invalidation_baseline,
5391 );
5392 journal.graph.undo(&mut self.graph)?;
5393 self.apply_inverse_row_visibility_events(&journal.graph.events);
5394 self.apply_arrow_undo_batch(&journal.arrow, true);
5395 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5396 for event in &journal.graph.events {
5397 self.record_change_for_event(event);
5398 }
5399 }
5400
5401 #[cfg(feature = "tracing")]
5402 let events_replayed = journal.graph.events.len();
5403 crate::engine::trace::fz_event!(
5404 tracing::Level::INFO,
5405 "history",
5406 "history.replay",
5407 op = "undo",
5408 events_replayed,
5409 ownership_resyncs = events_replayed
5410 );
5411 undo.push_redo_action(journal);
5412 Ok(())
5413 }
5414
5415 pub fn redo_action(
5419 &mut self,
5420 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
5421 ) -> Result<(), crate::engine::EditorError> {
5422 let Some(journal) = undo.pop_redo_action() else {
5423 return Ok(());
5424 };
5425 if let Err(error) = self.preflight_replay_admission(&journal.graph.events, true) {
5426 undo.push_redo_action(journal);
5427 return Err(error);
5428 }
5429 let invalidation_baseline = self.invalidation_baseline();
5430 self.invalidate_for_action_journal(
5431 &journal,
5432 LoggedEditDirection::ForwardReplay,
5433 invalidation_baseline,
5434 );
5435 journal.graph.redo(&mut self.graph)?;
5436 self.apply_forward_row_visibility_events(&journal.graph.events);
5437 self.apply_arrow_undo_batch(&journal.arrow, false);
5438 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
5439 for event in &journal.graph.events {
5440 self.record_change_for_event(event);
5441 }
5442 }
5443
5444 #[cfg(feature = "tracing")]
5445 let events_replayed = journal.graph.events.len();
5446 crate::engine::trace::fz_event!(
5447 tracing::Level::INFO,
5448 "history",
5449 "history.replay",
5450 op = "redo",
5451 events_replayed,
5452 ownership_resyncs = events_replayed
5453 );
5454 undo.push_done_action(journal);
5455 Ok(())
5456 }
5457
5458 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
5459 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
5460 let row = addr.coord.row() + 1;
5462 let col = addr.coord.col() + 1;
5463 (sheet, row, col)
5464 }
5465
5466 fn mirror_undo_batch_to_arrow(
5467 &mut self,
5468 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5469 ) {
5470 for item in batch.iter().rev() {
5472 self.mirror_inverse_change_to_arrow(&item.event);
5473 }
5474 }
5475
5476 fn mirror_redo_batch_to_arrow(
5477 &mut self,
5478 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
5479 ) {
5480 for item in batch.iter() {
5482 self.mirror_forward_change_to_arrow(&item.event);
5483 }
5484 }
5485
5486 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5487 use crate::engine::ChangeEvent;
5488 use formualizer_common::LiteralValue;
5489
5490 match ev {
5491 ChangeEvent::SetValue {
5492 addr,
5493 old_value,
5494 old_formula,
5495 ..
5496 } => {
5497 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5498 if old_formula.is_some() {
5499 self.clear_delta_overlay_cell(&sheet, row, col);
5500 } else {
5501 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5502 self.mirror_value_to_overlay(&sheet, row, col, &v);
5503 }
5504 }
5505 ChangeEvent::SetFormula {
5506 addr,
5507 old_value,
5508 old_formula,
5509 ..
5510 } => {
5511 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5512 if old_formula.is_some() {
5513 self.clear_delta_overlay_cell(&sheet, row, col);
5514 } else {
5515 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
5516 self.mirror_value_to_overlay(&sheet, row, col, &v);
5517 }
5518 }
5519 ChangeEvent::SpillCommitted { old, new, .. } => {
5520 self.mirror_spill_snapshot(new, true);
5522 if let Some(snap) = old {
5523 self.mirror_spill_snapshot(snap, false);
5524 }
5525 }
5526 ChangeEvent::SpillCleared { old, .. } => {
5527 self.mirror_spill_snapshot(old, false);
5529 }
5530 ChangeEvent::SetRowVisibility { .. } => {
5531 }
5533 _ => {}
5534 }
5535 }
5536
5537 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
5538 use crate::engine::ChangeEvent;
5539
5540 match ev {
5541 ChangeEvent::SetValue { addr, new, .. } => {
5542 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5543 self.mirror_value_to_overlay(&sheet, row, col, new);
5544 }
5545 ChangeEvent::SetFormula { addr, .. } => {
5546 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
5547 self.clear_delta_overlay_cell(&sheet, row, col);
5548 }
5550 ChangeEvent::SpillCommitted { old, new, .. } => {
5551 if let Some(snap) = old {
5552 self.mirror_spill_snapshot(snap, true);
5553 }
5554 self.mirror_spill_snapshot(new, false);
5555 }
5556 ChangeEvent::SpillCleared { old, .. } => {
5557 self.mirror_spill_snapshot(old, true);
5558 }
5559 ChangeEvent::SetRowVisibility { .. } => {
5560 }
5562 _ => {
5563 }
5565 }
5566 }
5567
5568 fn mirror_spill_snapshot(
5569 &mut self,
5570 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
5571 clear_only: bool,
5572 ) {
5573 use formualizer_common::LiteralValue;
5574
5575 let mut i = 0usize;
5576 for row in &snap.values {
5577 for v in row {
5578 if let Some(cell) = snap.target_cells.get(i) {
5579 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5580 let out = if clear_only {
5581 LiteralValue::Empty
5582 } else {
5583 v.clone()
5584 };
5585 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
5586 }
5587 i += 1;
5588 }
5589 }
5590 if clear_only {
5592 for cell in snap.target_cells.iter().skip(i) {
5593 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
5594 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
5595 }
5596 }
5597 }
5598
5599 pub fn set_default_sheet_by_name(&mut self, name: &str) {
5600 self.graph.set_default_sheet_by_name(name);
5601 }
5602
5603 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
5604 self.graph.set_default_sheet_by_id(id);
5605 }
5606
5607 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
5608 self.graph.set_sheet_index_mode(mode);
5609 }
5610
5611 #[cfg(feature = "test-support")]
5612 #[doc(hidden)]
5613 pub fn mark_all_formulas_dirty_for_test(&mut self) {
5614 self.mark_all_formula_vertices_dirty();
5615 }
5616
5617 #[cfg(feature = "test-support")]
5618 #[doc(hidden)]
5619 pub fn take_criteria_mask_work_for_test() -> (usize, usize) {
5620 criteria_mask_test_hooks::take_mask_work()
5621 }
5622
5623 #[cfg(feature = "test-support")]
5624 #[doc(hidden)]
5625 pub fn lookup_index_cache_report_for_test(&self) -> LookupIndexCacheReport {
5626 self.lookup_index_cache.report()
5627 }
5628
5629 #[cfg(any(test, feature = "benchmark_internal"))]
5630 #[doc(hidden)]
5631 pub fn reset_recalc_reuse_probe(&mut self) {
5632 *self.recalc_reuse_probe.get_mut().unwrap() = RecalcReuseProbe::default();
5633 }
5634
5635 #[cfg(any(test, feature = "benchmark_internal"))]
5636 #[doc(hidden)]
5637 pub fn recalc_reuse_probe(&self) -> RecalcReuseProbe {
5638 let mut probe = self.recalc_reuse_probe.lock().unwrap().clone();
5639 if let Some(cached) = self.cached_static_schedule.as_ref() {
5640 let entry_bytes = |e: &CachedScheduleEntry| {
5641 std::mem::size_of::<CachedScheduleEntry>()
5642 + e.candidate_vertices.heap_bytes()
5643 + std::mem::size_of::<crate::engine::Schedule>()
5644 + 2 * std::mem::size_of::<usize>()
5645 + schedule_probe_retained_bytes(&e.schedule)
5646 };
5647 probe.schedule_retained_bytes = entry_bytes(cached)
5648 + self.recent_schedules.iter().map(entry_bytes).sum::<usize>()
5649 + self.base_schedule.as_ref().map_or(0, entry_bytes)
5650 + self.recent_schedules.capacity() * std::mem::size_of::<CachedScheduleEntry>();
5651 }
5652 probe
5653 }
5654
5655 #[cfg(test)]
5656 pub(crate) fn cached_static_schedule_for_test(&self) -> Option<Arc<crate::engine::Schedule>> {
5657 self.cached_static_schedule
5658 .as_ref()
5659 .map(|cached| Arc::clone(&cached.schedule))
5660 }
5661
5662 fn clear_cached_static_schedule(&mut self) {
5663 self.cached_static_schedule = None;
5664 self.recent_schedules.clear();
5665 self.base_schedule = None;
5666 }
5667
5668 fn retain_recent_schedule(&mut self, entry: CachedScheduleEntry) {
5671 const RECENT_SCHEDULES: usize = 8;
5672 const RECENT_SCHEDULE_VERTICES: usize = 65_536;
5673 let revision = self.schedule_cache_authority_revision();
5674 let epoch = self.topology_epoch;
5675 self.recent_schedules
5676 .retain(|e| e.topology_epoch == epoch && e.authority_revision == revision);
5677 let current =
5678 |e: &CachedScheduleEntry| e.topology_epoch == epoch && e.authority_revision == revision;
5679 if self.base_schedule.as_ref().is_some_and(|b| !current(b)) {
5680 self.base_schedule = None;
5681 }
5682 let mut entry = entry;
5685 if current(&entry)
5686 && entry.candidate_vertices.len() > BASE_SCHEDULE_MIN_VERTICES
5687 && self
5688 .base_schedule
5689 .as_ref()
5690 .is_none_or(|b| entry.candidate_vertices.len() > b.candidate_vertices.len())
5691 {
5692 match self.base_schedule.replace(entry) {
5693 Some(previous) => entry = previous,
5694 None => return,
5695 }
5696 }
5697 if entry.topology_epoch != epoch
5698 || entry.authority_revision != revision
5699 || entry.candidate_vertices.len() > RECENT_SCHEDULE_VERTICES / 4
5700 {
5701 return;
5702 }
5703 self.recent_schedules.push(entry);
5704 let mut total: usize = self
5705 .recent_schedules
5706 .iter()
5707 .map(|e| e.candidate_vertices.len())
5708 .sum();
5709 while self.recent_schedules.len() > RECENT_SCHEDULES || total > RECENT_SCHEDULE_VERTICES {
5710 let oldest = self.recent_schedules.remove(0);
5711 total -= oldest.candidate_vertices.len();
5712 }
5713 }
5714
5715 fn invalidation_baseline(&self) -> InvalidationBaseline {
5716 InvalidationBaseline {
5717 snapshot_id: self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed),
5718 topology_epoch: self.topology_epoch,
5719 }
5720 }
5721
5722 fn classify_change_events(
5723 events: &[crate::engine::ChangeEvent],
5724 direction: LoggedEditDirection,
5725 ) -> LoggedEditImpact {
5726 use crate::engine::ChangeEvent;
5727
5728 events
5729 .iter()
5730 .map(|event| match event {
5731 ChangeEvent::CompoundStart { .. } | ChangeEvent::CompoundEnd { .. } => {
5732 LoggedEditImpact::NoOp
5733 }
5734 ChangeEvent::SetRowVisibility { .. }
5735 | ChangeEvent::SetValue {
5736 old_formula: None,
5737 old_value: Some(_),
5738 ..
5739 } => LoggedEditImpact::DataOnly,
5740 ChangeEvent::SetValue {
5744 old_formula: None,
5745 old_value: None,
5746 ..
5747 } if direction == LoggedEditDirection::Original => LoggedEditImpact::DataOnly,
5748 ChangeEvent::SetValue { .. }
5749 | ChangeEvent::SetFormula { .. }
5750 | ChangeEvent::AddVertex { .. }
5751 | ChangeEvent::RemoveVertex { .. }
5752 | ChangeEvent::VertexMoved { .. }
5753 | ChangeEvent::FormulaAdjusted { .. }
5754 | ChangeEvent::NamedRangeAdjusted { .. }
5755 | ChangeEvent::EdgeAdded { .. }
5756 | ChangeEvent::EdgeRemoved { .. }
5757 | ChangeEvent::DefineName { .. }
5758 | ChangeEvent::UpdateName { .. }
5759 | ChangeEvent::DeleteName { .. }
5760 | ChangeEvent::SpillCommitted { .. }
5761 | ChangeEvent::SpillCleared { .. }
5762 | ChangeEvent::StagedFormulaCellChanged { .. } => LoggedEditImpact::Topology,
5763 })
5764 .max()
5765 .unwrap_or(LoggedEditImpact::NoOp)
5766 }
5767
5768 fn classify_arrow_undo(arrow: &crate::engine::ArrowUndoBatch) -> LoggedEditImpact {
5769 use crate::engine::ArrowOp;
5770
5771 arrow
5772 .ops
5773 .iter()
5774 .map(|op| match op {
5775 ArrowOp::SetDeltaCell { .. } | ArrowOp::SetComputedCell { .. } => {
5776 LoggedEditImpact::DataOnly
5777 }
5778 ArrowOp::RestoreComputedRect { .. }
5779 | ArrowOp::InsertRows { .. }
5780 | ArrowOp::InsertCols { .. } => LoggedEditImpact::Topology,
5781 })
5782 .max()
5783 .unwrap_or(LoggedEditImpact::NoOp)
5784 }
5785
5786 fn apply_logged_edit_impact(
5787 &mut self,
5788 impact: LoggedEditImpact,
5789 baseline: InvalidationBaseline,
5790 ) {
5791 match impact {
5792 LoggedEditImpact::NoOp => {}
5793 LoggedEditImpact::DataOnly => {
5794 if self.topology_epoch == baseline.topology_epoch
5795 && self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
5796 == baseline.snapshot_id
5797 {
5798 self.mark_data_edited();
5799 }
5800 }
5801 LoggedEditImpact::Topology => {
5802 if self.topology_epoch == baseline.topology_epoch {
5805 self.mark_topology_edited();
5806 }
5807 }
5808 }
5809 }
5810
5811 fn invalidate_for_change_events(
5812 &mut self,
5813 events: &[crate::engine::ChangeEvent],
5814 direction: LoggedEditDirection,
5815 baseline: InvalidationBaseline,
5816 ) {
5817 self.apply_logged_edit_impact(Self::classify_change_events(events, direction), baseline);
5818 }
5819
5820 fn invalidate_for_action_journal(
5821 &mut self,
5822 journal: &crate::engine::ActionJournal,
5823 direction: LoggedEditDirection,
5824 baseline: InvalidationBaseline,
5825 ) {
5826 let impact = Self::classify_change_events(&journal.graph.events, direction)
5827 .max(Self::classify_arrow_undo(&journal.arrow));
5828 self.apply_logged_edit_impact(impact, baseline);
5829 }
5830
5831 pub fn mark_data_edited(&mut self) {
5834 self.lookup_index_cache.clear();
5835 self.snapshot_id
5836 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5837 self.has_edited = true;
5838 }
5839
5840 pub fn mark_topology_edited(&mut self) {
5842 self.lookup_index_cache.clear();
5843 self.snapshot_id
5844 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
5845 self.topology_epoch = self.topology_epoch.wrapping_add(1);
5846 self.graph.bump_topology_revision();
5847 self.clear_cached_static_schedule();
5848 if let Some(ledger) = self.active_resource_ledger.as_mut() {
5849 let released = ledger.account_mixed_cache(0);
5850 debug_assert!(released.is_ok());
5851 }
5852 self.has_edited = true;
5853 self.graph.authority_sync_eager();
5857 }
5858
5859 fn mark_all_formula_vertices_dirty(&mut self) {
5860 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
5861 for vertex in vertices {
5862 self.graph.mark_vertex_dirty(vertex);
5863 }
5864 }
5865
5866 fn mark_moved_formula_vertices_dirty(
5867 &mut self,
5868 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
5869 ) {
5870 for vertex in &summary.vertices_moved {
5871 if self.graph.has_formula(*vertex) {
5872 self.graph.mark_vertex_dirty(*vertex);
5873 }
5874 }
5875 }
5876
5877 pub fn sheet_store(&self) -> &SheetStore {
5879 &self.arrow_sheets
5880 }
5881
5882 pub(crate) fn has_row_visibility_state(&self) -> bool {
5885 !self.row_visibility.is_empty()
5886 }
5887
5888 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
5890 &mut self.arrow_sheets
5891 }
5892
5893 pub fn has_staged_formulas(&self) -> bool {
5894 !self.staged_formulas.is_empty()
5895 }
5896
5897 pub fn staged_formula_count(&self) -> usize {
5898 self.staged_formulas.values().map(StagedSheet::len).sum()
5899 }
5900
5901 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
5903 self.staged_formulas
5904 .entry(sheet.to_string())
5905 .or_default()
5906 .stage(row, col, text);
5907 self.staged_formula_index.stage(sheet, row, col);
5908 if let Some(sheet) = self.graph.sheet_id(sheet) {
5909 self.invalidate_pending_spills(StructuralScope::Cell {
5910 sheet,
5911 row: row.saturating_sub(1),
5912 col: col.saturating_sub(1),
5913 });
5914 }
5915 }
5916
5917 fn index_removed_staged_sheet(&mut self, sheet: &str, staged: &StagedSheet) {
5918 for (row, col, _) in &staged.entries {
5919 self.staged_formula_index.remove(sheet, *row, *col);
5920 }
5921 if staged.deferred_package.is_some() {
5922 self.staged_formula_index.set_package(sheet, None);
5923 }
5924 }
5925
5926 fn restore_staged_sheet(&mut self, sheet: String, staged: StagedSheet) {
5927 self.staged_formulas.insert(sheet, staged);
5928 }
5929
5930 #[doc(hidden)]
5931 pub fn source_formula_ingress(&mut self) -> SourceFormulaIngress<'_, R> {
5932 SourceFormulaIngress { engine: self }
5933 }
5934
5935 #[doc(hidden)]
5936 #[doc(hidden)]
5939 #[cfg(any(test, feature = "test-support"))]
5940 pub fn set_before_prepared_span_commit_hook(
5941 &mut self,
5942 hook: impl FnOnce() + Send + Sync + 'static,
5943 ) {
5944 self.before_prepared_span_commit_hook = Some(Box::new(hook));
5945 }
5946
5947 #[doc(hidden)]
5948 #[cfg(test)]
5950 pub(crate) fn set_before_target_preparation_commit_hook(
5951 &mut self,
5952 hook: impl FnOnce() + Send + Sync + 'static,
5953 ) {
5954 self.before_target_preparation_commit_hook = Some(Box::new(hook));
5955 }
5956
5957 fn stage_deferred_formula_package(&mut self, package: crate::engine::DeferredFormulaPackage) {
5958 if let Ok(replay) = package.replay.lock()
5959 && let Some(footprint) = replay.selection_cache_footprint()
5960 && !self
5961 .source_cache_footprints
5962 .iter()
5963 .any(|known| known.ptr_eq(&footprint))
5964 {
5965 self.source_cache_footprints.push(footprint);
5966 }
5967 let sheet = package.sheet_name.clone();
5968 let staged = self.staged_formulas.entry(sheet.clone()).or_default();
5969 debug_assert!(staged.deferred_package.is_none());
5970 staged.deferred_package = Some(package);
5971 staged.reconcile_attached_deferred_package();
5972 self.staged_formula_index
5973 .set_package(&sheet, staged.deferred_package.as_ref());
5974 if let Some(sheet_id) = self.graph.sheet_id(&sheet) {
5975 let package = self
5976 .staged_formulas
5977 .get(&sheet)
5978 .and_then(|staged| staged.deferred_package.as_ref());
5979 for &(vertex, anchor, region) in &self.blocked_pending_spills {
5980 if anchor.sheet_id == sheet_id
5981 && self.graph.vertex_exists(vertex)
5982 && self.graph.get_cell_ref(vertex) == Some(anchor)
5983 && self.staged_formula_index.package_occupies_spill(
5984 &sheet,
5985 (anchor.coord.row() + 1, anchor.coord.col() + 1),
5986 (
5987 region.rows.query_bounds().1 + 1,
5988 region.cols.query_bounds().1 + 1,
5989 ),
5990 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
5991 )
5992 {
5993 self.graph.mark_vertex_dirty(vertex);
5994 }
5995 }
5996 }
5997 }
5998
5999 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
6000 let mut removed = None;
6001 let mut remove_sheet = false;
6002 let mut had_package = false;
6003 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
6004 had_package = entries.deferred_package.is_some();
6005 removed = entries.remove(row, col);
6006 remove_sheet = entries.is_empty();
6007 }
6008 let ordinary_removed = self.staged_formula_index.remove(sheet, row, col);
6009 if !ordinary_removed && had_package {
6010 self.staged_formula_index.touch_package(sheet);
6011 }
6012 if remove_sheet {
6013 self.staged_formulas.remove(sheet);
6014 self.staged_formula_index.set_package(sheet, None);
6015 }
6016 if (ordinary_removed || had_package)
6017 && let Some(sheet) = self.graph.sheet_id(sheet)
6018 {
6019 self.invalidate_pending_spills(StructuralScope::Cell {
6020 sheet,
6021 row: row.saturating_sub(1),
6022 col: col.saturating_sub(1),
6023 });
6024 }
6025 removed
6026 }
6027
6028 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
6029 if self.staged_formulas.remove(sheet).is_some() {
6030 self.staged_formula_index.clear_sheet(sheet);
6031 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
6032 self.invalidate_pending_spills(StructuralScope::Sheet(sheet_id));
6033 }
6034 }
6035 }
6036
6037 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
6038 let Some(entries) = self.staged_formulas.remove(old) else {
6039 return;
6040 };
6041 self.staged_formula_index.clear_sheet(old);
6042 let (formulas, mut package) = entries.into_parts();
6043 for (row, col, text) in formulas {
6044 self.stage_formula_text(new, row, col, text);
6045 }
6046 if let Some(package) = package.as_mut() {
6047 package.sheet_name = new.to_string();
6048 }
6049 if let Some(package) = package {
6050 self.stage_deferred_formula_package(package);
6051 }
6052 }
6053
6054 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
6056 self.staged_formulas
6057 .get(sheet)
6058 .and_then(|v| v.get(row, col))
6059 }
6060
6061 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
6062 &self.formula_parse_diagnostics
6063 }
6064
6065 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
6066 std::mem::take(&mut self.formula_parse_diagnostics)
6067 }
6068
6069 pub fn clear_formula_parse_diagnostics(&mut self) {
6070 self.formula_parse_diagnostics.clear();
6071 }
6072
6073 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
6074 self.last_formula_ingest_report.as_ref()
6075 }
6076
6077 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
6078 &self.formula_ingest_report_total
6079 }
6080
6081 #[cfg(test)]
6082 pub(crate) fn set_before_target_planning_snapshot_hook_for_test(
6083 &mut self,
6084 hook: impl FnOnce() + Send + Sync + 'static,
6085 ) {
6086 self.before_target_planning_snapshot_hook = Some(Box::new(hook));
6087 }
6088
6089 #[cfg(test)]
6090 pub(crate) fn inject_target_semantic_stale_once_for_test(&mut self) {
6091 self.inject_target_semantic_stale_once_for_test = true;
6092 }
6093
6094 #[cfg(test)]
6095 pub(crate) fn force_virtual_dep_changes_for_test(&mut self, rounds: usize) {
6096 self.force_virtual_dep_changes_remaining_for_test = rounds;
6097 }
6098
6099 #[cfg(test)]
6100 pub(crate) fn fail_evaluation_commit_preflight_once_for_test(&mut self) {
6101 self.fail_evaluation_commit_preflight_once_for_test = true;
6102 }
6103
6104 #[cfg(test)]
6105 pub(crate) fn set_target_preparation_fault_for_test(
6106 &mut self,
6107 fault: crate::engine::target_preparation::TargetPreparationFault,
6108 ) {
6109 self.target_preparation_fault_for_test = Some(fault);
6110 }
6111
6112 #[cfg(test)]
6113 pub(crate) fn staged_formula_index_revision_for_test(&self) -> u64 {
6114 self.staged_formula_index.revision()
6115 }
6116
6117 #[cfg(test)]
6118 pub(crate) fn deferred_package_for_test(
6119 &self,
6120 sheet: &str,
6121 ) -> &crate::engine::DeferredFormulaPackage {
6122 self.staged_formulas
6123 .get(sheet)
6124 .unwrap()
6125 .deferred_package
6126 .as_ref()
6127 .unwrap()
6128 }
6129
6130 #[cfg(test)]
6131 pub(crate) fn staged_formula_index_is_consistent_for_test(&self) -> bool {
6132 let ordinary_storage = self
6133 .staged_formulas
6134 .values()
6135 .map(|sheet| sheet.entries.len())
6136 .sum::<usize>();
6137 let package_storage = self
6138 .staged_formulas
6139 .values()
6140 .filter(|sheet| sheet.deferred_package.is_some())
6141 .count();
6142 ordinary_storage == self.staged_formula_index.ordinary_count()
6143 && package_storage == self.staged_formula_index.package_count()
6144 && self.staged_formulas.iter().all(|(name, sheet)| {
6145 sheet.entries.iter().all(|(row, col, _)| {
6146 let leases = self
6147 .staged_formula_index
6148 .leases_in_region(name, *row, *col, *row, *col);
6149 leases.len() == 1 && leases[0].row == *row && leases[0].col == *col
6150 })
6151 })
6152 }
6153
6154 #[cfg(test)]
6155 pub(crate) fn evaluation_request_begin_count_for_test(&self) -> u64 {
6156 self.evaluation_request_begin_count_for_test
6157 }
6158
6159 #[cfg(test)]
6160 pub(crate) fn set_before_legacy_fallback_final_provider_sample_hook(
6161 &mut self,
6162 hook: impl FnOnce() + Send + Sync + 'static,
6163 ) {
6164 self.before_legacy_fallback_final_provider_sample_hook = Some(Box::new(hook));
6165 }
6166
6167 #[cfg(test)]
6168 pub(crate) fn set_after_eager_proposal_commit_hook(
6169 &mut self,
6170 hook: impl FnOnce() + Send + Sync + 'static,
6171 ) {
6172 self.after_eager_proposal_commit_hook = Some(Box::new(hook));
6173 }
6174
6175 #[cfg(test)]
6176 pub(crate) fn topology_epoch_for_test(&self) -> u64 {
6177 self.topology_epoch
6178 }
6179
6180 #[cfg(test)]
6181 pub(crate) fn graph_topology_revision_for_test(&self) -> u64 {
6182 self.graph.topology_revision()
6183 }
6184
6185 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
6186 self.formula_ingest_report_total.mode = report.mode;
6187 self.formula_ingest_report_total.accumulate(&report);
6188 self.last_formula_ingest_report = Some(report);
6189 }
6190
6191 fn collect_planning_function_requests(
6192 ast: &ASTNode,
6193 requests: &mut Vec<(String, String, usize)>,
6194 ) {
6195 match &ast.node_type {
6196 ASTNodeType::Function { name, args } => {
6197 requests.push((String::new(), name.clone(), args.len()));
6198 for arg in args {
6199 Self::collect_planning_function_requests(arg, requests);
6200 }
6201 }
6202 ASTNodeType::BinaryOp { left, right, .. } => {
6203 Self::collect_planning_function_requests(left, requests);
6204 Self::collect_planning_function_requests(right, requests);
6205 }
6206 ASTNodeType::UnaryOp { expr, .. } => {
6207 Self::collect_planning_function_requests(expr, requests);
6208 }
6209 ASTNodeType::Call { callee, args } => {
6210 Self::collect_planning_function_requests(callee, requests);
6211 for arg in args {
6212 Self::collect_planning_function_requests(arg, requests);
6213 }
6214 }
6215 ASTNodeType::Array(rows) => {
6216 for cell in rows.iter().flatten() {
6217 Self::collect_planning_function_requests(cell, requests);
6218 }
6219 }
6220 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => {}
6221 }
6222 }
6223
6224 fn prepared_function_semantics_changed(
6225 &self,
6226 preparation: &crate::engine::FormulaCompressedPreparation,
6227 guard: &crate::function_registry::SemanticEpochReadGuard,
6228 ) -> bool {
6229 if preparation.function_semantic_epoch == guard.epoch() {
6230 return false;
6231 }
6232
6233 guard
6234 .semantic_changes_affect_requests_since(preparation.function_semantic_epoch, Vec::new())
6235 }
6236
6237 pub(crate) fn prepare_source_formula_families(
6238 &mut self,
6239 sheet_name: &str,
6240 families: &[crate::engine::SourceFormulaFamily],
6241 ) -> crate::engine::FormulaCompressedPreparation {
6242 crate::engine::FormulaCompressedPreparation {
6243 engine_token: Arc::clone(&self.source_formula_token),
6244 function_semantic_epoch: crate::function_registry::semantic_epoch(),
6245 function_provider_revision: None,
6246 function_semantics_used: false,
6247 sheet_name: Arc::from(sheet_name),
6248 rejected: BTreeMap::new(),
6249 eager_replay: Vec::new(),
6250 preparation_spool_replays: 0,
6251 clean_rejected_anchor_counts: [0; 3],
6252 fragmented_rejected_anchor_counts: [0; 3],
6253 exact_replay: None,
6254 replay_disposition: crate::engine::FormulaReplayDisposition::default(),
6255 }
6256 }
6257
6258 fn prepare_source_formula_proposals(
6259 &mut self,
6260 sheet_name: &str,
6261 families: &[crate::engine::SourceFormulaFamily],
6262 authority_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6263 replay_partitions: &[crate::engine::PartitionedSourceFormulaFamily],
6264 formula_record_count: u64,
6265 replay: Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6266 suppressed: &BTreeSet<(u32, u32)>,
6267 consumed: &FxHashSet<(crate::engine::SourceFamilyId, crate::engine::SourceCoord)>,
6268 consumed_engine: Option<&Arc<()>>,
6269 ) -> Result<crate::engine::FormulaCompressedPreparation, ExcelError> {
6270 let mut preparation = self.prepare_source_formula_families(sheet_name, families);
6271 preparation.exact_replay = Some(Arc::clone(&replay));
6272 preparation
6273 .replay_disposition
6274 .register_consumed_members(consumed_engine, consumed.iter().copied());
6275 if !preparation
6276 .replay_disposition
6277 .consumed_engine_matches(&self.source_formula_token)
6278 {
6279 return Err(ExcelError::new(ExcelErrorKind::Value)
6280 .with_message("ResidualConsumedEngineMismatch"));
6281 }
6282 preparation
6283 .replay_disposition
6284 .extend_suppressed_excel_coords(suppressed.iter().copied());
6285 Ok(preparation)
6286 }
6287
6288 fn formula_batch_from_exact_replay(
6289 &mut self,
6290 sheet_name: &str,
6291 replayed: impl IntoIterator<Item = crate::engine::DeferredReplayFormula>,
6292 ) -> Result<FormulaIngestBatch, ExcelError> {
6293 let mut cache = rustc_hash::FxHashMap::default();
6294 let mut formulas = Vec::new();
6295 for record in replayed {
6296 let key = if record.text.starts_with('=') {
6297 record.text
6298 } else {
6299 format!("={}", record.text)
6300 };
6301 let ast_id = if let Some(cached) = cache.get(&key) {
6302 *cached
6303 } else {
6304 let parsed = match formualizer_parse::parser::parse(&key) {
6305 Ok(parsed) => parsed,
6306 Err(error) => {
6307 let Some(parsed) = self.handle_formula_parse_error(
6308 sheet_name,
6309 record.row,
6310 record.col,
6311 &key,
6312 error.to_string(),
6313 )?
6314 else {
6315 continue;
6316 };
6317 parsed
6318 }
6319 };
6320 let ast_id = self.intern_formula_ast(&parsed);
6321 cache.insert(key.clone(), ast_id);
6322 ast_id
6323 };
6324 formulas.push(
6325 FormulaIngestRecord::new(record.row, record.col, ast_id, Some(Arc::from(key)))
6326 .with_source_proof(record.source_order, record.family, record.partition_owner),
6327 );
6328 }
6329 Ok(FormulaIngestBatch::new(sheet_name.to_string(), formulas))
6330 }
6331
6332 fn prepare_target_combined_legacy_graph(
6333 &self,
6334 packages: &[PreparedTargetSourcePackage],
6335 ordinary: &[PreparedOrdinaryStagedFormula],
6336 ) -> Result<(PreparedLegacyGraphPlan, usize), ExcelError> {
6337 let mut planned_by_coord = BTreeMap::new();
6338 for package in packages {
6339 for (row, col, ast_id, plan) in &package.legacy {
6340 planned_by_coord.insert((package.sheet_id, *row, *col), (*ast_id, plan.clone()));
6341 }
6342 }
6343 for formula in ordinary {
6344 if let Some((ast_id, plan)) = formula.ast_id.zip(formula.plan.clone()) {
6345 planned_by_coord.insert(
6346 (formula.sheet_id, formula.lease.row, formula.lease.col),
6347 (ast_id, plan),
6348 );
6349 }
6350 }
6351 let planned = planned_by_coord
6352 .into_iter()
6353 .map(|((sheet_id, row, col), (ast_id, plan))| (sheet_id, row, col, ast_id, plan))
6354 .collect::<Vec<_>>();
6355 let formula_count = planned.len();
6356 let graph = self
6357 .graph
6358 .prepare_legacy_graph_plan_multi_sheet(planned)
6359 .map_err(|error| {
6360 ExcelError::new(ExcelErrorKind::Value)
6361 .with_message(format!("target graph preparation failed: {error}"))
6362 })?;
6363 Ok((graph, formula_count))
6364 }
6365
6366 fn replay_target_coordinates(
6367 &mut self,
6368 replay: &Arc<std::sync::Mutex<Box<dyn crate::engine::DeferredFormulaReplay>>>,
6369 coordinates: &[(u32, u32)],
6370 deadline: Option<std::time::Instant>,
6371 scratch: &mut u64,
6372 ) -> Result<Option<Vec<crate::engine::DeferredReplayFormula>>, ExcelError> {
6373 if coordinates.is_empty() {
6374 return Ok(Some(Vec::new()));
6375 }
6376 let mut guard = replay.lock().map_err(|_| {
6377 ExcelError::new(ExcelErrorKind::Value)
6378 .with_message("deferred formula spool lock poisoned")
6379 })?;
6380 let retained = guard.selection_cache_footprint().is_some();
6381 let records = guard.replay_selected_exact(coordinates, &mut |work, bytes| {
6382 self.target_preparation_checkpoint(deadline, work)?;
6383 if retained && let Some(ledger) = self.active_resource_ledger.as_mut() {
6384 ledger
6385 .reserve_retained(bytes)
6386 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
6387 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
6388 }
6389 self.reserve_graph_source_scratch(bytes)?;
6390 *scratch = scratch.saturating_add(bytes);
6391 Ok(())
6392 });
6393 drop(guard);
6394 let reconciled = self.reconcile_source_cache_footprints();
6395 let records = records?;
6396 reconciled?;
6397 Ok(records)
6398 }
6399
6400 fn prepare_target_exact_source_selection(
6401 &mut self,
6402 sheet: &str,
6403 lease: StagedPackageLease,
6404 coordinates: Vec<(u32, u32)>,
6405 previous: &BTreeSet<(u32, u32)>,
6406 allow_partial_shared: bool,
6407 deadline: Option<std::time::Instant>,
6408 scratch: &mut u64,
6409 ) -> Result<Option<PreparedTargetSourcePackage>, ExcelError> {
6410 let package = self
6411 .staged_formulas
6412 .get(sheet)
6413 .and_then(|s| s.deferred_package.as_ref())
6414 .unwrap();
6415 if !package.source_geometry_complete
6416 || ((!package.families.is_empty() || !package.partitioned_families.is_empty())
6417 && !package.coordinates_cover_families)
6418 || package.reconciliation_replay.is_some()
6419 {
6420 return Ok(None);
6421 }
6422 let selected_points: BTreeSet<_> = coordinates
6423 .into_iter()
6424 .filter(|point| !package.suppressed.contains(point))
6425 .collect();
6426 let mut prepared = PreparedTargetSourcePackage::empty_selection(
6427 sheet,
6428 self.graph.sheet_id(sheet).unwrap(),
6429 lease,
6430 );
6431 if selected_points.is_empty() {
6432 return Ok(Some(prepared));
6433 }
6434 let mut routing = crate::engine::FormulaReplayDisposition::default();
6435 for partition in &package.partitioned_families {
6436 routing
6437 .register_partition(partition, false)
6438 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?;
6439 }
6440 routing.extend_suppressed_excel_coords(package.suppressed.iter().copied());
6441 routing.register_consumed_members(
6442 package.consumed_engine.as_ref(),
6443 package.consumed_members.iter().copied(),
6444 );
6445 let replay = Arc::clone(&package.replay);
6446 let source_report = package.accounting_report();
6447 prepared.source_report = source_report;
6448 prepared.disposition = routing.clone();
6449
6450 let replay_points: BTreeSet<_> = selected_points
6451 .iter()
6452 .copied()
6453 .filter(|&(row, col)| !prepared.direct_contains(row, col))
6454 .collect();
6455 let coordinates: Vec<_> = replay_points.iter().copied().collect();
6456 let records = self.replay_target_coordinates(&replay, &coordinates, deadline, scratch)?;
6457 prepared.spool_replays = u64::from(!coordinates.is_empty());
6458 let Some(mut replay_records) = records else {
6459 return Ok(None);
6460 };
6461 for record in &mut replay_records {
6462 if record.family.is_none() {
6463 record.partition_owner = routing
6464 .ordinary_disposition(crate::engine::SourceCoord {
6465 row: record.row.saturating_sub(1),
6466 col: record.col.saturating_sub(1),
6467 })
6468 .1;
6469 }
6470 }
6471 replay_records.sort_by_key(|record| record.source_order);
6472 if replay_records
6473 .iter()
6474 .any(|record| !selected_points.contains(&(record.row, record.col)))
6475 || replay_records
6476 .windows(2)
6477 .any(|records| records[0].source_order == records[1].source_order)
6478 {
6479 return Err(ExcelError::new(ExcelErrorKind::Value)
6480 .with_message("invalid indexed exact source selection"));
6481 }
6482 let represented: BTreeSet<_> = replay_records.iter().map(|r| (r.row, r.col)).collect();
6483 if represented != replay_points {
6484 return Err(ExcelError::new(ExcelErrorKind::Value)
6485 .with_message("incomplete indexed exact source selection"));
6486 }
6487 let source = self
6490 .staged_formulas
6491 .get(sheet)
6492 .unwrap()
6493 .deferred_package
6494 .as_ref()
6495 .unwrap();
6496 let contains = |rect: crate::engine::SourceRect, coord: crate::engine::SourceCoord| {
6497 coord.row >= rect.start.row
6498 && coord.row <= rect.end.row
6499 && coord.col >= rect.start.col
6500 && coord.col <= rect.end.col
6501 };
6502 let mut checked = BTreeSet::new();
6503 for record in replay_records.iter().rev() {
6504 if !checked.insert((record.row, record.col)) {
6505 continue;
6506 }
6507 let coord = crate::engine::SourceCoord {
6508 row: record.row - 1,
6509 col: record.col - 1,
6510 };
6511 let agrees = |owner, shared: bool| {
6512 record.family == shared.then_some(owner)
6513 && record.partition_owner.or(record.family) == Some(owner)
6514 };
6515 let mut valid = true;
6516 for family in &source.families {
6517 let owns = match &family.members {
6518 crate::engine::SourceFamilyMembers::CompleteDomain(domain) => {
6519 contains(domain.rect(), coord)
6520 }
6521 crate::engine::SourceFamilyMembers::ExplicitMembers(members) => {
6522 members.as_slice().binary_search(&coord).is_ok()
6523 }
6524 };
6525 if owns && !agrees(family.source_id, true) {
6526 valid = false;
6527 }
6528 }
6529 for family in &source.partitioned_families {
6530 if family
6531 .fragments
6532 .iter()
6533 .any(|fragment| contains(fragment.rect(), coord))
6534 && !agrees(family.source_id, true)
6535 {
6536 valid = false;
6537 }
6538 if let Some(member) = family
6539 .legacy_members
6540 .as_slice()
6541 .iter()
6542 .find(|member| member.coord == coord)
6543 && !agrees(
6544 family.source_id,
6545 member.kind == crate::engine::PartitionLegacyMemberKind::SharedFamilyMember,
6546 )
6547 {
6548 valid = false;
6549 }
6550 }
6551 if !valid {
6552 return Err(ExcelError::new(ExcelErrorKind::Value)
6553 .with_message("indexed source ownership mismatch"));
6554 }
6555 }
6556 prepared.selected_points = Some(selected_points);
6557 prepared.replay_records = replay_records;
6558 Ok(Some(prepared))
6559 }
6560
6561 fn prepare_target_source_package(
6562 &mut self,
6563 sheet: &str,
6564 lease: StagedPackageLease,
6565 deadline: Option<std::time::Instant>,
6566 ) -> Result<PreparedTargetSourcePackage, ExcelError> {
6567 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
6568 ExcelError::new(ExcelErrorKind::Ref)
6569 .with_message(format!("deferred source sheet not found: {sheet}"))
6570 })?;
6571 let (
6572 source_report,
6573 families,
6574 partitions,
6575 replay,
6576 invalidated,
6577 suppressed,
6578 consumed_members,
6579 consumed_engine,
6580 reconciliation_replay,
6581 ) = {
6582 let package = self
6583 .staged_formulas
6584 .get(sheet)
6585 .and_then(|staged| staged.deferred_package.as_ref())
6586 .ok_or_else(|| {
6587 ExcelError::new(ExcelErrorKind::Value)
6588 .with_message("staged deferred source package is unavailable")
6589 })?;
6590 if package.sheet_name != sheet {
6591 return Err(ExcelError::new(ExcelErrorKind::Value)
6592 .with_message("deferred formula package sheet mismatch"));
6593 }
6594 (
6595 package.accounting_report(),
6596 package.families.clone(),
6597 package.partitioned_families.clone(),
6598 Arc::clone(&package.replay),
6599 package.invalidated.clone(),
6600 package.suppressed.clone(),
6601 package.consumed_members.clone(),
6602 package.consumed_engine.clone(),
6603 package.reconciliation_replay.clone(),
6604 )
6605 };
6606
6607 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
6608 for partition in &partitions {
6609 replay_disposition
6610 .register_partition(partition, false)
6611 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
6612 }
6613 replay_disposition.extend_suppressed_excel_coords(suppressed.iter().copied());
6614 replay_disposition
6615 .register_consumed_members(consumed_engine.as_ref(), consumed_members.iter().copied());
6616 if !replay_disposition.consumed_engine_matches(&self.source_formula_token) {
6617 return Err(ExcelError::new(ExcelErrorKind::Value)
6618 .with_message("ResidualConsumedEngineMismatch"));
6619 }
6620 self.target_preparation_checkpoint(deadline, 1)?;
6621 let mut replay_records = if let Some(mut records) = reconciliation_replay {
6622 records.retain(|record| {
6623 let Some((row, col)) = record.row.checked_sub(1).zip(record.col.checked_sub(1))
6624 else {
6625 return true;
6626 };
6627 let coord = crate::engine::SourceCoord { row, col };
6628 let disposition = record.family.map_or_else(
6629 || replay_disposition.ordinary_disposition(coord).0,
6630 |family| replay_disposition.shared_disposition(family, coord),
6631 );
6632 !matches!(
6633 disposition,
6634 crate::engine::FormulaReplayCoordinateDisposition::Direct
6635 | crate::engine::FormulaReplayCoordinateDisposition::Suppressed
6636 )
6637 });
6638 records
6639 } else {
6640 replay
6641 .lock()
6642 .map_err(|_| {
6643 ExcelError::new(ExcelErrorKind::Value)
6644 .with_message("deferred formula spool lock poisoned")
6645 })?
6646 .replay_partitioned(&replay_disposition, &partitions)
6647 .map_err(|message| ExcelError::new(ExcelErrorKind::Value).with_message(message))?
6648 };
6649 replay_records.sort_by_key(|record| record.source_order);
6650 for chunk in replay_records.chunks(256) {
6651 self.target_preparation_checkpoint(deadline, chunk.len() as u64)?;
6652 }
6653 if replay_records
6654 .windows(2)
6655 .any(|records| records[0].source_order == records[1].source_order)
6656 {
6657 return Err(ExcelError::new(ExcelErrorKind::Value)
6658 .with_message("duplicate deferred source-order proof"));
6659 }
6660
6661 let disposition = replay_disposition;
6662 let direct_families = 0usize;
6663 let direct_cells = 0u64;
6664 let direct_fragments = 0u64;
6665 let direct_complete_families = 0u64;
6666 let direct_complete_cells = 0u64;
6667 let direct_partition_families = 0u64;
6668 let direct_partition_cells = 0u64;
6669 let anchor_parses = 0u64;
6670 let anchor_asts = 0u64;
6671 let anchor_analyses = 0u64;
6672
6673 Ok(PreparedTargetSourcePackage {
6674 sheet: sheet.to_string(),
6675 sheet_id,
6676 lease,
6677 selected_points: None,
6678 complete_selections: Default::default(),
6679 deferred_shared: false,
6680 direct_domains: Vec::new(),
6681 source_report,
6682 replay_records,
6683 spool_replays: 1,
6684 disposition,
6685 legacy: Vec::new(),
6686 direct_families,
6687 direct_cells,
6688 direct_fragments,
6689 direct_complete_families,
6690 direct_complete_cells,
6691 direct_partition_families,
6692 direct_partition_cells,
6693 anchor_parses,
6694 anchor_asts,
6695 anchor_analyses,
6696 })
6697 }
6698
6699 fn fallback_planning_snapshot(
6700 &self,
6701 batch: &FormulaIngestBatch,
6702 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
6703 let mut requests = Vec::new();
6704 for formula in &batch.formulas {
6705 let ast = self
6706 .graph
6707 .data_store()
6708 .retrieve_ast(formula.ast_id, self.graph.sheet_reg())
6709 .ok_or_else(|| {
6710 ExcelError::new(ExcelErrorKind::Value)
6711 .with_message("ordered fallback AST is unavailable")
6712 })?;
6713 Self::collect_planning_function_requests(&ast, &mut requests);
6714 }
6715 requests.sort();
6716 requests.dedup();
6717 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
6718 &self.resolver,
6719 requests,
6720 )
6721 .map_err(|error| ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}")))
6722 }
6723
6724 fn prepare_legacy_batch_fallback(
6725 &mut self,
6726 batch: FormulaIngestBatch,
6727 function_provider: &dyn crate::traits::FunctionProvider,
6728 ) -> Result<
6729 (
6730 crate::engine::graph::prepared_legacy_graph::PreparedLegacyGraphPlan,
6731 u64,
6732 ),
6733 ExcelError,
6734 > {
6735 let formula_count = batch.formulas.len() as u64;
6736 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
6737 let mut planned = Vec::with_capacity(batch.formulas.len());
6738 for record in batch.formulas {
6739 let placement = CellRef::new(
6740 sheet_id,
6741 Coord::from_excel(record.row, record.col, true, true),
6742 );
6743 let ingested = self
6744 .graph
6745 .ingest_pipeline(function_provider)
6746 .enable_function_semantics()
6747 .ingest_formula(
6748 FormulaAstInput::RawArena(record.ast_id),
6749 placement,
6750 record.formula_text,
6751 )
6752 .map_err(|error| {
6753 ExcelError::new(ExcelErrorKind::Value).with_message(format!("{error:?}"))
6754 })?;
6755 planned.push((record.row, record.col, ingested.ast_id, ingested.dep_plan));
6756 }
6757 let plan = self
6758 .graph
6759 .prepare_legacy_graph_plan(sheet_id, planned)
6760 .map_err(|error| {
6761 ExcelError::new(ExcelErrorKind::Value).with_message(error.to_string())
6762 })?;
6763 Ok((plan, formula_count))
6764 }
6765
6766 fn publish_compressed_partial_report(
6767 &mut self,
6768 report: &FormulaIngestReport,
6769 direct_report: &FormulaIngestReport,
6770 ) {
6771 if direct_report.source_family_promoted == 0
6772 && direct_report.graph_formula_cells_materialized == 0
6773 {
6774 return;
6775 }
6776 let mut published = report.clone();
6777 published.accumulate(direct_report);
6778 self.record_formula_ingest_report(published);
6779 }
6780
6781 fn finish_compressed_formula_sources(
6782 &mut self,
6783 batches: Vec<(
6784 FormulaIngestBatch,
6785 crate::engine::FormulaCompressedSourceReport,
6786 crate::engine::FormulaCompressedPreparation,
6787 )>,
6788 ) -> Result<FormulaIngestReport, ExcelError> {
6789 self.observe_function_semantic_epoch()?;
6790 if batches.iter().any(|(_, _, preparation)| {
6791 !Arc::ptr_eq(&preparation.engine_token, &self.source_formula_token)
6792 }) {
6793 return Err(ExcelError::new(ExcelErrorKind::Value)
6794 .with_message("compressed source preparation belongs to another engine"));
6795 }
6796 if batches.iter().any(|(fallback, _, preparation)| {
6797 preparation.sheet_name.as_ref() != fallback.sheet_name
6798 }) {
6799 return Err(ExcelError::new(ExcelErrorKind::Value)
6800 .with_message("compressed source preparation sheet mismatch"));
6801 }
6802 let initial_guard = crate::function_registry::semantic_epoch_read_guard();
6803 let initial_provider_revision = self.resolver.planning_semantic_revision();
6804 let mut fallback_batches = Vec::with_capacity(batches.len());
6805 let mut stale_fallback_batches = Vec::new();
6806 let mut pending_preparations = Vec::new();
6807 for (mut fallback, mut source, mut preparation) in batches {
6808 for formula in fallback.formulas.drain(..) {
6809 let source_order = formula.source_order.ok_or_else(|| {
6810 ExcelError::new(ExcelErrorKind::Value).with_message(
6811 "compressed source supplied formulas without source-order proof",
6812 )
6813 })?;
6814 let text = formula.formula_text.ok_or_else(|| {
6815 ExcelError::new(ExcelErrorKind::Value).with_message(
6816 "ordered compressed fallback formula has no exact source text",
6817 )
6818 })?;
6819 preparation
6820 .eager_replay
6821 .push(crate::engine::DeferredReplayFormula {
6822 source_order,
6823 row: formula.row,
6824 col: formula.col,
6825 text: text.to_string(),
6826 family: formula.source_family,
6827 partition_owner: formula.partition_owner,
6828 });
6829 }
6830 preparation
6831 .eager_replay
6832 .sort_by_key(|record| record.source_order);
6833 source.source_spool_replays = source
6834 .source_spool_replays
6835 .saturating_add(preparation.preparation_spool_replays);
6836 let stale_reason = preparation
6837 .function_semantics_used
6838 .then(|| {
6839 if preparation.function_provider_revision != initial_provider_revision {
6840 Some("FunctionProviderRevisionChanged")
6841 } else if self.prepared_function_semantics_changed(&preparation, &initial_guard)
6842 {
6843 Some("FunctionSemanticEpochChanged")
6844 } else {
6845 None
6846 }
6847 })
6848 .flatten();
6849 let stale_semantics = stale_reason.is_some();
6850 for reason in preparation.rejected.values() {
6851 *source.fallback_reasons.entry(reason.clone()).or_default() += 1;
6852 }
6853 source.replay_families = source.families_seen;
6855 source.replay_cells = source.family_cells_seen;
6856 let compressed = crate::engine::FormulaCompressedSourceBatch::new(
6857 fallback.sheet_name.clone(),
6858 source,
6859 );
6860 if stale_semantics {
6861 stale_fallback_batches.push((fallback, compressed));
6862 } else {
6863 fallback_batches.push((fallback, compressed));
6864 }
6865 pending_preparations.push((preparation, stale_semantics));
6866 }
6867 drop(initial_guard);
6868
6869 let configured_mode = self.config.formula_plane_mode;
6871 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6872 let stale_result =
6873 self.ingest_compressed_formula_source_batches_inner(stale_fallback_batches, false);
6874 self.config.formula_plane_mode = configured_mode;
6875 let mut report = stale_result?;
6876 report.mode = configured_mode;
6877
6878 self.config.formula_plane_mode = FormulaPlaneMode::Off;
6879 let fallback_result =
6880 self.ingest_compressed_formula_source_batches_inner(fallback_batches, false);
6881 self.config.formula_plane_mode = configured_mode;
6882 match fallback_result {
6883 Ok(fallback_report) => report.accumulate(&fallback_report),
6884 Err(error) => {
6885 self.record_formula_ingest_report(report);
6886 return Err(error);
6887 }
6888 }
6889
6890 let mut direct_report =
6891 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
6892 for (preparation, _) in &pending_preparations {
6893 direct_report.source_anchor_parses = direct_report.source_anchor_parses.saturating_add(
6894 preparation.clean_rejected_anchor_counts[0]
6895 .saturating_add(preparation.fragmented_rejected_anchor_counts[0]),
6896 );
6897 direct_report.source_anchor_asts = direct_report.source_anchor_asts.saturating_add(
6898 preparation.clean_rejected_anchor_counts[1]
6899 .saturating_add(preparation.fragmented_rejected_anchor_counts[1]),
6900 );
6901 direct_report.source_anchor_analyses =
6902 direct_report.source_anchor_analyses.saturating_add(
6903 preparation.clean_rejected_anchor_counts[2]
6904 .saturating_add(preparation.fragmented_rejected_anchor_counts[2]),
6905 );
6906 }
6907 loop {
6908 #[cfg(any(test, feature = "test-support"))]
6909 if let Some(hook) = self.before_prepared_span_commit_hook.take() {
6910 hook();
6911 }
6912 let commit_guard = crate::function_registry::semantic_epoch_read_guard();
6913 let commit_provider_revision = self.resolver.planning_semantic_revision();
6914 let mut newly_stale = Vec::new();
6915 let mut current = Vec::new();
6916 for pending in pending_preparations.drain(..) {
6917 let stale_reason = pending
6918 .0
6919 .function_semantics_used
6920 .then(|| {
6921 if pending.0.function_provider_revision != commit_provider_revision {
6922 Some("FunctionProviderRevisionChanged")
6923 } else if self
6924 .prepared_function_semantics_changed(&pending.0, &commit_guard)
6925 {
6926 Some("FunctionSemanticEpochChanged")
6927 } else {
6928 None
6929 }
6930 })
6931 .flatten();
6932 if let Some(reason) = stale_reason {
6933 newly_stale.push((pending, reason));
6934 } else {
6935 current.push(pending);
6936 }
6937 }
6938 if !newly_stale.is_empty() {
6939 drop(commit_guard);
6940 for ((mut preparation, was_initially_stale), reason) in newly_stale {
6941 preparation
6942 .eager_replay
6943 .sort_by_key(|record| record.source_order);
6944 direct_report.source_spool_replays =
6945 direct_report.source_spool_replays.saturating_add(1);
6946 if !was_initially_stale {
6947 direct_report
6948 .fallback_reasons
6949 .entry(reason.to_string())
6950 .or_default();
6951 }
6952 preparation.function_semantics_used = false;
6953 if !preparation.eager_replay.is_empty() {
6954 current.push((preparation, false));
6955 }
6956 }
6957 pending_preparations = current;
6958 continue;
6959 }
6960 drop(commit_guard);
6961
6962 enum SourceProposal {
6964 KnownFallback {
6965 source_order: crate::engine::SourceFormulaOrder,
6966 records: Vec<crate::engine::DeferredReplayFormula>,
6967 },
6968 }
6969
6970 impl SourceProposal {
6971 fn source_order(&self) -> crate::engine::SourceFormulaOrder {
6972 match self {
6973 Self::KnownFallback { source_order, .. } => *source_order,
6974 }
6975 }
6976 }
6977
6978 for (mut preparation, _) in current {
6979 let mut proposals = Vec::with_capacity(preparation.eager_replay.len());
6980 let mut family_fallbacks: BTreeMap<_, Vec<_>> = BTreeMap::new();
6981 for record in preparation.eager_replay.drain(..) {
6982 if let Some(owner) = record.partition_owner.or(record.family) {
6983 family_fallbacks.entry(owner).or_default().push(record);
6984 } else {
6985 proposals.push(SourceProposal::KnownFallback {
6986 source_order: record.source_order,
6987 records: vec![record],
6988 });
6989 }
6990 }
6991 for (_, mut records) in family_fallbacks {
6992 records.sort_by_key(|record| record.source_order);
6993 if records
6994 .windows(2)
6995 .any(|window| window[0].source_order == window[1].source_order)
6996 {
6997 self.publish_compressed_partial_report(&report, &direct_report);
6998 return Err(ExcelError::new(ExcelErrorKind::Value)
6999 .with_message("duplicate exact-replay source-order proof"));
7000 }
7001 let Some(source_order) = records.first().map(|record| record.source_order)
7002 else {
7003 self.publish_compressed_partial_report(&report, &direct_report);
7004 return Err(ExcelError::new(ExcelErrorKind::Value)
7005 .with_message("empty exact-replay fallback family"));
7006 };
7007 proposals.push(SourceProposal::KnownFallback {
7008 source_order,
7009 records,
7010 });
7011 }
7012 proposals.sort_by_key(SourceProposal::source_order);
7013 if proposals
7014 .windows(2)
7015 .any(|window| window[0].source_order() == window[1].source_order())
7016 {
7017 self.publish_compressed_partial_report(&report, &direct_report);
7018 return Err(ExcelError::new(ExcelErrorKind::Value)
7019 .with_message("ambiguous compressed source-order proof"));
7020 }
7021
7022 for proposal in proposals {
7023 match proposal {
7024 SourceProposal::KnownFallback { records, .. } => {
7025 let batch = match self.formula_batch_from_exact_replay(
7026 preparation.sheet_name.as_ref(),
7027 records,
7028 ) {
7029 Ok(batch) => batch,
7030 Err(error) => {
7031 self.publish_compressed_partial_report(&report, &direct_report);
7032 return Err(error);
7033 }
7034 };
7035 if batch.is_empty() {
7036 continue;
7037 }
7038 let snapshot = match self.fallback_planning_snapshot(&batch) {
7039 Ok(snapshot) => snapshot,
7040 Err(error) => {
7041 self.publish_compressed_partial_report(&report, &direct_report);
7042 return Err(error);
7043 }
7044 };
7045 let commit_guard =
7046 crate::function_registry::semantic_epoch_read_guard();
7047 let provider_revision_initial =
7048 self.resolver.planning_semantic_revision();
7049 if (commit_guard.epoch() != snapshot.epoch()
7050 && snapshot.semantic_changes_affect_requests_since_guarded(
7051 &commit_guard,
7052 snapshot.epoch(),
7053 ))
7054 || snapshot.provider_revision().is_some_and(|revision| {
7055 Some(revision) != provider_revision_initial
7056 })
7057 {
7058 drop(commit_guard);
7059 self.publish_compressed_partial_report(&report, &direct_report);
7060 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
7061 "ordered fallback planning snapshot became stale",
7062 ));
7063 }
7064 let (plan, formula_count) = match self
7065 .prepare_legacy_batch_fallback(batch, &snapshot)
7066 {
7067 Ok(plan) => plan,
7068 Err(error) => {
7069 drop(commit_guard);
7070 self.publish_compressed_partial_report(&report, &direct_report);
7071 return Err(error);
7072 }
7073 };
7074 let provider_revision_after =
7075 self.resolver.planning_semantic_revision();
7076 if provider_revision_after != provider_revision_initial {
7077 drop(commit_guard);
7078 self.publish_compressed_partial_report(&report, &direct_report);
7079 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
7080 "function provider changed while preparing ordered fallback",
7081 ));
7082 }
7083 let graph_vertices = plan.new_vertex_count();
7084 let Some(graph_edges) = plan.planned_edge_count() else {
7085 drop(commit_guard);
7086 self.publish_compressed_partial_report(&report, &direct_report);
7087 return Err(ExcelError::new(ExcelErrorKind::Value)
7088 .with_message("prepared ordered fallback size overflow"));
7089 };
7090 if let Err(error) = self.prepared_legacy_admission(&plan, formula_count)
7091 {
7092 drop(commit_guard);
7093 self.publish_compressed_partial_report(&report, &direct_report);
7094 return Err(error);
7095 }
7096 if let Err(error) =
7097 self.graph.validate_prepared_legacy_graph_plan(&plan)
7098 {
7099 drop(commit_guard);
7100 self.publish_compressed_partial_report(&report, &direct_report);
7101 return Err(ExcelError::new(ExcelErrorKind::Value)
7102 .with_message(error.to_string()));
7103 }
7104 #[cfg(test)]
7105 if let Some(hook) = self
7106 .before_legacy_fallback_final_provider_sample_hook
7107 .take()
7108 {
7109 hook();
7110 }
7111 let provider_revision_final =
7112 self.resolver.planning_semantic_revision();
7113 if provider_revision_final != provider_revision_initial {
7114 drop(commit_guard);
7115 self.publish_compressed_partial_report(&report, &direct_report);
7116 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
7117 "function provider changed after ordered fallback validation",
7118 ));
7119 }
7120 let graph_formulas =
7121 self.graph.apply_prevalidated_legacy_graph_plan(plan);
7122 direct_report.formula_cells_seen = direct_report
7123 .formula_cells_seen
7124 .saturating_add(formula_count);
7125 direct_report.graph_formula_cells_materialized = direct_report
7126 .graph_formula_cells_materialized
7127 .saturating_add(graph_formulas as u64);
7128 direct_report.graph_vertices_created = direct_report
7129 .graph_vertices_created
7130 .saturating_add(graph_vertices as u64);
7131 direct_report.graph_edges_created = direct_report
7132 .graph_edges_created
7133 .saturating_add(graph_edges as u64);
7134 }
7135 }
7136 #[cfg(test)]
7137 if let Some(hook) = self.after_eager_proposal_commit_hook.take() {
7138 hook();
7139 }
7140 }
7141 }
7142 break;
7143 }
7144 report.accumulate(&direct_report);
7145 self.record_formula_ingest_report(report.clone());
7146 Ok(report)
7147 }
7148 pub(crate) fn ingest_compressed_formula_source_batches(
7150 &mut self,
7151 batches: Vec<(
7152 FormulaIngestBatch,
7153 crate::engine::FormulaCompressedSourceBatch,
7154 )>,
7155 ) -> Result<FormulaIngestReport, ExcelError> {
7156 self.ingest_compressed_formula_source_batches_inner(batches, true)
7157 }
7158
7159 fn ingest_compressed_formula_source_batches_inner(
7160 &mut self,
7161 batches: Vec<(
7162 FormulaIngestBatch,
7163 crate::engine::FormulaCompressedSourceBatch,
7164 )>,
7165 publish_report: bool,
7166 ) -> Result<FormulaIngestReport, ExcelError> {
7167 let mut source_counts = [0_u64; 11];
7168 let mut source_report = crate::engine::FormulaCompressedSourceReport::default();
7169 let mut formula_batches = Vec::with_capacity(batches.len());
7170 let mut compressed_families = Vec::new();
7171 let mut partitioned_families = Vec::new();
7172 for (batch, compressed_batch) in batches {
7173 let (sheet_name, compressed, families, partitions) = compressed_batch.into_parts();
7174 if sheet_name.as_ref() != batch.sheet_name {
7175 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
7176 "compressed formula source sheet does not match its replay batch",
7177 ));
7178 }
7179 compressed_families.push((batch.sheet_name.clone(), families));
7180 partitioned_families.push((batch.sheet_name.clone(), partitions));
7181 source_counts[0] = source_counts[0].saturating_add(compressed.source_formula_events);
7182 source_counts[1] = source_counts[1].saturating_add(compressed.source_ordinary_events);
7183 source_counts[2] =
7184 source_counts[2].saturating_add(compressed.source_shared_anchor_events);
7185 source_counts[3] =
7186 source_counts[3].saturating_add(compressed.source_shared_descendant_events);
7187 source_counts[4] = source_counts[4].saturating_add(compressed.source_unknown_events);
7188 source_counts[5] =
7189 source_counts[5].saturating_add(compressed.source_formula_records_spooled);
7190 source_counts[6] =
7191 source_counts[6].saturating_add(compressed.source_spool_encoded_bytes);
7192 source_counts[7] = source_counts[7].max(compressed.source_spool_peak_memory_bytes);
7193 source_counts[8] =
7194 source_counts[8].saturating_add(compressed.source_spool_spilled_bytes);
7195 source_counts[9] = source_counts[9].saturating_add(compressed.source_spool_spill_files);
7196 source_counts[10] = source_counts[10].saturating_add(compressed.source_spool_replays);
7197 source_report.families_seen = source_report
7198 .families_seen
7199 .saturating_add(compressed.families_seen);
7200 source_report.family_cells_seen = source_report
7201 .family_cells_seen
7202 .saturating_add(compressed.family_cells_seen);
7203 source_report.source_clean_families = source_report
7204 .source_clean_families
7205 .saturating_add(compressed.source_clean_families);
7206 source_report.source_clean_cells = source_report
7207 .source_clean_cells
7208 .saturating_add(compressed.source_clean_cells);
7209 source_report.source_fragmentable_families = source_report
7210 .source_fragmentable_families
7211 .saturating_add(compressed.source_fragmentable_families);
7212 source_report.source_fragmentable_cells = source_report
7213 .source_fragmentable_cells
7214 .saturating_add(compressed.source_fragmentable_cells);
7215 source_report.source_fragment_count = source_report
7216 .source_fragment_count
7217 .saturating_add(compressed.source_fragment_count);
7218 source_report.source_isolated_fallback_cells = source_report
7219 .source_isolated_fallback_cells
7220 .saturating_add(compressed.source_isolated_fallback_cells);
7221 source_report.source_hole_exclusions = source_report
7222 .source_hole_exclusions
7223 .saturating_add(compressed.source_hole_exclusions);
7224 source_report.source_ordinary_exclusions = source_report
7225 .source_ordinary_exclusions
7226 .saturating_add(compressed.source_ordinary_exclusions);
7227 source_report.source_partition_failures = source_report
7228 .source_partition_failures
7229 .saturating_add(compressed.source_partition_failures);
7230 source_report.replay_families = source_report
7231 .replay_families
7232 .saturating_add(compressed.replay_families);
7233 source_report.replay_cells = source_report
7234 .replay_cells
7235 .saturating_add(compressed.replay_cells);
7236 source_report.forward_descendants = source_report
7237 .forward_descendants
7238 .saturating_add(compressed.forward_descendants);
7239 source_report.evidence_limit_fallbacks = source_report
7240 .evidence_limit_fallbacks
7241 .saturating_add(compressed.evidence_limit_fallbacks);
7242 source_report.evidence_peak_bytes = source_report
7243 .evidence_peak_bytes
7244 .max(compressed.evidence_peak_bytes);
7245 for (reason, count) in compressed.fallback_reasons {
7246 *source_report.fallback_reasons.entry(reason).or_default() += count;
7247 }
7248 formula_batches.push(batch);
7249 }
7250 self.ingest_formula_batches_inner(
7251 formula_batches,
7252 source_counts,
7253 Some(source_report),
7254 compressed_families,
7255 partitioned_families,
7256 publish_report,
7257 )
7258 }
7259
7260 pub fn ingest_formula_batches(
7261 &mut self,
7262 batches: Vec<FormulaIngestBatch>,
7263 ) -> Result<FormulaIngestReport, ExcelError> {
7264 let has_formulas = batches.iter().any(|batch| !batch.formulas.is_empty());
7265 let report = self.ingest_formula_batches_inner(
7266 batches,
7267 [0; 11],
7268 None,
7269 Vec::new(),
7270 Vec::new(),
7271 true,
7272 )?;
7273 if has_formulas {
7274 self.mark_topology_edited();
7275 }
7276 Ok(report)
7277 }
7278
7279 fn ingest_formula_batches_unpublished(
7280 &mut self,
7281 batches: Vec<FormulaIngestBatch>,
7282 ) -> Result<FormulaIngestReport, ExcelError> {
7283 self.ingest_formula_batches_inner(batches, [0; 11], None, Vec::new(), Vec::new(), false)
7284 }
7285
7286 fn ingest_formula_batches_inner(
7287 &mut self,
7288 batches: Vec<FormulaIngestBatch>,
7289 source_counts: [u64; 11],
7290 source_report: Option<crate::engine::FormulaCompressedSourceReport>,
7291 compressed_families: Vec<(String, Vec<crate::engine::SourceFormulaFamily>)>,
7292 partitioned_families: Vec<(String, Vec<crate::engine::PartitionedSourceFormulaFamily>)>,
7293 publish_report: bool,
7294 ) -> Result<FormulaIngestReport, ExcelError> {
7295 self.observe_function_semantic_epoch()?;
7296 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
7297 #[cfg(feature = "tracing")]
7298 let arena_nodes_before = self.graph.data_store().memory_usage().total_ast_nodes;
7299 #[cfg(feature = "tracing")]
7300 let route = if !partitioned_families.is_empty() {
7301 "partitioned"
7302 } else if !compressed_families.is_empty() {
7303 "compressed_source"
7304 } else if source_report.is_some() {
7305 "replay"
7306 } else {
7307 "ordinary"
7308 };
7309 let _ingest_span = crate::engine::trace::fz_span!(
7310 tracing::Level::INFO,
7311 "ingest",
7312 "ingest.batch",
7313 mode = ?FormulaPlaneMode::Off,
7314 route,
7315 formula_cells = formula_cells_seen
7316 );
7317 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
7318 let materialize_batches = batches;
7319 report.formula_cells_seen = formula_cells_seen;
7320 report.source_formula_events = source_counts[0];
7321 report.source_ordinary_events = source_counts[1];
7322 report.source_shared_anchor_events = source_counts[2];
7323 report.source_shared_descendant_events = source_counts[3];
7324 report.source_unknown_events = source_counts[4];
7325 report.source_formula_records_spooled = source_counts[5];
7326 report.source_spool_encoded_bytes = source_counts[6];
7327 report.source_spool_peak_memory_bytes = source_counts[7];
7328 report.source_spool_spilled_bytes = source_counts[8];
7329 report.source_spool_spill_files = source_counts[9];
7330 report.source_spool_replays = source_counts[10];
7331 if let Some(source) = source_report {
7332 report.source_families_seen = source.families_seen;
7333 report.source_family_cells_seen = source.family_cells_seen;
7334 report.source_family_shadow_eligible = source.source_clean_families;
7335 report.source_family_shadow_eligible_cells = source.source_clean_cells;
7336 report.source_partitioned_families_seen = source.source_fragmentable_families;
7337 report.source_partition_holes = source.source_hole_exclusions;
7338 report.source_partition_ordinary_exceptions = source.source_ordinary_exclusions;
7339 report.source_partition_failures = source.source_partition_failures;
7340 report.source_partition_surviving_cells = source.source_fragmentable_cells;
7341 report.source_family_fallback = report
7342 .source_family_fallback
7343 .saturating_add(source.replay_families);
7344 report.source_family_fallback_cells = report
7345 .source_family_fallback_cells
7346 .saturating_add(source.replay_cells);
7347 report.source_forward_descendants = source.forward_descendants;
7348 report.source_evidence_limit_fallbacks = source.evidence_limit_fallbacks;
7349 report.source_evidence_peak_bytes = source.evidence_peak_bytes;
7350 for (reason, count) in source.fallback_reasons {
7351 let total = report.fallback_reasons.entry(reason).or_default();
7352 *total = total.saturating_add(count);
7353 }
7354 }
7355
7356 if self.graph.first_load_assume_new()
7360 && !self.graph_admission_enabled()
7361 && !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
7362 {
7363 let mut builder =
7364 crate::engine::ingest_builder::BulkIngestBuilder::new(&mut self.graph);
7365 for batch in materialize_batches {
7366 if batch.is_empty() {
7367 continue;
7368 }
7369 let sheet_id = builder
7370 .add_sheet_checked(&batch.sheet_name)
7371 .ok_or_else(|| {
7372 ExcelError::new(ExcelErrorKind::Ref)
7373 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7374 })?;
7375 builder.add_formula_refs(
7376 sheet_id,
7377 batch.formulas.into_iter().map(|record| {
7378 (
7379 record.row,
7380 record.col,
7381 crate::engine::graph::FormulaRef::of_ingested(
7382 record.ast_id,
7383 record.member_anchor,
7384 ),
7385 )
7386 }),
7387 );
7388 }
7389 let summary = builder.finish_with_provider(&self.resolver)?;
7390 report.graph_formula_cells_materialized = summary.formulas as u64;
7391 report.graph_vertices_created = summary.vertices as u64;
7392 report.graph_edges_created = summary.edges as u64;
7393 } else if !materialize_batches.iter().all(FormulaIngestBatch::is_empty) {
7394 let mut prepared_by_sheet: BTreeMap<String, Vec<_>> = BTreeMap::new();
7395 for batch in materialize_batches {
7396 if batch.is_empty() {
7397 continue;
7398 }
7399 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
7400 ExcelError::new(ExcelErrorKind::Ref)
7401 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
7402 })?;
7403 let mut pipeline = self.ingest_pipeline();
7404 let prepared = prepared_by_sheet.entry(batch.sheet_name).or_default();
7407 prepared.reserve(batch.formulas.len());
7408 for record in batch.formulas {
7409 let placement = CellRef::new(
7410 sheet_id,
7411 Coord::from_excel(record.row, record.col, true, true),
7412 );
7413 let input = match record.member_anchor {
7414 Some(anchor) => FormulaAstInput::Member {
7415 template: record.ast_id,
7416 anchor,
7417 },
7418 None => FormulaAstInput::RawArena(record.ast_id),
7419 };
7420 let formula = pipeline.ingest_formula(input, placement, None)?;
7421 prepared.push((
7422 record.row,
7423 record.col,
7424 crate::engine::graph::FormulaRef::of_ingested(
7425 formula.ast_id,
7426 formula.member_anchor,
7427 ),
7428 formula.dep_plan,
7429 ));
7430 }
7431 }
7432 let admission_preflighted = self.graph_admission_enabled();
7433 if admission_preflighted {
7434 let mut preview = Vec::new();
7435 for (sheet_name, formulas) in &prepared_by_sheet {
7436 let sheet_id = self.graph.sheet_id(sheet_name).ok_or_else(|| {
7437 ExcelError::new(ExcelErrorKind::Ref)
7438 .with_message(format!("unknown ingest sheet: {sheet_name}"))
7439 })?;
7440 preview.extend(
7441 formulas
7442 .iter()
7443 .map(|(row, col, _, plan)| (sheet_id, *row, *col, plan.clone())),
7444 );
7445 }
7446 let admission = self.graph.preview_formula_mutations(&preview)?;
7447 self.preflight_graph_admission(admission)?;
7448 }
7449
7450 let mut builder = self.begin_bulk_ingest();
7451 if admission_preflighted {
7452 builder.mark_admission_preflighted();
7453 }
7454 for (sheet_name, formulas) in prepared_by_sheet {
7455 if formulas.is_empty() {
7456 continue;
7457 }
7458 let sheet_id = builder.add_sheet(&sheet_name);
7459 builder.add_formula_plans(sheet_id, formulas);
7460 }
7461 let summary = builder.finish()?;
7462 report.graph_formula_cells_materialized = summary.formulas as u64;
7463 report.graph_vertices_created = summary.vertices as u64;
7464 report.graph_edges_created = summary.edges as u64;
7465 }
7466
7467 crate::engine::trace::fz_event!(
7468 tracing::Level::INFO,
7469 "ingest",
7470 "ingest.summary",
7471 candidate_cells = report.shadow_candidate_cells,
7472 accepted_span_cells = report.shadow_accepted_span_cells,
7473 fallback_cells = report.shadow_fallback_cells,
7474 spans_created = report.shadow_spans_created,
7475 templates_interned = report.shadow_templates_interned,
7476 graph_vertices_created = report.graph_vertices_created,
7477 graph_edges_created = report.graph_edges_created,
7478 arena_nodes_delta = self
7479 .graph
7480 .data_store()
7481 .memory_usage()
7482 .total_ast_nodes
7483 .saturating_sub(arena_nodes_before)
7484 );
7485 if publish_report {
7486 self.record_formula_ingest_report(report.clone());
7487 }
7488 Ok(report)
7489 }
7490
7491 fn dedup_formula_parse_diagnostics_since(&mut self, start: usize) {
7492 let mut unique = Vec::new();
7493 for diagnostic in self.formula_parse_diagnostics.drain(start..) {
7494 let duplicate = unique.iter().any(|prior: &FormulaParseDiagnostic| {
7495 prior.sheet == diagnostic.sheet
7496 && prior.row == diagnostic.row
7497 && prior.col == diagnostic.col
7498 && prior.formula == diagnostic.formula
7499 && prior.policy == diagnostic.policy
7500 });
7501 if !duplicate {
7502 unique.push(diagnostic);
7503 }
7504 }
7505 self.formula_parse_diagnostics.extend(unique);
7506 }
7507
7508 pub fn handle_formula_parse_error(
7509 &mut self,
7510 sheet: &str,
7511 row: u32,
7512 col: u32,
7513 formula: &str,
7514 message: String,
7515 ) -> Result<Option<ASTNode>, ExcelError> {
7516 let policy = self.config.formula_parse_policy;
7517
7518 if policy == FormulaParsePolicy::Strict {
7519 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
7520 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
7521 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
7522 )));
7523 }
7524
7525 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
7526 sheet: sheet.to_string(),
7527 row,
7528 col,
7529 formula: formula.to_string(),
7530 message: message.clone(),
7531 policy,
7532 });
7533
7534 match policy {
7535 FormulaParsePolicy::Strict => unreachable!(),
7536 FormulaParsePolicy::KeepCachedValue => Ok(None),
7537 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
7538 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
7539 None,
7540 ))),
7541 FormulaParsePolicy::CoerceToError => {
7542 let err = ExcelError::new(ExcelErrorKind::Error)
7543 .with_message(format!("Malformed formula: {message}"));
7544 Ok(Some(ASTNode::new(
7545 ASTNodeType::Literal(LiteralValue::Error(err)),
7546 None,
7547 )))
7548 }
7549 }
7550 }
7551
7552 #[cfg(test)]
7553 fn target_preparation_fault(
7554 &mut self,
7555 seam: crate::engine::target_preparation::TargetPreparationFault,
7556 ) -> Result<(), ExcelError> {
7557 if self.target_preparation_fault_for_test == Some(seam) {
7558 self.target_preparation_fault_for_test = None;
7559 Err(ExcelError::new(ExcelErrorKind::Value)
7560 .with_message(format!("injected target preparation fault: {seam:?}")))
7561 } else {
7562 Ok(())
7563 }
7564 }
7565
7566 fn preparation_stale(
7567 reason: formualizer_common::PreparationStaleReason,
7568 message: impl Into<String>,
7569 ) -> ExcelError {
7570 ExcelError::new(ExcelErrorKind::Value)
7571 .with_message(message)
7572 .with_extra(formualizer_common::ExcelErrorExtra::PreparationStale { reason })
7573 }
7574
7575 fn preparation_revision_stale_reason(
7576 assumptions: &crate::engine::PreparationRevision,
7577 current: &crate::engine::PreparationRevision,
7578 planning_requests: &BTreeSet<(String, String, usize)>,
7579 staged_leases_match: bool,
7580 ) -> Option<formualizer_common::PreparationStaleReason> {
7581 if assumptions.graph != current.graph {
7582 Some(formualizer_common::PreparationStaleReason::Graph)
7583 } else if assumptions.staged != current.staged || !staged_leases_match {
7584 Some(formualizer_common::PreparationStaleReason::Staged)
7585 } else if assumptions.symbols != current.symbols {
7586 Some(formualizer_common::PreparationStaleReason::Symbols)
7587 } else if assumptions.provider != current.provider {
7588 Some(formualizer_common::PreparationStaleReason::Provider)
7589 } else if assumptions.semantic != current.semantic
7590 && crate::function_registry::semantic_changes_affect_requests_since(
7591 assumptions.semantic,
7592 planning_requests.iter().cloned(),
7593 )
7594 {
7595 Some(formualizer_common::PreparationStaleReason::Semantic)
7596 } else {
7597 None
7598 }
7599 }
7600
7601 fn preparation_revisions(&self) -> crate::engine::PreparationRevision {
7602 crate::engine::PreparationRevision {
7603 graph: self.graph.topology_revision(),
7604 authority: 0,
7605 authority_indexes: 0,
7606 authority_indexed_plane: 0,
7607 staged: self.staged_formula_index.revision(),
7608 symbols: self.graph.symbol_revision(),
7609 semantic: crate::function_registry::semantic_epoch(),
7610 provider: self.resolver.planning_semantic_revision(),
7611 }
7612 }
7613
7614 fn planning_revision_snapshot(&self) -> PlanningRevisionSnapshot {
7615 let registry_guard = crate::function_registry::semantic_epoch_read_guard();
7616 let provider = self.resolver.planning_semantic_revision();
7617 let semantic = registry_guard.epoch();
7618 PlanningRevisionSnapshot {
7619 engine_topology_epoch: self.topology_epoch,
7620 graph_topology_revision: self.graph.topology_revision(),
7621 staged: self.staged_formula_index.revision(),
7622 symbols: self.graph.symbol_revision(),
7623 semantic,
7624 provider,
7625 deterministic_mode: self.config.deterministic_mode.clone(),
7626 budgets: self.evaluation_resource_budgets.clone(),
7627 }
7628 }
7629
7630 fn recalc_plan_key(&self) -> RecalcPlanKey {
7631 RecalcPlanKey {
7632 engine_token: Arc::clone(&self.recalc_plan_token),
7633 revisions: self.planning_revision_snapshot(),
7634 }
7635 }
7636
7637 fn plan_stale(reason: formualizer_common::PlanStaleReason) -> ExcelError {
7638 ExcelError::new(ExcelErrorKind::Value)
7639 .with_message(format!("recalculation plan is stale: {}", reason.as_str()))
7640 .with_extra(formualizer_common::ExcelErrorExtra::PlanStale { reason })
7641 }
7642
7643 fn validate_recalc_plan_key(&self, key: &RecalcPlanKey) -> Result<(), ExcelError> {
7644 use formualizer_common::PlanStaleReason;
7645
7646 if !Arc::ptr_eq(&key.engine_token, &self.recalc_plan_token) {
7647 return Err(Self::plan_stale(PlanStaleReason::Engine));
7648 }
7649
7650 let current = self.planning_revision_snapshot();
7651 let expected = &key.revisions;
7652 let stale = if expected.provider != current.provider {
7653 Some(PlanStaleReason::Provider)
7654 } else if expected.semantic != current.semantic {
7655 Some(PlanStaleReason::Semantic)
7656 } else if expected.budgets != current.budgets
7657 || expected.deterministic_mode != current.deterministic_mode
7658 {
7659 Some(PlanStaleReason::Budget)
7660 } else if expected.staged != current.staged {
7661 Some(PlanStaleReason::Staged)
7662 } else if expected.symbols != current.symbols {
7663 Some(PlanStaleReason::Symbols)
7664 } else if expected.graph_topology_revision != current.graph_topology_revision
7665 || expected.engine_topology_epoch != current.engine_topology_epoch
7666 {
7667 Some(PlanStaleReason::Graph)
7668 } else {
7669 None
7670 };
7671 stale.map_or(Ok(()), |reason| Err(Self::plan_stale(reason)))
7672 }
7673
7674 fn target_preparation_checkpoint(
7675 &mut self,
7676 deadline: Option<std::time::Instant>,
7677 work: u64,
7678 ) -> Result<(), ExcelError> {
7679 if self
7680 .active_cancel_flag
7681 .as_ref()
7682 .is_some_and(|cancel| cancel.is_cancelled())
7683 {
7684 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
7685 .with_message("target graph preparation cancelled"));
7686 }
7687 if deadline.is_some_and(|deadline| std::time::Instant::now() >= deadline) {
7688 return Err(crate::engine::ResourceLedgerError::Exhausted(
7689 formualizer_common::ResourceExhaustionDetail {
7690 reason: formualizer_common::ResourceExhaustionReason::Deadline,
7691 limit: 0,
7692 observed: 1,
7693 request_id: self
7694 .active_evaluation_resource_request
7695 .as_ref()
7696 .map(|stats| stats.request_id),
7697 },
7698 )
7699 .into_excel_error());
7700 }
7701 self.charge_bounded_work(work)
7702 }
7703
7704 fn opaque_reason_in_ast(
7705 &self,
7706 ast: &ASTNode,
7707 provider: &dyn crate::traits::FunctionProvider,
7708 ) -> Option<crate::engine::OpaqueReason> {
7709 match &ast.node_type {
7710 ASTNodeType::Function { name, args } => {
7711 let canonical = name.rsplit('.').next().unwrap_or(name).to_ascii_uppercase();
7712 if canonical == "INDIRECT" {
7713 return Some(crate::engine::OpaqueReason::RuntimeTextReference);
7714 }
7715 let Some(function) = provider.get_function_for_planning("", name) else {
7716 return Some(crate::engine::OpaqueReason::UnknownFunction);
7717 };
7718 let caps = function.caps();
7719 if caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7720 || caps.contains(FnCaps::RETURNS_REFERENCE)
7721 {
7722 return Some(crate::engine::OpaqueReason::DynamicReference);
7723 }
7724 args.iter()
7725 .find_map(|arg| self.opaque_reason_in_ast(arg, provider))
7726 }
7727 ASTNodeType::Call { .. } => Some(crate::engine::OpaqueReason::UnknownCustomFunction),
7728 ASTNodeType::UnaryOp { expr, .. } => self.opaque_reason_in_ast(expr, provider),
7729 ASTNodeType::BinaryOp { left, right, .. } => self
7730 .opaque_reason_in_ast(left, provider)
7731 .or_else(|| self.opaque_reason_in_ast(right, provider)),
7732 ASTNodeType::Array(rows) => rows
7733 .iter()
7734 .flat_map(|row| row.iter())
7735 .find_map(|item| self.opaque_reason_in_ast(item, provider)),
7736 ASTNodeType::Reference {
7737 reference:
7738 ReferenceType::Cell {
7739 sheet: Some(sheet), ..
7740 }
7741 | ReferenceType::Range {
7742 sheet: Some(sheet), ..
7743 },
7744 ..
7745 } if self.graph.sheet_id(sheet).is_none() => {
7746 Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding)
7747 }
7748 ASTNodeType::Reference {
7749 reference:
7750 ReferenceType::External(_)
7751 | ReferenceType::Cell3D { .. }
7752 | ReferenceType::Range3D { .. },
7753 ..
7754 } => Some(crate::engine::OpaqueReason::UnresolvedCrossSheetBinding),
7755 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => None,
7756 }
7757 }
7758
7759 fn target_planning_snapshot(
7760 &mut self,
7761 ast: &ASTNode,
7762 planning_requests: &mut BTreeSet<(String, String, usize)>,
7763 ) -> Result<crate::function_registry::RegistryPlanningSnapshot, ExcelError> {
7764 #[cfg(test)]
7765 if std::mem::take(&mut self.inject_target_semantic_stale_once_for_test) {
7766 return Err(Self::preparation_stale(
7767 formualizer_common::PreparationStaleReason::Semantic,
7768 "injected target semantic preparation movement",
7769 ));
7770 }
7771 #[cfg(test)]
7772 if let Some(hook) = self.before_target_planning_snapshot_hook.take() {
7773 hook();
7774 }
7775 let mut requests = Vec::new();
7776 Self::collect_planning_function_requests(ast, &mut requests);
7777 requests.sort();
7778 requests.dedup();
7779 planning_requests.extend(requests.iter().cloned());
7780 crate::function_registry::RegistryPlanningSnapshot::capture_for_requests(
7781 &self.resolver,
7782 requests,
7783 )
7784 .map_err(|error| {
7785 ExcelError::new(ExcelErrorKind::Value)
7786 .with_message(format!("target planning snapshot unavailable: {error:?}"))
7787 })
7788 }
7789
7790 fn target_planning_snapshot_stale_reason(
7791 snapshot: &crate::function_registry::RegistryPlanningSnapshot,
7792 assumptions: &crate::engine::PreparationRevision,
7793 ) -> Option<formualizer_common::PreparationStaleReason> {
7794 if snapshot
7795 .provider_revision()
7796 .is_some_and(|revision| Some(revision) != assumptions.provider)
7797 {
7798 Some(formualizer_common::PreparationStaleReason::Provider)
7799 } else if snapshot.epoch() != assumptions.semantic
7800 && snapshot.semantic_changes_affect_requests_since(assumptions.semantic)
7801 {
7802 Some(formualizer_common::PreparationStaleReason::Semantic)
7803 } else {
7804 None
7805 }
7806 }
7807
7808 fn widen_target_preparation(
7809 policy: crate::engine::OpaquePreparePolicy,
7810 scope: &mut crate::engine::PrepareScope,
7811 reasons: &mut Vec<crate::engine::OpaqueReason>,
7812 reason: crate::engine::OpaqueReason,
7813 ) -> Result<bool, ExcelError> {
7814 if policy == crate::engine::OpaquePreparePolicy::Error {
7815 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7816 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7817 }
7818 if !reasons.contains(&reason) {
7819 reasons.push(reason);
7820 }
7821 if !matches!(scope, crate::engine::PrepareScope::Workbook) {
7822 *scope = crate::engine::PrepareScope::Workbook;
7823 Ok(true)
7824 } else {
7825 Ok(false)
7826 }
7827 }
7828
7829 fn widen_target_preparation_to_sheet(
7830 policy: crate::engine::OpaquePreparePolicy,
7831 scope: &mut crate::engine::PrepareScope,
7832 reasons: &mut Vec<crate::engine::OpaqueReason>,
7833 reason: crate::engine::OpaqueReason,
7834 sheet: &str,
7835 ) -> Result<bool, ExcelError> {
7836 if policy == crate::engine::OpaquePreparePolicy::Error {
7837 return Err(ExcelError::new(ExcelErrorKind::NImpl)
7838 .with_message(format!("opaque target preparation semantics: {reason:?}")));
7839 }
7840 if !reasons.contains(&reason) {
7841 reasons.push(reason);
7842 }
7843 match scope {
7844 crate::engine::PrepareScope::Exact => {
7845 *scope = crate::engine::PrepareScope::Sheets(vec![sheet.to_string()]);
7846 Ok(true)
7847 }
7848 crate::engine::PrepareScope::Sheets(sheets) => {
7849 if sheets.iter().any(|candidate| candidate == sheet) {
7850 Ok(false)
7851 } else {
7852 sheets.push(sheet.to_string());
7853 sheets.sort();
7854 Ok(true)
7855 }
7856 }
7857 crate::engine::PrepareScope::Workbook => Ok(false),
7858 }
7859 }
7860
7861 fn ast_has_proven_sheet_local_dynamic(
7862 ast: &ASTNode,
7863 provider: &dyn crate::traits::FunctionProvider,
7864 ) -> bool {
7865 fn classify(ast: &ASTNode, provider: &dyn crate::traits::FunctionProvider) -> (bool, bool) {
7866 match &ast.node_type {
7867 ASTNodeType::Function { name, args } => {
7868 let Some(function) = provider.get_function_for_planning("", name) else {
7869 return (false, false);
7870 };
7871 let caps = function.caps();
7872 let dynamic = caps.contains(FnCaps::DYNAMIC_DEPENDENCY)
7873 || caps.contains(FnCaps::RETURNS_REFERENCE);
7874 let canonical = name.rsplit('.').next().unwrap_or(name);
7875 if dynamic
7876 && !canonical.eq_ignore_ascii_case("OFFSET")
7877 && !canonical.eq_ignore_ascii_case("INDEX")
7878 {
7879 return (false, true);
7880 }
7881 let mut has_dynamic = dynamic;
7882 for arg in args {
7883 let (safe, child_dynamic) = classify(arg, provider);
7884 if !safe {
7885 return (false, has_dynamic || child_dynamic);
7886 }
7887 has_dynamic |= child_dynamic;
7888 }
7889 (true, has_dynamic)
7890 }
7891 ASTNodeType::UnaryOp { expr, .. } => classify(expr, provider),
7892 ASTNodeType::BinaryOp { left, right, .. } => {
7893 let (left_safe, left_dynamic) = classify(left, provider);
7894 let (right_safe, right_dynamic) = classify(right, provider);
7895 (left_safe && right_safe, left_dynamic || right_dynamic)
7896 }
7897 ASTNodeType::Array(rows) => {
7898 let mut has_dynamic = false;
7899 for item in rows.iter().flatten() {
7900 let (safe, child_dynamic) = classify(item, provider);
7901 if !safe {
7902 return (false, has_dynamic || child_dynamic);
7903 }
7904 has_dynamic |= child_dynamic;
7905 }
7906 (true, has_dynamic)
7907 }
7908 ASTNodeType::Reference {
7909 reference:
7910 ReferenceType::Cell { sheet: None, .. }
7911 | ReferenceType::Range { sheet: None, .. },
7912 ..
7913 }
7914 | ASTNodeType::Literal(_)
7915 | ASTNodeType::Omitted => (true, false),
7916 ASTNodeType::Call { .. } | ASTNodeType::Reference { .. } => (false, false),
7917 }
7918 }
7919
7920 let (safe, dynamic) = classify(ast, provider);
7921 safe && dynamic
7922 }
7923
7924 fn table_selection_region(
7925 &self,
7926 entry: &crate::engine::graph::TableEntry,
7927 selection: &crate::engine::TableSelection,
7928 ) -> Result<PreparationRegion, ExcelError> {
7929 let mut start_row = entry.range.start.coord.row() + 1;
7930 let mut end_row = entry.range.end.coord.row() + 1;
7931 let mut start_col = entry.range.start.coord.col() + 1;
7932 let mut end_col = entry.range.end.coord.col() + 1;
7933 match selection {
7934 crate::engine::TableSelection::Whole => {}
7935 crate::engine::TableSelection::Headers => {
7936 if !entry.header_row {
7937 return Err(ExcelError::new(ExcelErrorKind::Value)
7938 .with_message(format!("table {} has no header row", entry.name)));
7939 }
7940 end_row = start_row;
7941 }
7942 crate::engine::TableSelection::Data => {
7943 if entry.header_row {
7944 start_row = start_row.saturating_add(1);
7945 }
7946 if entry.totals_row {
7947 end_row = end_row.saturating_sub(1);
7948 }
7949 }
7950 crate::engine::TableSelection::Totals => {
7951 if !entry.totals_row {
7952 return Err(ExcelError::new(ExcelErrorKind::Value)
7953 .with_message(format!("table {} has no totals row", entry.name)));
7954 }
7955 start_row = end_row;
7956 }
7957 crate::engine::TableSelection::Column(column) => {
7958 let index = entry.col_index(column).ok_or_else(|| {
7959 ExcelError::new(ExcelErrorKind::Name)
7960 .with_message(format!("unknown table column: {column}"))
7961 })?;
7962 start_col = start_col.saturating_add(index as u32);
7963 end_col = start_col;
7964 }
7965 crate::engine::TableSelection::Columns { start, end } => {
7966 let first = entry.col_index(start).ok_or_else(|| {
7967 ExcelError::new(ExcelErrorKind::Name)
7968 .with_message(format!("unknown table column: {start}"))
7969 })?;
7970 let last = entry.col_index(end).ok_or_else(|| {
7971 ExcelError::new(ExcelErrorKind::Name)
7972 .with_message(format!("unknown table column: {end}"))
7973 })?;
7974 if first > last {
7975 return Err(ExcelError::new(ExcelErrorKind::Value)
7976 .with_message("table column selection is reversed"));
7977 }
7978 start_col = start_col.saturating_add(first as u32);
7979 end_col = entry
7980 .range
7981 .start
7982 .coord
7983 .col()
7984 .saturating_add(last as u32)
7985 .saturating_add(1);
7986 }
7987 }
7988 if start_row > end_row && matches!(selection, crate::engine::TableSelection::Data) {
7989 start_row = entry.range.start.coord.row() + 1;
7990 end_row = start_row;
7991 }
7992 if start_row > end_row || start_col > end_col {
7993 return Err(
7994 ExcelError::new(ExcelErrorKind::Value).with_message("table selection is empty")
7995 );
7996 }
7997 Ok(PreparationRegion {
7998 sheet: self.graph.sheet_name(entry.sheet_id()).to_string(),
7999 sheet_id: entry.sheet_id(),
8000 start_row,
8001 start_col,
8002 end_row,
8003 end_col,
8004 })
8005 }
8006
8007 #[cfg(test)]
8008 pub(crate) fn reset_target_root_dedup_probes_for_test() {
8009 TARGET_ROOT_DEDUP_PROBES.with(|probes| probes.set(0));
8010 }
8011
8012 #[cfg(test)]
8013 pub(crate) fn target_root_dedup_probes_for_test() -> usize {
8014 TARGET_ROOT_DEDUP_PROBES.with(std::cell::Cell::get)
8015 }
8016
8017 pub(crate) fn resolve_target_producers(
8018 &mut self,
8019 targets: &[crate::engine::EvaluationTarget],
8020 ) -> Result<Vec<crate::engine::target_preparation::TargetProducer>, ExcelError> {
8021 use crate::engine::target_preparation::TargetProducer;
8022
8023 let request_id = self
8024 .active_evaluation_resource_request
8025 .as_ref()
8026 .map(|request| request.request_id);
8027 let mut roots = OrderedTargetProducers::with_capacity(targets.len())
8028 .map_err(|_| target_root_allocation_error(targets.len(), request_id))?;
8029 let resolve_region = |engine: &mut Self,
8030 region: Region,
8031 value_only: Option<CellRef>,
8032 roots: &mut OrderedTargetProducers|
8033 -> Result<(), ExcelError> {
8034 let before = roots.len();
8035 for anchor in engine.graph.spill_anchors_in_region(
8036 region.sheet_id(),
8037 region.axis_ranges().0.query_bounds().0,
8038 region.axis_ranges().1.query_bounds().0,
8039 region.axis_ranges().0.query_bounds().1,
8040 region.axis_ranges().1.query_bounds().1,
8041 ) {
8042 roots
8043 .push(TargetProducer::Legacy(anchor))
8044 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
8045 }
8046 for vertex in engine.graph.vertices_in_region(
8047 region.sheet_id(),
8048 region.axis_ranges().0.query_bounds().0,
8049 region.axis_ranges().0.query_bounds().1,
8050 region.axis_ranges().1.query_bounds().0,
8051 region.axis_ranges().1.query_bounds().1,
8052 ) {
8053 let vertex = engine
8054 .graph
8055 .get_cell_ref(vertex)
8056 .and_then(|cell| engine.graph.spill_registry_anchor_for_cell(cell))
8057 .unwrap_or(vertex);
8058 if matches!(
8063 engine.graph.get_vertex_kind(vertex),
8064 VertexKind::FormulaScalar | VertexKind::FormulaArray
8065 ) {
8066 roots
8067 .push(TargetProducer::Legacy(vertex))
8068 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
8069 }
8070 }
8071 if roots.len() == before
8072 && let Some(cell) = value_only
8073 {
8074 roots
8075 .push(TargetProducer::ValueOnly(cell))
8076 .map_err(|_| target_root_allocation_error(roots.len() + 1, request_id))?;
8077 }
8078 Ok(())
8079 };
8080
8081 for target in targets {
8082 match target {
8083 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
8084 if *row == 0 || *col == 0 {
8085 return Err(ExcelError::new(ExcelErrorKind::Ref)
8086 .with_message("target cell coordinates are one-based"));
8087 }
8088 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
8089 ExcelError::new(ExcelErrorKind::Ref)
8090 .with_message(format!("target sheet not found: {sheet}"))
8091 })?;
8092 let cell = CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true));
8093 resolve_region(
8094 self,
8095 Region::point(sheet_id, *row - 1, *col - 1),
8096 Some(cell),
8097 &mut roots,
8098 )?;
8099 }
8100 crate::engine::EvaluationTarget::Range(range) => {
8101 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
8102 ExcelError::new(ExcelErrorKind::Ref)
8103 .with_message(format!("target sheet not found: {}", range.sheet))
8104 })?;
8105 resolve_region(
8106 self,
8107 Region::rect(
8108 sheet_id,
8109 range.start_row - 1,
8110 range.end_row - 1,
8111 range.start_col - 1,
8112 range.end_col - 1,
8113 ),
8114 None,
8115 &mut roots,
8116 )?;
8117 }
8118 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
8119 let scope = self.name_query_scope(scope_sheet.as_deref())?;
8120 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, scope) {
8121 roots
8122 .push(TargetProducer::Symbol(entry.vertex))
8123 .map_err(|_| {
8124 target_root_allocation_error(roots.len() + 1, request_id)
8125 })?;
8126 }
8127 }
8128 crate::engine::EvaluationTarget::Table { name, .. } => {
8129 if let Some(entry) = self.graph.resolve_table_entry(name) {
8130 roots
8131 .push(TargetProducer::Symbol(entry.vertex))
8132 .map_err(|_| {
8133 target_root_allocation_error(roots.len() + 1, request_id)
8134 })?;
8135 }
8136 }
8137 }
8138 }
8139 Ok(roots.into_vec())
8140 }
8141
8142 pub fn prepare_graph_for_targets(
8146 &mut self,
8147 targets: &[crate::engine::EvaluationTarget],
8148 options: crate::engine::TargetEvalOptions<'_>,
8149 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
8150 let previous_budgets = self.evaluation_resource_budgets.clone();
8151 let diagnostics_len = self.formula_parse_diagnostics.len();
8152 let previous_report = self.last_formula_ingest_report.clone();
8153 if let Some(budgets) = options.budgets {
8154 self.evaluation_resource_budgets = budgets.clone();
8155 }
8156 let previous_graph_budget_override = self
8157 .graph
8158 .set_admission_budget_override(Some(self.evaluation_resource_budgets.clone()));
8159 let previous_cancel = self.active_cancel_flag.take();
8163 self.active_cancel_flag = options.cancel.clone();
8164 let result = self.observe_evaluation_resource_request(
8165 EvaluationRequestKind::TargetPreparation,
8166 |engine| engine.prepare_graph_for_targets_unobserved(targets, &options),
8167 );
8168 self.active_cancel_flag = previous_cancel;
8169 self.graph
8170 .set_admission_budget_override(previous_graph_budget_override);
8171 self.evaluation_resource_budgets = previous_budgets;
8172 if result.is_err() {
8173 self.formula_parse_diagnostics.truncate(diagnostics_len);
8174 self.last_formula_ingest_report = previous_report;
8175 }
8176 result
8177 }
8178
8179 fn prepare_graph_for_targets_unobserved(
8180 &mut self,
8181 targets: &[crate::engine::EvaluationTarget],
8182 options: &crate::engine::TargetEvalOptions<'_>,
8183 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
8184 let scratch_checkpoint = self
8185 .active_resource_ledger
8186 .as_ref()
8187 .map_or(0, crate::engine::ResourceLedger::scratch_checkpoint);
8188 let result = self.prepare_graph_for_targets_transaction(targets, options);
8189 let release = self
8190 .active_resource_ledger
8191 .as_mut()
8192 .map_or(Ok(()), |ledger| {
8193 ledger.release_scratch_to(scratch_checkpoint)
8194 });
8195 match (result, release) {
8196 (result, Ok(())) => result,
8197 (Ok(_), Err(error)) | (Err(_), Err(error)) => Err(error.into_excel_error()),
8198 }
8199 }
8200
8201 fn prepare_graph_for_targets_transaction(
8202 &mut self,
8203 targets: &[crate::engine::EvaluationTarget],
8204 options: &crate::engine::TargetEvalOptions<'_>,
8205 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
8206 use crate::engine::{
8207 OpaqueReason, PreparationOutcome, PrepareScope, PreparedTargetGraphReport,
8208 TableSelection,
8209 };
8210
8211 self.target_preparation_checkpoint(options.deadline, 0)?;
8212 self.observe_function_semantic_epoch()?;
8213 let assumptions = self.preparation_revisions();
8214 let ledger_at_start = self
8215 .active_resource_ledger
8216 .as_ref()
8217 .map(|ledger| ledger.snapshot());
8218 let diagnostics_len = self.formula_parse_diagnostics.len();
8219 let report_len = self.last_formula_ingest_report.clone();
8220 let request_id = options.request_id.or_else(|| {
8221 self.active_evaluation_resource_request
8222 .as_ref()
8223 .map(|stats| stats.request_id)
8224 });
8225
8226 let mut scope = PrepareScope::Exact;
8227 let mut reasons = Vec::new();
8228 let mut regions = VecDeque::new();
8229 let mut deferred_shared_regions = VecDeque::new();
8230 let mut normalized = Vec::with_capacity(targets.len());
8231 let mut symbol_vertices = VecDeque::new();
8232 for target in targets {
8233 self.target_preparation_checkpoint(options.deadline, 1)?;
8234 match target {
8235 crate::engine::EvaluationTarget::Cell { sheet, row, col } => {
8236 if *row == 0 || *col == 0 {
8237 return Err(ExcelError::new(ExcelErrorKind::Ref)
8238 .with_message("target cell coordinates are one-based"));
8239 }
8240 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
8241 ExcelError::new(ExcelErrorKind::Ref)
8242 .with_message(format!("target sheet not found: {sheet}"))
8243 })?;
8244 regions.push_back(PreparationRegion {
8245 sheet: sheet.clone(),
8246 sheet_id,
8247 start_row: *row,
8248 start_col: *col,
8249 end_row: *row,
8250 end_col: *col,
8251 });
8252 normalized.push(target.clone());
8253 }
8254 crate::engine::EvaluationTarget::Range(range) => {
8255 if range.start_row == 0
8256 || range.start_col == 0
8257 || range.end_row < range.start_row
8258 || range.end_col < range.start_col
8259 {
8260 return Err(ExcelError::new(ExcelErrorKind::Ref)
8261 .with_message("invalid target range"));
8262 }
8263 let sheet_id = self.graph.sheet_id(&range.sheet).ok_or_else(|| {
8264 ExcelError::new(ExcelErrorKind::Ref)
8265 .with_message(format!("target sheet not found: {}", range.sheet))
8266 })?;
8267 regions.push_back(PreparationRegion {
8268 sheet: range.sheet.clone(),
8269 sheet_id,
8270 start_row: range.start_row,
8271 start_col: range.start_col,
8272 end_row: range.end_row,
8273 end_col: range.end_col,
8274 });
8275 normalized.push(target.clone());
8276 }
8277 crate::engine::EvaluationTarget::Name { name, scope_sheet } => {
8278 let name_scope = self.name_query_scope(scope_sheet.as_deref())?;
8279 if let Some(entry) = self.graph.resolve_name_entry_in_scope(name, name_scope) {
8280 symbol_vertices.push_back(entry.vertex);
8281 } else {
8282 Self::widen_target_preparation(
8283 options.opaque_policy,
8284 &mut scope,
8285 &mut reasons,
8286 OpaqueReason::UnresolvedName,
8287 )?;
8288 }
8289 normalized.push(target.clone());
8290 }
8291 crate::engine::EvaluationTarget::Table { name, selection } => {
8292 if let Some(entry) = self.graph.resolve_table_entry(name) {
8293 let region = self.table_selection_region(entry, selection)?;
8294 symbol_vertices.push_back(entry.vertex);
8295 regions.push_back(region);
8296 } else {
8297 Self::widen_target_preparation(
8298 options.opaque_policy,
8299 &mut scope,
8300 &mut reasons,
8301 OpaqueReason::UnresolvedTable,
8302 )?;
8303 }
8304 normalized.push(target.clone());
8305 }
8306 }
8307 }
8308
8309 let mut visited_regions = FxHashSet::default();
8310 let mut visited_vertices = FxHashSet::default();
8311 let mut selected = FxHashSet::default();
8312 let mut prepared = Vec::new();
8313 let mut pending_diagnostics = Vec::new();
8314 let mut planning_requests = BTreeSet::new();
8315 let mut selected_cells = Vec::new();
8316 let mut workbook_seeded = false;
8317 let mut sheet_scope_seeded = BTreeSet::new();
8318 let mut indexed_query_sheets = FxHashSet::default();
8319 let mut discovery_scratch_reserved = 0u64;
8320 let mut package_encountered = false;
8321 let mut selected_package_sheets = FxHashSet::default();
8322 let mut selected_package_points: BTreeMap<String, BTreeSet<(u32, u32)>> = BTreeMap::new();
8323 let mut prepared_packages: Vec<PreparedTargetSourcePackage> = Vec::new();
8324 let authoritative_with_ordinary = false;
8325 let has_unknown_package_sheet = self
8326 .staged_formula_index
8327 .package_sheets()
8328 .any(|sheet| self.graph.sheet_id(sheet).is_none());
8329
8330 loop {
8331 if let PrepareScope::Sheets(sheets) = &scope {
8332 for sheet in sheets.clone() {
8333 if !sheet_scope_seeded.insert(sheet.clone()) {
8334 continue;
8335 }
8336 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8337 package_encountered = true;
8338 Self::widen_target_preparation(
8339 options.opaque_policy,
8340 &mut scope,
8341 &mut reasons,
8342 OpaqueReason::UnsupportedSourceSemantics,
8343 )?;
8344 continue;
8345 };
8346 for lease in self.staged_formula_index.leases_for_sheet(&sheet) {
8347 self.target_preparation_checkpoint(options.deadline, 1)?;
8348 regions.push_back(PreparationRegion {
8349 sheet: sheet.clone(),
8350 sheet_id,
8351 start_row: lease.row,
8352 start_col: lease.col,
8353 end_row: lease.row,
8354 end_col: lease.col,
8355 });
8356 }
8357 if self
8358 .staged_formula_index
8359 .package_lease_for_sheet(&sheet)
8360 .is_some()
8361 {
8362 regions.push_back(PreparationRegion {
8363 sheet: sheet.clone(),
8364 sheet_id,
8365 start_row: 1,
8366 start_col: 1,
8367 end_row: self.workbook_load_limits.max_sheet_rows,
8368 end_col: self.workbook_load_limits.max_sheet_cols,
8369 });
8370 }
8371 }
8372 }
8373 if matches!(scope, PrepareScope::Workbook) && !workbook_seeded {
8374 workbook_seeded = true;
8375 for (sheet, lease) in self.staged_formula_index.all_leases() {
8376 self.target_preparation_checkpoint(options.deadline, 1)?;
8377 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8378 package_encountered = true;
8379 Self::widen_target_preparation(
8380 options.opaque_policy,
8381 &mut scope,
8382 &mut reasons,
8383 OpaqueReason::UnsupportedSourceSemantics,
8384 )?;
8385 continue;
8386 };
8387 regions.push_back(PreparationRegion {
8388 sheet,
8389 sheet_id,
8390 start_row: lease.row,
8391 start_col: lease.col,
8392 end_row: lease.row,
8393 end_col: lease.col,
8394 });
8395 }
8396 let package_sheets = self
8397 .staged_formula_index
8398 .package_sheets()
8399 .map(str::to_string)
8400 .collect::<Vec<_>>();
8401 for sheet in package_sheets {
8402 let Some(sheet_id) = self.graph.sheet_id(&sheet) else {
8403 package_encountered = true;
8404 Self::widen_target_preparation(
8405 options.opaque_policy,
8406 &mut scope,
8407 &mut reasons,
8408 OpaqueReason::UnsupportedSourceSemantics,
8409 )?;
8410 continue;
8411 };
8412 regions.push_back(PreparationRegion {
8413 sheet,
8414 sheet_id,
8415 start_row: 1,
8416 start_col: 1,
8417 end_row: self.workbook_load_limits.max_sheet_rows,
8418 end_col: self.workbook_load_limits.max_sheet_cols,
8419 });
8420 }
8421 }
8422
8423 let allow_partial_shared = regions.is_empty() && symbol_vertices.is_empty();
8426 let next_region = if allow_partial_shared {
8427 deferred_shared_regions.pop_front()
8428 } else {
8429 regions.pop_front()
8430 };
8431 let Some(region) = next_region else {
8432 if let Some(vertex) = symbol_vertices.pop_front() {
8433 self.target_preparation_checkpoint(options.deadline, 1)?;
8434 if !visited_vertices.insert(vertex) || !self.graph.vertex_exists(vertex) {
8435 continue;
8436 }
8437 let vertex_is_dynamic = self.graph.is_dynamic(vertex);
8438 if let Some(ast) = self.graph.get_formula(vertex) {
8439 let snapshot =
8440 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8441 if let Some(reason) =
8442 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8443 {
8444 return Err(Self::preparation_stale(
8445 reason,
8446 "target planning snapshot became stale during discovery",
8447 ));
8448 }
8449 let opaque = self.opaque_reason_in_ast(&ast, &snapshot);
8450 if let Some(reason) =
8451 opaque.or(vertex_is_dynamic.then_some(OpaqueReason::DynamicReference))
8452 {
8453 if reason == OpaqueReason::DynamicReference
8454 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
8455 {
8456 let sheet = self.graph.get_vertex_sheet_id(vertex);
8457 let sheet = self.graph.sheet_name(sheet).to_string();
8458 Self::widen_target_preparation_to_sheet(
8459 options.opaque_policy,
8460 &mut scope,
8461 &mut reasons,
8462 reason,
8463 &sheet,
8464 )?;
8465 } else {
8466 Self::widen_target_preparation(
8467 options.opaque_policy,
8468 &mut scope,
8469 &mut reasons,
8470 reason,
8471 )?;
8472 }
8473 }
8474 } else if vertex_is_dynamic {
8475 Self::widen_target_preparation(
8476 options.opaque_policy,
8477 &mut scope,
8478 &mut reasons,
8479 OpaqueReason::DynamicReference,
8480 )?;
8481 }
8482 if let Some(anchor) = self
8483 .graph
8484 .get_cell_ref(vertex)
8485 .and_then(|cell| self.graph.spill_registry_anchor_for_cell(cell))
8486 {
8487 symbol_vertices.push_back(anchor);
8488 }
8489 if let Some(cell) = self.graph.get_cell_ref(vertex) {
8490 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
8491 regions.push_back(PreparationRegion {
8492 sheet,
8493 sheet_id: cell.sheet_id,
8494 start_row: cell.coord.row() + 1,
8495 start_col: cell.coord.col() + 1,
8496 end_row: cell.coord.row() + 1,
8497 end_col: cell.coord.col() + 1,
8498 });
8499 }
8500 match self.graph.authority_vertex_precedents(vertex) {
8504 Some(precedents) => {
8505 for (sheet_id, rect) in precedents {
8506 self.target_preparation_checkpoint(options.deadline, 1)?;
8507 if sheet_id == crate::engine::authority::geom::SYMBOL_SHEET {
8508 for slot in rect.r0..=rect.r1 {
8509 if let Some(symbol) =
8510 self.graph.authority_host().symbols().vertex(slot)
8511 {
8512 symbol_vertices.push_back(symbol);
8513 }
8514 }
8515 continue;
8516 }
8517 let sheet = self.graph.sheet_name(sheet_id).to_string();
8518 regions.push_back(PreparationRegion {
8519 sheet,
8520 sheet_id,
8521 start_row: rect.r0 + 1,
8522 start_col: rect.c0 + 1,
8523 end_row: (rect.r1 + 1)
8524 .min(self.workbook_load_limits.max_sheet_rows),
8525 end_col: (rect.c1 + 1)
8526 .min(self.workbook_load_limits.max_sheet_cols),
8527 });
8528 }
8529 }
8530 None => {
8532 if Self::widen_target_preparation(
8533 options.opaque_policy,
8534 &mut scope,
8535 &mut reasons,
8536 OpaqueReason::UnresolvedCrossSheetBinding,
8537 )? {
8538 continue;
8539 }
8540 }
8541 }
8542 if let Some(name) = self.graph.named_range_by_vertex(vertex).cloned() {
8543 match &name.definition {
8544 NamedDefinition::Cell(cell) => regions.push_back(PreparationRegion {
8545 sheet: self.graph.sheet_name(cell.sheet_id).to_string(),
8546 sheet_id: cell.sheet_id,
8547 start_row: cell.coord.row() + 1,
8548 start_col: cell.coord.col() + 1,
8549 end_row: cell.coord.row() + 1,
8550 end_col: cell.coord.col() + 1,
8551 }),
8552 NamedDefinition::Range(range) => regions.push_back(PreparationRegion {
8553 sheet: self.graph.sheet_name(range.start.sheet_id).to_string(),
8554 sheet_id: range.start.sheet_id,
8555 start_row: range.start.coord.row() + 1,
8556 start_col: range.start.coord.col() + 1,
8557 end_row: range.end.coord.row() + 1,
8558 end_col: range.end.coord.col() + 1,
8559 }),
8560 NamedDefinition::Formula {
8561 ast,
8562 dependencies,
8563 range_deps,
8564 } => {
8565 let snapshot =
8566 self.target_planning_snapshot(ast, &mut planning_requests)?;
8567 if let Some(reason) = Self::target_planning_snapshot_stale_reason(
8568 &snapshot,
8569 &assumptions,
8570 ) {
8571 return Err(Self::preparation_stale(
8572 reason,
8573 "target planning snapshot became stale during discovery",
8574 ));
8575 }
8576 if let Some(reason) = self.opaque_reason_in_ast(ast, &snapshot) {
8577 Self::widen_target_preparation(
8578 options.opaque_policy,
8579 &mut scope,
8580 &mut reasons,
8581 reason,
8582 )?;
8583 }
8584 for dependency in dependencies {
8585 self.target_preparation_checkpoint(options.deadline, 1)?;
8586 symbol_vertices.push_back(*dependency);
8587 }
8588 for range in range_deps {
8589 self.target_preparation_checkpoint(options.deadline, 1)?;
8590 let context_sheet = self.graph.get_vertex_sheet_id(vertex);
8598 let Ok(sheet_id) =
8599 self.resolve_sheet_locator(&range.sheet, context_sheet)
8600 else {
8601 if Self::widen_target_preparation(
8602 options.opaque_policy,
8603 &mut scope,
8604 &mut reasons,
8605 OpaqueReason::UnresolvedCrossSheetBinding,
8606 )? {
8607 break;
8608 }
8609 continue;
8610 };
8611 regions.push_back(PreparationRegion {
8612 sheet: self.graph.sheet_name(sheet_id).to_string(),
8613 sheet_id,
8614 start_row: range
8615 .start_row
8616 .map_or(1, |bound| bound.index + 1),
8617 start_col: range
8618 .start_col
8619 .map_or(1, |bound| bound.index + 1),
8620 end_row: range.end_row.map_or(
8621 self.workbook_load_limits.max_sheet_rows,
8622 |bound| bound.index + 1,
8623 ),
8624 end_col: range.end_col.map_or(
8625 self.workbook_load_limits.max_sheet_cols,
8626 |bound| bound.index + 1,
8627 ),
8628 });
8629 }
8630 }
8631 NamedDefinition::Literal(_) => {}
8632 }
8633 }
8634 if let Some(table) = self.graph.table_by_vertex(vertex) {
8635 regions
8636 .push_back(self.table_selection_region(table, &TableSelection::Whole)?);
8637 }
8638 continue;
8639 }
8640 break;
8641 };
8642
8643 self.target_preparation_checkpoint(options.deadline, 1)?;
8644 if !visited_regions.insert(region.clone()) && !allow_partial_shared {
8645 continue;
8646 }
8647 if let PrepareScope::Sheets(sheets) = &mut scope
8648 && !sheets.iter().any(|sheet| sheet == ®ion.sheet)
8649 {
8650 sheets.push(region.sheet.clone());
8651 sheets.sort();
8652 }
8653 let package_match = self.staged_formula_index.package_for_region(
8654 ®ion.sheet,
8655 region.start_row,
8656 region.start_col,
8657 region.end_row,
8658 region.end_col,
8659 );
8660 let package_lease = match package_match {
8661 Some(Ok(lease)) => Some(lease),
8662 Some(Err(()))
8663 if region.start_row == 1
8664 && region.start_col == 1
8665 && region.end_row == self.workbook_load_limits.max_sheet_rows
8666 && region.end_col == self.workbook_load_limits.max_sheet_cols =>
8667 {
8668 self.staged_formula_index
8669 .package_lease_for_sheet(®ion.sheet)
8670 }
8671 Some(Err(())) => {
8672 Self::widen_target_preparation(
8673 options.opaque_policy,
8674 &mut scope,
8675 &mut reasons,
8676 OpaqueReason::DeferredSourcePackage,
8677 )?;
8678 None
8679 }
8680 None => None,
8681 };
8682 let compatibility_before_package_replay = package_lease.is_some()
8683 && (authoritative_with_ordinary || has_unknown_package_sheet);
8684 let package_lease = if compatibility_before_package_replay {
8685 package_encountered = true;
8686 Self::widen_target_preparation(
8687 options.opaque_policy,
8688 &mut scope,
8689 &mut reasons,
8690 OpaqueReason::UnsupportedSourceSemantics,
8691 )?;
8692 None
8693 } else {
8694 package_lease
8695 };
8696 if let Some(package_lease) = package_lease
8697 && !selected_package_sheets.contains(®ion.sheet)
8698 {
8699 self.target_preparation_checkpoint(options.deadline, 1)?;
8700 let mut points = self.staged_formula_index.package_points_in_region(
8701 ®ion.sheet,
8702 region.start_row,
8703 region.start_col,
8704 region.end_row,
8705 region.end_col,
8706 );
8707 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8708 points.retain(|point| !selected.contains(point));
8709 }
8710
8711 if let Some(selected) = selected_package_points.get(®ion.sheet) {
8712 points.retain(|point| !selected.contains(point));
8713 }
8714 let permit_partial = allow_partial_shared
8715 || (points.len() == 1
8716 && regions.is_empty()
8717 && symbol_vertices.is_empty()
8718 && deferred_shared_regions.is_empty());
8719 let partial = self.prepare_target_exact_source_selection(
8720 ®ion.sheet,
8721 package_lease,
8722 points,
8723 selected_package_points
8724 .get(®ion.sheet)
8725 .unwrap_or(&BTreeSet::new()),
8726 permit_partial,
8727 options.deadline,
8728 &mut discovery_scratch_reserved,
8729 )?;
8730 let mut package = if let Some(mut package) = partial {
8731 if package.deferred_shared {
8732 deferred_shared_regions.push_back(region.clone());
8733 }
8734 if !package.direct_domains.is_empty() {
8738 for prior in prepared_packages
8739 .iter_mut()
8740 .filter(|prior| prior.sheet == region.sheet)
8741 {
8742 prior
8743 .replay_records
8744 .retain(|record| !package.direct_contains(record.row, record.col));
8745 prior
8746 .legacy
8747 .retain(|(row, col, _, _)| !package.direct_contains(*row, *col));
8748 if let Some(points) = prior.selected_points.as_mut() {
8749 points.retain(|&(row, col)| !package.direct_contains(row, col));
8750 }
8751 }
8752 }
8753 if selected_package_points.contains_key(®ion.sheet) {
8754 package.source_report = Default::default();
8755 }
8756 let points = package.selected_points.as_ref().unwrap();
8757 if !points.is_empty() {
8758 selected_package_points
8759 .entry(region.sheet.clone())
8760 .or_default()
8761 .extend(points.iter().copied());
8762 }
8763 package
8764 } else {
8765 selected_package_sheets.insert(region.sheet.clone());
8766 self.prepare_target_source_package(
8767 ®ion.sheet,
8768 package_lease,
8769 options.deadline,
8770 )?
8771 };
8772 if package
8773 .selected_points
8774 .as_ref()
8775 .is_none_or(|points| !points.is_empty())
8776 {
8777 let mut final_fallback = BTreeMap::new();
8778 for record in package.fallback_records() {
8779 final_fallback.insert((record.row, record.col), record.clone());
8780 }
8781 let batch = self.formula_batch_from_exact_replay(
8782 ®ion.sheet,
8783 final_fallback.into_values(),
8784 )?;
8785 for record in batch.formulas {
8786 self.target_preparation_checkpoint(options.deadline, 1)?;
8787 let ast = self
8788 .graph
8789 .data_store()
8790 .retrieve_ast(record.ast_id, self.graph.sheet_reg())
8791 .ok_or_else(|| {
8792 ExcelError::new(ExcelErrorKind::Value)
8793 .with_message("target fallback AST is unavailable")
8794 })?;
8795 let snapshot =
8796 self.target_planning_snapshot(&ast, &mut planning_requests)?;
8797 if let Some(reason) =
8798 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
8799 {
8800 return Err(Self::preparation_stale(
8801 reason,
8802 "target fallback planning snapshot became stale during discovery",
8803 ));
8804 }
8805 let proven_sheet_local_dynamic =
8806 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
8807 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
8808 if reason == OpaqueReason::DynamicReference
8809 && proven_sheet_local_dynamic
8810 {
8811 Self::widen_target_preparation_to_sheet(
8812 options.opaque_policy,
8813 &mut scope,
8814 &mut reasons,
8815 reason,
8816 ®ion.sheet,
8817 )?;
8818 } else {
8819 Self::widen_target_preparation(
8820 options.opaque_policy,
8821 &mut scope,
8822 &mut reasons,
8823 reason,
8824 )?;
8825 }
8826 }
8827 let placement = CellRef::new(
8828 package.sheet_id,
8829 Coord::from_excel(record.row, record.col, true, true),
8830 );
8831 let ingested = self
8832 .graph
8833 .ingest_pipeline(&snapshot)
8834 .enable_function_semantics()
8835 .ingest_formula(
8836 FormulaAstInput::RawArena(record.ast_id),
8837 placement,
8838 record.formula_text,
8839 )?;
8840 if ingested.dep_plan.dynamic {
8841 if proven_sheet_local_dynamic {
8842 Self::widen_target_preparation_to_sheet(
8843 options.opaque_policy,
8844 &mut scope,
8845 &mut reasons,
8846 OpaqueReason::DynamicReference,
8847 ®ion.sheet,
8848 )?;
8849 } else {
8850 Self::widen_target_preparation(
8851 options.opaque_policy,
8852 &mut scope,
8853 &mut reasons,
8854 OpaqueReason::DynamicReference,
8855 )?;
8856 }
8857 }
8858 for dep in &ingested.dep_plan.direct_cell_deps {
8859 self.target_preparation_checkpoint(options.deadline, 1)?;
8860 regions.push_back(PreparationRegion {
8861 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
8862 sheet_id: dep.sheet_id,
8863 start_row: dep.coord.row().saturating_add(1),
8864 start_col: dep.coord.col().saturating_add(1),
8865 end_row: dep.coord.row().saturating_add(1),
8866 end_col: dep.coord.col().saturating_add(1),
8867 });
8868 }
8869 for range in &ingested.dep_plan.range_deps {
8870 self.target_preparation_checkpoint(options.deadline, 1)?;
8871 let Ok(dependency_sheet) =
8874 self.resolve_sheet_locator(&range.sheet, package.sheet_id)
8875 else {
8876 Self::widen_target_preparation(
8877 options.opaque_policy,
8878 &mut scope,
8879 &mut reasons,
8880 OpaqueReason::UnresolvedCrossSheetBinding,
8881 )?;
8882 continue;
8883 };
8884 regions.push_back(PreparationRegion {
8885 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
8886 sheet_id: dependency_sheet,
8887 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
8888 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
8889 end_row: range
8890 .end_row
8891 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
8892 bound.index + 1
8893 }),
8894 end_col: range
8895 .end_col
8896 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
8897 bound.index + 1
8898 }),
8899 });
8900 }
8901 for name in ingested
8902 .dep_plan
8903 .resolved_named_refs
8904 .iter()
8905 .chain(&ingested.dep_plan.named_refs)
8906 {
8907 self.target_preparation_checkpoint(options.deadline, 1)?;
8908 if let Some(entry) =
8909 self.graph.resolve_name_entry(name, package.sheet_id)
8910 {
8911 symbol_vertices.push_back(entry.vertex);
8912 } else if self.graph.resolve_source_scalar_entry(name).is_none()
8913 && self.graph.resolve_source_table_entry(name).is_none()
8914 {
8915 Self::widen_target_preparation(
8916 options.opaque_policy,
8917 &mut scope,
8918 &mut reasons,
8919 OpaqueReason::UnresolvedName,
8920 )?;
8921 }
8922 }
8923 for table in &ingested.dep_plan.table_refs {
8924 self.target_preparation_checkpoint(options.deadline, 1)?;
8925 if let Some(entry) = self.graph.resolve_table_entry(table) {
8926 symbol_vertices.push_back(entry.vertex);
8927 } else if self.graph.resolve_source_table_entry(table).is_none() {
8928 Self::widen_target_preparation(
8929 options.opaque_policy,
8930 &mut scope,
8931 &mut reasons,
8932 OpaqueReason::UnresolvedTable,
8933 )?;
8934 }
8935 }
8936 package.legacy.push((
8937 record.row,
8938 record.col,
8939 ingested.ast_id,
8940 ingested.dep_plan,
8941 ));
8942 }
8943 prepared_packages.push(package);
8944 }
8945 }
8946 let leases = self.staged_formula_index.leases_in_region(
8947 ®ion.sheet,
8948 region.start_row,
8949 region.start_col,
8950 region.end_row,
8951 region.end_col,
8952 );
8953 for lease in leases {
8954 self.target_preparation_checkpoint(options.deadline, 1)?;
8955 let sheet_id = self.graph.sheet_id(®ion.sheet).ok_or_else(|| {
8956 ExcelError::new(ExcelErrorKind::Ref)
8957 .with_message(format!("staged formula sheet not found: {}", region.sheet))
8958 })?;
8959 let key = (region.sheet.clone(), lease.row, lease.col, lease.generation);
8960 if !selected.insert(key) {
8961 continue;
8962 }
8963 let text = self
8964 .staged_formulas
8965 .get(®ion.sheet)
8966 .and_then(|sheet| sheet.get_ordinary(lease.row, lease.col))
8967 .ok_or_else(|| {
8968 ExcelError::new(ExcelErrorKind::Value)
8969 .with_message("staged formula index is stale")
8970 })?
8971 .to_string();
8972 let formula = if text.starts_with('=') {
8973 text.clone()
8974 } else {
8975 format!("={text}")
8976 };
8977 self.target_preparation_checkpoint(options.deadline, 1)?;
8978 let ast = match formualizer_parse::parser::parse(&formula) {
8979 Ok(ast) => ast,
8980 Err(error) => {
8981 if self.config.formula_parse_policy == FormulaParsePolicy::Strict {
8982 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
8983 format!(
8984 "Formula parse error at {}!{}{}: {error}",
8985 region.sheet,
8986 col_letters_from_1based(lease.col)
8987 .unwrap_or_else(|_| "?".to_string()),
8988 lease.row
8989 ),
8990 ));
8991 }
8992 pending_diagnostics.push(FormulaParseDiagnostic {
8993 sheet: region.sheet.clone(),
8994 row: lease.row,
8995 col: lease.col,
8996 formula: formula.clone(),
8997 message: error.to_string(),
8998 policy: self.config.formula_parse_policy,
8999 });
9000 match self.config.formula_parse_policy {
9001 FormulaParsePolicy::KeepCachedValue => {
9002 selected_cells.push(
9003 formualizer_common::RangeAddress::new(
9004 region.sheet.clone(),
9005 lease.row,
9006 lease.col,
9007 lease.row,
9008 lease.col,
9009 )
9010 .expect("selected staged coordinates are valid"),
9011 );
9012 prepared.push(PreparedOrdinaryStagedFormula {
9013 sheet: region.sheet.clone(),
9014 sheet_id,
9015 lease,
9016 ast_id: None,
9017 plan: None,
9018 });
9019 continue;
9020 }
9021 FormulaParsePolicy::AsText => ASTNode::new(
9022 ASTNodeType::Literal(LiteralValue::Text(formula.clone())),
9023 None,
9024 ),
9025 FormulaParsePolicy::CoerceToError => ASTNode::new(
9026 ASTNodeType::Literal(LiteralValue::Error(
9027 ExcelError::new(ExcelErrorKind::Error)
9028 .with_message(format!("Malformed formula: {error}")),
9029 )),
9030 None,
9031 ),
9032 FormulaParsePolicy::Strict => unreachable!(),
9033 }
9034 }
9035 };
9036 self.target_preparation_checkpoint(options.deadline, 1)?;
9037 let snapshot = self.target_planning_snapshot(&ast, &mut planning_requests)?;
9038 self.target_preparation_checkpoint(options.deadline, 1)?;
9039 if let Some(reason) =
9040 Self::target_planning_snapshot_stale_reason(&snapshot, &assumptions)
9041 {
9042 return Err(Self::preparation_stale(
9043 reason,
9044 "target planning snapshot became stale during discovery",
9045 ));
9046 }
9047 if let Some(reason) = self.opaque_reason_in_ast(&ast, &snapshot) {
9048 if reason == OpaqueReason::DynamicReference
9049 && Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot)
9050 {
9051 Self::widen_target_preparation_to_sheet(
9052 options.opaque_policy,
9053 &mut scope,
9054 &mut reasons,
9055 reason,
9056 ®ion.sheet,
9057 )?;
9058 } else {
9059 Self::widen_target_preparation(
9060 options.opaque_policy,
9061 &mut scope,
9062 &mut reasons,
9063 reason,
9064 )?;
9065 }
9066 }
9067 let proven_sheet_local_dynamic =
9068 Self::ast_has_proven_sheet_local_dynamic(&ast, &snapshot);
9069 let placement = CellRef::new(
9070 sheet_id,
9071 Coord::from_excel(lease.row, lease.col, true, true),
9072 );
9073 let ingested = self.graph.ingest_pipeline(&snapshot).ingest_formula(
9074 FormulaAstInput::Tree(ast),
9075 placement,
9076 Some(Arc::from(formula)),
9077 )?;
9078 self.target_preparation_checkpoint(options.deadline, 1)?;
9079 if ingested.dep_plan.dynamic {
9080 if proven_sheet_local_dynamic {
9081 Self::widen_target_preparation_to_sheet(
9082 options.opaque_policy,
9083 &mut scope,
9084 &mut reasons,
9085 OpaqueReason::DynamicReference,
9086 ®ion.sheet,
9087 )?;
9088 } else {
9089 Self::widen_target_preparation(
9090 options.opaque_policy,
9091 &mut scope,
9092 &mut reasons,
9093 OpaqueReason::DynamicReference,
9094 )?;
9095 }
9096 }
9097 for dep in &ingested.dep_plan.direct_cell_deps {
9098 self.target_preparation_checkpoint(options.deadline, 1)?;
9099 regions.push_back(PreparationRegion {
9100 sheet: self.graph.sheet_name(dep.sheet_id).to_string(),
9101 sheet_id: dep.sheet_id,
9102 start_row: dep.coord.row() + 1,
9103 start_col: dep.coord.col() + 1,
9104 end_row: dep.coord.row() + 1,
9105 end_col: dep.coord.col() + 1,
9106 });
9107 }
9108 for range in &ingested.dep_plan.range_deps {
9109 self.target_preparation_checkpoint(options.deadline, 1)?;
9110 let Ok(dependency_sheet) = self.resolve_sheet_locator(&range.sheet, sheet_id)
9112 else {
9113 Self::widen_target_preparation(
9114 options.opaque_policy,
9115 &mut scope,
9116 &mut reasons,
9117 OpaqueReason::UnresolvedCrossSheetBinding,
9118 )?;
9119 continue;
9120 };
9121 regions.push_back(PreparationRegion {
9122 sheet: self.graph.sheet_name(dependency_sheet).to_string(),
9123 sheet_id: dependency_sheet,
9124 start_row: range.start_row.map_or(1, |bound| bound.index + 1),
9125 start_col: range.start_col.map_or(1, |bound| bound.index + 1),
9126 end_row: range
9127 .end_row
9128 .map_or(self.workbook_load_limits.max_sheet_rows, |bound| {
9129 bound.index + 1
9130 }),
9131 end_col: range
9132 .end_col
9133 .map_or(self.workbook_load_limits.max_sheet_cols, |bound| {
9134 bound.index + 1
9135 }),
9136 });
9137 }
9138 for name in ingested
9139 .dep_plan
9140 .resolved_named_refs
9141 .iter()
9142 .chain(&ingested.dep_plan.named_refs)
9143 {
9144 self.target_preparation_checkpoint(options.deadline, 1)?;
9145 if let Some(entry) = self.graph.resolve_name_entry(name, sheet_id) {
9146 symbol_vertices.push_back(entry.vertex);
9147 } else if self.graph.resolve_source_scalar_entry(name).is_none()
9148 && self.graph.resolve_source_table_entry(name).is_none()
9149 {
9150 Self::widen_target_preparation(
9151 options.opaque_policy,
9152 &mut scope,
9153 &mut reasons,
9154 OpaqueReason::UnresolvedName,
9155 )?;
9156 }
9157 }
9158 for table in &ingested.dep_plan.table_refs {
9159 self.target_preparation_checkpoint(options.deadline, 1)?;
9160 if let Some(entry) = self.graph.resolve_table_entry(table) {
9161 symbol_vertices.push_back(entry.vertex);
9162 } else if self.graph.resolve_source_table_entry(table).is_none() {
9163 Self::widen_target_preparation(
9164 options.opaque_policy,
9165 &mut scope,
9166 &mut reasons,
9167 OpaqueReason::UnresolvedTable,
9168 )?;
9169 }
9170 }
9171 selected_cells.push(
9172 formualizer_common::RangeAddress::new(
9173 region.sheet.clone(),
9174 lease.row,
9175 lease.col,
9176 lease.row,
9177 lease.col,
9178 )
9179 .expect("selected staged coordinates are valid"),
9180 );
9181 prepared.push(PreparedOrdinaryStagedFormula {
9182 sheet: region.sheet.clone(),
9183 sheet_id,
9184 lease,
9185 ast_id: Some(ingested.ast_id),
9186 plan: Some(ingested.dep_plan),
9187 });
9188 }
9189
9190 if indexed_query_sheets.insert(region.sheet_id) {
9191 self.graph.prepare_sheet_index_for_query(region.sheet_id);
9192 let bytes = (self.graph.sheet_index_vertex_count(region.sheet_id) as u64)
9193 .saturating_mul(32);
9194 self.reserve_graph_source_scratch(bytes)?;
9195 discovery_scratch_reserved = discovery_scratch_reserved.saturating_add(bytes);
9196 }
9197 let spill_anchors = self.graph.spill_anchors_in_region(
9198 region.sheet_id,
9199 region.start_row - 1,
9200 region.start_col - 1,
9201 region.end_row - 1,
9202 region.end_col - 1,
9203 );
9204 for anchor in spill_anchors {
9205 self.target_preparation_checkpoint(options.deadline, 1)?;
9206 symbol_vertices.push_back(anchor);
9207 }
9208 let vertices = self.graph.vertices_in_region(
9209 region.sheet_id,
9210 region.start_row - 1,
9211 region.end_row - 1,
9212 region.start_col - 1,
9213 region.end_col - 1,
9214 );
9215 for vertex in vertices {
9216 self.target_preparation_checkpoint(options.deadline, 1)?;
9217 symbol_vertices.push_back(vertex);
9218 }
9219 }
9220
9221 #[cfg(test)]
9222 self.target_preparation_fault(
9223 crate::engine::target_preparation::TargetPreparationFault::AfterDiscovery,
9224 )?;
9225
9226 if package_encountered {
9227 Self::widen_target_preparation(
9228 options.opaque_policy,
9229 &mut scope,
9230 &mut reasons,
9231 OpaqueReason::UnsupportedSourceSemantics,
9232 )?;
9233 self.target_preparation_checkpoint(options.deadline, 0)?;
9234 let selected_count = self.staged_formula_count();
9235 let selected_packages = self
9236 .staged_formulas
9237 .values()
9238 .filter_map(|staged| staged.deferred_package.as_ref())
9239 .map(|package| package.families.len() + package.partitioned_families.len())
9240 .sum();
9241 self.formula_parse_diagnostics.truncate(diagnostics_len);
9242 self.last_formula_ingest_report = report_len;
9243 #[cfg(test)]
9244 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
9245 hook();
9246 }
9247 self.target_preparation_checkpoint(options.deadline, 0)?;
9248 #[cfg(test)]
9249 self.target_preparation_fault(
9250 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
9251 )?;
9252 let current_revisions = self.preparation_revisions();
9253 if let Some(reason) = Self::preparation_revision_stale_reason(
9254 &assumptions,
9255 ¤t_revisions,
9256 &planning_requests,
9257 true,
9258 ) {
9259 return Err(Self::preparation_stale(
9260 reason,
9261 "target compatibility preparation plan is stale",
9262 ));
9263 }
9264 #[cfg(test)]
9265 self.target_preparation_fault(
9266 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9267 )?;
9268 let commit_work_before = self
9269 .active_resource_ledger
9270 .as_ref()
9271 .map_or(0, |ledger| ledger.snapshot().work_charged);
9272 let commit_started = crate::instant::FzInstant::now();
9273 self.build_graph_all_unobserved()?;
9274 let commit_window = commit_started.elapsed();
9275 let ledger_after = self
9276 .active_resource_ledger
9277 .as_ref()
9278 .map(|ledger| ledger.snapshot());
9279 let actual_commit_work = ledger_after
9280 .map_or(0, |snapshot| snapshot.work_charged)
9281 .saturating_sub(commit_work_before);
9282 let observed_scratch_bytes = ledger_after
9283 .map_or(0, |snapshot| snapshot.scratch_peak)
9284 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9285 let revisions = assumptions.clone();
9286 let report = PreparedTargetGraphReport {
9287 request_id: request_id.unwrap_or_default(),
9288 requested_targets: targets.len(),
9289 normalized_regions: visited_regions.len(),
9290 normalized_target_list: normalized,
9291 selected_staged_cells: selected_count,
9292 selected_source_families: selected_packages,
9293 retained_staged_cells: self.staged_formula_count(),
9294 selected_cells,
9295 retained_cells: Vec::new(),
9296 widened_scope: PrepareScope::Workbook,
9297 widening_reasons: reasons,
9298 revisions,
9299 commit_window,
9300 estimated_scratch_bytes: discovery_scratch_reserved
9301 .saturating_add((selected_count as u64).saturating_mul(256)),
9302 observed_scratch_bytes,
9303 estimated_commit_work: selected_count as u64,
9304 actual_commit_work,
9305 outcome: PreparationOutcome::CompatibilityPrepared,
9306 };
9307 self.observe_target_preparation_report(&report);
9308 return Ok(report);
9309 }
9310
9311 prepared.sort_by_key(|formula| formula.lease.insertion_order);
9312 for package in &prepared_packages {
9313 if let Some(points) = &package.selected_points {
9314 selected_cells.extend(points.iter().filter_map(|&(row, col)| {
9315 formualizer_common::RangeAddress::new(&package.sheet, row, col, row, col).ok()
9316 }));
9317 continue;
9318 }
9319 selected_cells.extend(package.replay_records.iter().filter_map(|record| {
9320 formualizer_common::RangeAddress::new(
9321 package.sheet.clone(),
9322 record.row,
9323 record.col,
9324 record.row,
9325 record.col,
9326 )
9327 .ok()
9328 }));
9329 }
9330 let (legacy_graph, planned_formula_count) =
9331 self.prepare_target_combined_legacy_graph(&prepared_packages, &prepared)?;
9332 let new_vertices = legacy_graph.new_vertex_count();
9333 let new_edges = legacy_graph.planned_edge_count().ok_or_else(|| {
9334 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9335 })?;
9336 let removed_edges = legacy_graph.removed_edge_count().ok_or_else(|| {
9337 ExcelError::new(ExcelErrorKind::NImpl).with_message("target graph edge count overflow")
9338 })?;
9339 let current = self.graph.baseline_stats();
9340 let final_vertices = current
9341 .graph_vertex_count
9342 .checked_add(new_vertices)
9343 .ok_or_else(|| {
9344 crate::engine::ResourceLedgerError::Exhausted(
9345 formualizer_common::ResourceExhaustionDetail {
9346 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9347 limit: u64::MAX,
9348 observed: u64::MAX,
9349 request_id,
9350 },
9351 )
9352 .into_excel_error()
9353 })?;
9354 let final_edges = current
9355 .graph_edge_count
9356 .checked_sub(removed_edges)
9357 .and_then(|count| count.checked_add(new_edges))
9358 .ok_or_else(|| {
9359 crate::engine::ResourceLedgerError::Exhausted(
9360 formualizer_common::ResourceExhaustionDetail {
9361 reason: formualizer_common::ResourceExhaustionReason::ArithmeticOverflow,
9362 limit: u64::MAX,
9363 observed: u64::MAX,
9364 request_id,
9365 },
9366 )
9367 .into_excel_error()
9368 })?;
9369 #[cfg(test)]
9370 self.target_preparation_fault(
9371 crate::engine::target_preparation::TargetPreparationFault::Admission,
9372 )?;
9373 let resource = |reason, limit: u64, observed: u64| {
9374 crate::engine::ResourceLedgerError::Exhausted(
9375 formualizer_common::ResourceExhaustionDetail {
9376 reason,
9377 limit,
9378 observed,
9379 request_id,
9380 },
9381 )
9382 .into_excel_error()
9383 };
9384 let admission = crate::engine::resource_ledger::GraphAdmission {
9385 final_vertices,
9386 final_edges,
9387 materialization_cells: planned_formula_count as u64,
9388 added_vertices: new_vertices,
9389 added_edges: new_edges,
9390 };
9391 let materialized_bytes = admission
9392 .materialized_graph_bytes()
9393 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
9394 if let Err(error) = self.preflight_graph_admission(admission) {
9395 if let formualizer_common::ExcelErrorExtra::Resource { detail } = &error.extra {
9396 self.observe_target_admission_failure(detail.reason);
9397 }
9398 return Err(error);
9399 }
9400 let selected_package_records = prepared_packages
9401 .iter()
9402 .map(|package| package.replay_records.len() as u64)
9403 .sum::<u64>();
9404 let planned_working_bytes = (prepared.len() as u64)
9405 .saturating_add(selected_package_records)
9406 .saturating_mul(256)
9407 .saturating_add((visited_regions.len() as u64).saturating_mul(128))
9408 .saturating_add((visited_vertices.len() as u64).saturating_mul(32))
9409 .saturating_add((selected.len() as u64).saturating_mul(96))
9410 .saturating_add(materialized_bytes);
9411 let residual_scratch = selected_package_points
9412 .iter()
9413 .map(|(sheet, points)| {
9414 let source = self
9415 .staged_formulas
9416 .get(sheet)
9417 .unwrap()
9418 .deferred_package
9419 .as_ref()
9420 .unwrap();
9421 (source.families.len() as u64)
9422 .saturating_mul(256)
9423 .saturating_add(
9424 source
9425 .partitioned_families
9426 .iter()
9427 .map(|family| {
9428 256u64
9429 .saturating_add(family.fragments.len() as u64 * 32)
9430 .saturating_add(family.legacy_members.len() as u64 * 32)
9431 })
9432 .sum::<u64>(),
9433 )
9434 .saturating_add(points.len() as u64 * 32)
9437 .saturating_add(
9438 prepared_packages
9439 .iter()
9440 .filter(|package| &package.sheet == sheet)
9441 .map(|package| package.replay_records.len() as u64 * 480)
9442 .sum::<u64>(),
9443 )
9444 })
9445 .sum::<u64>();
9446 let scratch_bytes = discovery_scratch_reserved
9447 .saturating_add(planned_working_bytes)
9448 .saturating_add(residual_scratch);
9449 let remaining_scratch = scratch_bytes.saturating_sub(discovery_scratch_reserved);
9450 if let Err(error) = self.reserve_graph_source_scratch(remaining_scratch) {
9451 self.observe_target_admission_failure(
9452 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9453 );
9454 return Err(error);
9455 }
9456
9457 let mut residual_sources = BTreeMap::new();
9458 let mut residual_owners = BTreeMap::new();
9459 for (sheet, points) in &selected_package_points {
9460 let selected: BTreeMap<_, BTreeSet<_>> = prepared_packages
9461 .iter()
9462 .filter(|package| &package.sheet == sheet)
9463 .flat_map(|package| &package.replay_records)
9464 .filter_map(|record| {
9465 record.partition_owner.or(record.family).map(|owner| {
9466 (
9467 owner,
9468 crate::engine::SourceCoord {
9469 row: record.row - 1,
9470 col: record.col - 1,
9471 },
9472 )
9473 })
9474 })
9475 .fold(BTreeMap::new(), |mut map, (owner, coord)| {
9476 map.entry(owner).or_default().insert(coord);
9477 map
9478 });
9479 let metadata_work = self
9480 .staged_formulas
9481 .get(sheet)
9482 .unwrap()
9483 .deferred_package
9484 .as_ref()
9485 .map_or(0, |source| {
9486 source.families.len() as u64
9487 + source
9488 .partitioned_families
9489 .iter()
9490 .map(|family| {
9491 1 + family.fragments.len() as u64 * family.fragments.len() as u64
9492 + family.legacy_members.len() as u64
9493 })
9494 .sum::<u64>()
9495 });
9496 let split_work = selected
9497 .values()
9498 .map(|points| points.len() as u64 * 128)
9499 .sum::<u64>();
9500 self.target_preparation_checkpoint(
9501 options.deadline,
9502 metadata_work
9503 .saturating_add(split_work)
9504 .saturating_add(points.len() as u64),
9505 )?;
9506 let source = self
9507 .staged_formulas
9508 .get(sheet)
9509 .unwrap()
9510 .deferred_package
9511 .as_ref()
9512 .unwrap();
9513 let complete: BTreeSet<_> = prepared_packages
9514 .iter()
9515 .filter(|package| &package.sheet == sheet)
9516 .flat_map(|package| package.complete_selections.iter().copied())
9517 .collect();
9518 let residual = source
9519 .residual_sources(&selected, &complete, &self.workbook_load_limits)
9520 .map_err(|reason| ExcelError::new(ExcelErrorKind::Value).with_message(reason))?;
9521 residual_owners.insert(
9522 sheet.clone(),
9523 residual
9524 .1
9525 .iter()
9526 .map(|family| family.source_id)
9527 .collect::<BTreeSet<_>>(),
9528 );
9529 residual_sources.insert(sheet.clone(), residual);
9530 }
9531
9532 for (sheet, points) in &selected_package_points {
9535 let package = self
9536 .staged_formulas
9537 .get_mut(sheet)
9538 .unwrap()
9539 .deferred_package
9540 .as_mut()
9541 .unwrap();
9542 let owner_count = prepared_packages
9543 .iter()
9544 .filter(|p| &p.sheet == sheet)
9545 .flat_map(|p| &p.replay_records)
9546 .filter_map(|record| record.partition_owner.or(record.family))
9547 .filter(|owner| residual_owners[sheet].contains(owner))
9548 .count();
9549 package
9550 .consumed_members
9551 .try_reserve(owner_count)
9552 .map_err(|_| {
9553 resource(
9554 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9555 0,
9556 owner_count as u64 * 24,
9557 )
9558 })?;
9559 package.suppressed.try_reserve(points.len()).map_err(|_| {
9560 resource(
9561 formualizer_common::ResourceExhaustionReason::ScratchMemory,
9562 0,
9563 points.len() as u64 * 16,
9564 )
9565 })?;
9566 }
9567
9568 let estimated_commit_duration = std::time::Duration::from_nanos(
9569 (new_vertices as u64)
9570 .saturating_add(new_edges as u64)
9571 .saturating_add(prepared.len() as u64)
9572 .saturating_add(selected_package_records)
9573 .max(1)
9574 .saturating_mul(100),
9575 );
9576 if options.deadline.is_some_and(|deadline| {
9577 std::time::Instant::now()
9578 .checked_add(estimated_commit_duration)
9579 .is_none_or(|finish| finish > deadline)
9580 }) {
9581 self.observe_target_admission_failure(
9582 formualizer_common::ResourceExhaustionReason::Deadline,
9583 );
9584 return Err(resource(
9585 formualizer_common::ResourceExhaustionReason::Deadline,
9586 0,
9587 1,
9588 ));
9589 }
9590 #[cfg(test)]
9591 if let Some(hook) = self.before_target_preparation_commit_hook.take() {
9592 hook();
9593 }
9594 self.target_preparation_checkpoint(options.deadline, 0)?;
9595 #[cfg(test)]
9596 self.target_preparation_fault(
9597 crate::engine::target_preparation::TargetPreparationFault::FinalRevisionValidation,
9598 )?;
9599 let current_revisions = self.preparation_revisions();
9600 let staged_leases_match = prepared.iter().all(|formula| {
9601 self.staged_formula_index
9602 .lease_matches(&formula.sheet, formula.lease)
9603 }) && prepared_packages.iter().all(|package| {
9604 self.staged_formula_index
9605 .package_lease_matches(&package.sheet, package.lease)
9606 });
9607 let stale_reason = Self::preparation_revision_stale_reason(
9608 &assumptions,
9609 ¤t_revisions,
9610 &planning_requests,
9611 staged_leases_match,
9612 );
9613 if let Some(reason) = stale_reason {
9614 return Err(Self::preparation_stale(
9615 reason,
9616 "target graph preparation plan is stale",
9617 ));
9618 }
9619 #[cfg(test)]
9620 self.target_preparation_fault(
9621 crate::engine::target_preparation::TargetPreparationFault::FinalGraphValidation,
9622 )?;
9623 self.graph
9624 .validate_prepared_legacy_graph_plan(&legacy_graph)
9625 .map_err(|error| {
9626 Self::preparation_stale(
9627 formualizer_common::PreparationStaleReason::Graph,
9628 format!("target graph preparation plan is stale: {error}"),
9629 )
9630 })?;
9631 #[cfg(test)]
9632 self.target_preparation_fault(
9633 crate::engine::target_preparation::TargetPreparationFault::Reservation,
9634 )?;
9635 self.graph.reserve_prepared_legacy_graph_plan(&legacy_graph);
9636 self.formula_parse_diagnostics
9637 .try_reserve(pending_diagnostics.len())
9638 .map_err(|_| {
9639 resource(
9640 formualizer_common::ResourceExhaustionReason::Admission,
9641 pending_diagnostics.len() as u64,
9642 pending_diagnostics.len() as u64,
9643 )
9644 })?;
9645 self.target_preparation_checkpoint(options.deadline, 0)?;
9646 #[cfg(test)]
9647 self.target_preparation_fault(
9648 crate::engine::target_preparation::TargetPreparationFault::BeforeFirstMutation,
9649 )?;
9650
9651 let commit_started = crate::instant::FzInstant::now();
9652 let committed = self
9653 .graph
9654 .apply_prevalidated_legacy_graph_plan(legacy_graph);
9655 for formula in &prepared {
9656 let removed = self
9657 .staged_formulas
9658 .get_mut(&formula.sheet)
9659 .and_then(|sheet| sheet.remove_ordinary(formula.lease.row, formula.lease.col));
9660 debug_assert!(removed.is_some());
9661 let index_removed = self.staged_formula_index.remove(
9662 &formula.sheet,
9663 formula.lease.row,
9664 formula.lease.col,
9665 );
9666 debug_assert!(index_removed);
9667 }
9668 for package in &prepared_packages {
9669 if let Some(points) = &package.selected_points {
9670 let staged = self.staged_formulas.get_mut(&package.sheet).unwrap();
9671 let source = staged.deferred_package.as_mut().unwrap();
9672 source.consumed_engine = Some(Arc::clone(&self.source_formula_token));
9673 source
9676 .consumed_members
9677 .extend(package.replay_records.iter().filter_map(|record| {
9678 record
9679 .partition_owner
9680 .or(record.family)
9681 .filter(|owner| residual_owners[&package.sheet].contains(owner))
9682 .map(|owner| {
9683 (
9684 owner,
9685 crate::engine::SourceCoord {
9686 row: record.row - 1,
9687 col: record.col - 1,
9688 },
9689 )
9690 })
9691 }));
9692 source.suppressed.extend(points.iter().copied());
9693 source.source_accounted = true;
9694 self.staged_formula_index
9695 .consume_package_points(&package.sheet, points);
9696 if source.suppressed.len() >= source.source_coordinates.len() {
9697 staged.deferred_package = None;
9698 self.staged_formula_index.set_package(&package.sheet, None);
9699 }
9700 } else {
9701 let removed = self
9702 .staged_formulas
9703 .get_mut(&package.sheet)
9704 .and_then(|staged| staged.deferred_package.take());
9705 debug_assert!(removed.is_some());
9706 self.staged_formula_index.set_package(&package.sheet, None);
9707 }
9708 }
9709 for (sheet, (families, partitions)) in residual_sources {
9710 if let Some(source) = self
9711 .staged_formulas
9712 .get_mut(&sheet)
9713 .and_then(|staged| staged.deferred_package.as_mut())
9714 {
9715 source.families = families;
9716 source.partitioned_families = partitions;
9717 source
9718 .consumed_members
9719 .retain(|(owner, _)| residual_owners[&sheet].contains(owner));
9720 self.staged_formula_index.update_package_family_count(
9721 &sheet,
9722 source.families.len() + source.partitioned_families.len(),
9723 );
9724 }
9725 }
9726 let empty_sheets = self
9727 .staged_formulas
9728 .iter()
9729 .filter_map(|(sheet, staged)| staged.is_empty().then_some(sheet.clone()))
9730 .collect::<Vec<_>>();
9731 for sheet in empty_sheets {
9732 self.staged_formulas.remove(&sheet);
9733 }
9734 if committed > 0 {
9735 self.mark_topology_edited();
9736 }
9737 self.formula_parse_diagnostics.extend(pending_diagnostics);
9738 if !prepared.is_empty() || !prepared_packages.is_empty() {
9739 let mut ingest_delta = FormulaIngestReport::with_mode(FormulaPlaneMode::Off);
9740 ingest_delta.formula_cells_seen = (prepared.len() as u64).saturating_add(
9741 prepared_packages
9742 .iter()
9743 .map(|package| {
9744 (package.replay_records.len() as u64).saturating_add(
9745 if package.selected_points.is_some() {
9746 package.direct_cells
9747 } else {
9748 0
9749 },
9750 )
9751 })
9752 .sum::<u64>(),
9753 );
9754 ingest_delta.graph_formula_cells_materialized = committed as u64;
9755 ingest_delta.graph_vertices_created = new_vertices as u64;
9756 ingest_delta.graph_edges_created = new_edges as u64;
9757 for package in &prepared_packages {
9758 let source = &package.source_report;
9759 ingest_delta.source_formula_events = ingest_delta
9760 .source_formula_events
9761 .saturating_add(source.source_formula_events);
9762 ingest_delta.source_formula_records_spooled = ingest_delta
9763 .source_formula_records_spooled
9764 .saturating_add(source.source_formula_records_spooled);
9765 ingest_delta.source_spool_encoded_bytes = ingest_delta
9766 .source_spool_encoded_bytes
9767 .saturating_add(source.source_spool_encoded_bytes);
9768 ingest_delta.source_spool_peak_memory_bytes = ingest_delta
9769 .source_spool_peak_memory_bytes
9770 .max(source.source_spool_peak_memory_bytes);
9771 ingest_delta.source_spool_spilled_bytes = ingest_delta
9772 .source_spool_spilled_bytes
9773 .saturating_add(source.source_spool_spilled_bytes);
9774 ingest_delta.source_spool_spill_files = ingest_delta
9775 .source_spool_spill_files
9776 .saturating_add(source.source_spool_spill_files);
9777 ingest_delta.source_spool_replays = ingest_delta
9778 .source_spool_replays
9779 .saturating_add(source.source_spool_replays)
9780 .saturating_add(package.spool_replays);
9781 ingest_delta.source_families_seen = ingest_delta
9782 .source_families_seen
9783 .saturating_add(source.families_seen);
9784 ingest_delta.source_family_cells_seen = ingest_delta
9785 .source_family_cells_seen
9786 .saturating_add(source.family_cells_seen);
9787 ingest_delta.source_family_shadow_eligible = ingest_delta
9788 .source_family_shadow_eligible
9789 .saturating_add(source.source_clean_families);
9790 ingest_delta.source_family_shadow_eligible_cells = ingest_delta
9791 .source_family_shadow_eligible_cells
9792 .saturating_add(source.source_clean_cells);
9793 ingest_delta.source_partitioned_families_seen = ingest_delta
9794 .source_partitioned_families_seen
9795 .saturating_add(source.source_fragmentable_families);
9796 ingest_delta.source_partition_holes = ingest_delta
9797 .source_partition_holes
9798 .saturating_add(source.source_hole_exclusions);
9799 ingest_delta.source_partition_ordinary_exceptions = ingest_delta
9800 .source_partition_ordinary_exceptions
9801 .saturating_add(source.source_ordinary_exclusions);
9802 ingest_delta.source_partition_surviving_cells = ingest_delta
9803 .source_partition_surviving_cells
9804 .saturating_add(source.source_fragmentable_cells);
9805 for (reason, count) in &source.fallback_reasons {
9806 let total = ingest_delta
9807 .fallback_reasons
9808 .entry(reason.clone())
9809 .or_default();
9810 *total = total.saturating_add(*count);
9811 }
9812
9813 ingest_delta.source_family_fallback = ingest_delta
9814 .source_family_fallback
9815 .saturating_add(source.families_seen);
9816 ingest_delta.source_family_fallback_cells = ingest_delta
9817 .source_family_fallback_cells
9818 .saturating_add(source.family_cells_seen);
9819 }
9820 self.record_formula_ingest_report(ingest_delta);
9821 }
9822 let commit_window = commit_started.elapsed();
9823 let retained_cells = self
9824 .staged_formula_index
9825 .all_leases()
9826 .into_iter()
9827 .filter_map(|(sheet, lease)| {
9828 formualizer_common::RangeAddress::new(
9829 sheet, lease.row, lease.col, lease.row, lease.col,
9830 )
9831 .ok()
9832 })
9833 .collect::<Vec<_>>();
9834 let observed_scratch_bytes = self
9835 .active_resource_ledger
9836 .as_ref()
9837 .map_or(0, |ledger| ledger.snapshot().scratch_peak)
9838 .saturating_sub(ledger_at_start.map_or(0, |snapshot| snapshot.scratch_current));
9839 let committed_spans = 0u64;
9840 let selected_source_families = prepared_packages
9841 .iter()
9842 .filter(|package| package.selected_points.is_none())
9843 .map(|package| package.lease.family_count)
9844 .sum::<usize>()
9845 + prepared_packages
9846 .iter()
9847 .filter(|package| package.selected_points.is_some())
9848 .flat_map(|package| package.replay_records.iter())
9849 .filter_map(|record| record.partition_owner.or(record.family))
9850 .chain(
9851 prepared_packages
9852 .iter()
9853 .flat_map(|package| package.complete_selections.iter().copied()),
9854 )
9855 .collect::<BTreeSet<_>>()
9856 .len();
9857 let selected_staged_cells = prepared.len().saturating_add(
9858 prepared_packages
9859 .iter()
9860 .map(|package| {
9861 package
9862 .selected_points
9863 .as_ref()
9864 .map_or(package.replay_records.len(), BTreeSet::len)
9865 })
9866 .sum::<usize>(),
9867 );
9868 let actual_commit_work = (new_vertices as u64)
9869 .saturating_add(new_edges as u64)
9870 .saturating_add(committed as u64)
9871 .saturating_add(committed_spans)
9872 .saturating_add(prepared.len() as u64)
9873 .saturating_add(
9874 prepared_packages
9875 .iter()
9876 .map(|package| {
9877 package
9878 .selected_points
9879 .as_ref()
9880 .map_or(1, |points| points.len() as u64)
9881 })
9882 .sum::<u64>(),
9883 );
9884 let report = PreparedTargetGraphReport {
9885 request_id: request_id.unwrap_or_default(),
9886 requested_targets: targets.len(),
9887 normalized_regions: visited_regions.len(),
9888 normalized_target_list: normalized,
9889 selected_staged_cells,
9890 selected_source_families,
9891 retained_staged_cells: self.staged_formula_count(),
9892 selected_cells,
9893 retained_cells,
9894 widened_scope: scope,
9895 widening_reasons: reasons,
9896 revisions: assumptions,
9897 commit_window,
9898 estimated_scratch_bytes: scratch_bytes,
9899 observed_scratch_bytes,
9900 estimated_commit_work: (new_vertices as u64)
9901 .saturating_add(new_edges as u64)
9902 .saturating_add(prepared.len() as u64)
9903 .saturating_add(selected_package_records),
9904 actual_commit_work,
9905 outcome: PreparationOutcome::Prepared,
9906 };
9907 self.observe_target_preparation_report(&report);
9908 Ok(report)
9909 }
9910
9911 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9913 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
9914 engine.build_graph_all_unobserved()
9915 })
9916 }
9917
9918 fn build_graph_all_unobserved(&mut self) -> Result<(), formualizer_parse::ExcelError> {
9919 let selected = self.staged_formula_count();
9920 let started = crate::instant::FzInstant::now();
9921 self.resource_checkpoint(selected as u64)?;
9922 let scratch_bytes = (selected as u64).saturating_mul(256);
9923 let result = self.with_request_scratch(scratch_bytes, |engine| {
9924 let index_snapshot = engine.staged_formula_index.clone();
9925 let collected = std::mem::take(&mut engine.staged_formulas)
9926 .into_iter()
9927 .collect();
9928 engine.staged_formula_index.clear_all();
9929 engine.build_graph_from_staged_batches(collected, false, index_snapshot)
9930 });
9931 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9932 result
9933 }
9934
9935 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
9937 &mut self,
9938 sheets: I,
9939 ) -> Result<(), formualizer_parse::ExcelError> {
9940 let mut sheets = sheets.into_iter();
9941 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, move |engine| {
9942 let name_scratch = (sheets.size_hint().0 as u64).saturating_mul(64);
9943 engine.with_request_scratch(name_scratch, |engine| {
9944 let names = sheets.by_ref().map(str::to_string).collect::<Vec<_>>();
9947 engine.charge_bounded_work(names.len() as u64)?;
9948 engine.build_graph_for_sheet_names_unobserved(names)
9949 })
9950 })
9951 }
9952
9953 fn build_graph_for_sheet_names_unobserved(
9954 &mut self,
9955 sheets: Vec<String>,
9956 ) -> Result<(), formualizer_parse::ExcelError> {
9957 let started = crate::instant::FzInstant::now();
9958 let selected = sheets
9959 .iter()
9960 .filter_map(|sheet| self.staged_formulas.get(sheet))
9961 .map(StagedSheet::len)
9962 .sum::<usize>();
9963 self.resource_checkpoint(selected as u64)?;
9964 let scratch_bytes = (selected as u64).saturating_mul(256);
9965 self.reserve_request_scratch(scratch_bytes)?;
9966 let index_snapshot = self.staged_formula_index.clone();
9967 let mut collected = Vec::new();
9968 for sheet in sheets {
9969 if let Some(staged) = self.staged_formulas.remove(&sheet) {
9970 self.index_removed_staged_sheet(&sheet, &staged);
9971 collected.push((sheet, staged));
9972 }
9973 }
9974 let result = self.build_graph_from_staged_batches(collected, true, index_snapshot);
9975 self.release_request_scratch(scratch_bytes);
9976 self.observe_staged_preparation(selected, self.staged_formula_count(), started.elapsed());
9977 result
9978 }
9979
9980 fn build_graph_from_staged_batches(
9981 &mut self,
9982 collected: StagedFormulaBatches,
9983 share_parse_cache_across_sheets: bool,
9984 staged_index_snapshot: StagedFormulaIndex,
9985 ) -> Result<(), formualizer_parse::ExcelError> {
9986 if collected.is_empty() {
9987 return Ok(());
9988 }
9989 for (sheet, _) in &collected {
9990 let _ = self.add_sheet(sheet);
9991 }
9992
9993 let diagnostics_len = self.formula_parse_diagnostics.len();
9994 let mut collected = collected;
9995 let prepared = match self
9996 .prepare_staged_formula_batches(&mut collected, share_parse_cache_across_sheets)
9997 {
9998 Ok(prepared) => prepared,
9999 Err(error) => {
10000 self.formula_parse_diagnostics.truncate(diagnostics_len);
10001 for (sheet, staged) in collected {
10002 self.restore_staged_sheet(sheet, staged);
10003 }
10004 self.staged_formula_index = staged_index_snapshot;
10005 return Err(error);
10006 }
10007 };
10008 let (ordinary, compressed, direct, may_fail) = prepared;
10009
10010 let first_build = if self.deferred_build_can_be_first_load() {
10020 if let Err(error) = self.check_staged_formula_plans(
10021 ordinary
10022 .iter()
10023 .chain(compressed.iter().map(|(batch, _)| batch)),
10024 &may_fail,
10025 ) {
10026 self.formula_parse_diagnostics.truncate(diagnostics_len);
10027 for (sheet, staged) in collected {
10028 self.restore_staged_sheet(sheet, staged);
10029 }
10030 self.staged_formula_index = staged_index_snapshot;
10031 return Err(error);
10032 }
10033 self.deferred_build_as_first_load()
10034 } else {
10035 None
10036 };
10037 let built_sheets: Vec<String> = if first_build.is_some() {
10038 collected.iter().map(|(sheet, _)| sheet.clone()).collect()
10039 } else {
10040 Vec::new()
10041 };
10042
10043 let result = (|| {
10047 if !ordinary.is_empty() {
10048 self.ingest_formula_batches(ordinary)?;
10049 }
10050 if !compressed.is_empty() {
10051 self.ingest_compressed_formula_source_batches(compressed)?;
10052 }
10053 if !direct.is_empty() {
10054 self.finish_compressed_formula_sources(direct)?;
10055 }
10056 Ok(())
10057 })();
10058 if let Some(saved) = first_build {
10059 self.leave_deferred_first_load(saved, &built_sheets);
10060 }
10061 if let Err(error) = result {
10062 self.formula_parse_diagnostics.truncate(diagnostics_len);
10063 for (sheet, staged) in collected {
10064 self.restore_staged_sheet(sheet, staged);
10065 }
10066 self.staged_formula_index = staged_index_snapshot;
10067 return Err(error);
10068 }
10069 self.dedup_formula_parse_diagnostics_since(diagnostics_len);
10070 Ok(())
10071 }
10072
10073 fn deferred_build_can_be_first_load(&self) -> bool {
10079 !self.graph.first_load_assume_new()
10080 && !self.graph_admission_enabled()
10081 && self.graph.formula_vertex_count() == 0
10082 }
10083
10084 fn check_staged_formula_plans<'b>(
10089 &mut self,
10090 batches: impl Iterator<Item = &'b FormulaIngestBatch>,
10091 may_fail: &FxHashSet<crate::engine::arena::AstNodeId>,
10092 ) -> Result<(), ExcelError> {
10093 if may_fail.is_empty() {
10094 return Ok(());
10095 }
10096 let mut seen: FxHashSet<(SheetId, crate::engine::arena::AstNodeId)> = FxHashSet::default();
10097 for batch in batches {
10098 let sheet_id = self.graph.sheet_id(&batch.sheet_name).ok_or_else(|| {
10099 ExcelError::new(ExcelErrorKind::Ref)
10100 .with_message(format!("unknown ingest sheet: {}", batch.sheet_name))
10101 })?;
10102 let mut pipeline = self.ingest_pipeline();
10103 for record in &batch.formulas {
10104 if record.member_anchor.is_some()
10105 || !may_fail.contains(&record.ast_id)
10106 || !seen.insert((sheet_id, record.ast_id))
10107 {
10108 continue;
10109 }
10110 let placement = CellRef::new(
10111 sheet_id,
10112 Coord::from_excel(record.row, record.col, true, true),
10113 );
10114 pipeline.ingest_formula(
10115 FormulaAstInput::RawArena(record.ast_id),
10116 placement,
10117 None,
10118 )?;
10119 }
10120 }
10121 Ok(())
10122 }
10123
10124 fn deferred_build_as_first_load(&mut self) -> Option<(crate::engine::SheetIndexMode, usize)> {
10125 if !self.deferred_build_can_be_first_load() {
10126 return None;
10127 }
10128 let saved = (
10129 self.graph.get_config().sheet_index_mode,
10130 self.config.range_expansion_limit,
10131 );
10132 self.graph
10133 .set_sheet_index_mode(crate::engine::SheetIndexMode::Lazy);
10134 self.config.range_expansion_limit = 0;
10135 self.graph.set_first_load_assume_new(true);
10136 self.graph.reset_ensure_touched();
10137 Some(saved)
10138 }
10139
10140 fn leave_deferred_first_load(
10143 &mut self,
10144 (index_mode, range_limit): (crate::engine::SheetIndexMode, usize),
10145 sheets: &[String],
10146 ) {
10147 self.graph.set_first_load_assume_new(false);
10148 self.graph.reset_ensure_touched();
10149 self.graph.set_sheet_index_mode(index_mode);
10150 self.config.range_expansion_limit = range_limit;
10151 for sheet in sheets {
10152 self.graph.finalize_sheet_index(sheet);
10153 }
10154 }
10155
10156 fn prepare_staged_formula_batches(
10157 &mut self,
10158 collected: &mut StagedFormulaBatches,
10159 share_parse_cache_across_sheets: bool,
10160 ) -> Result<PreparedStagedFormulaBatches, formualizer_parse::ExcelError> {
10161 let mut ordinary = Vec::new();
10162 let mut compressed = Vec::new();
10163 let mut direct = Vec::new();
10164 let mut may_fail = FxHashSet::default();
10165 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
10166 rustc_hash::FxHashMap::default();
10167 cache.reserve(4096);
10168
10169 for (sheet, staged) in collected {
10170 if !share_parse_cache_across_sheets {
10171 cache.clear();
10172 }
10173 let mut grouper = crate::engine::FormulaFamilyGrouper::new();
10177 let mut replayed_records = Vec::new();
10181 let deferred_source = None;
10182 let mut deferred_fallback = None;
10183 if let Some(package) = staged.deferred_package.as_mut() {
10184 if package.sheet_name != *sheet {
10185 return Err(ExcelError::new(ExcelErrorKind::Value)
10186 .with_message("deferred formula package sheet mismatch"));
10187 }
10188 let eligible: Vec<_> = package
10189 .families
10190 .iter()
10191 .filter(|family| !package.invalidated.contains(&family.source_id))
10192 .cloned()
10193 .collect();
10194 let eligible_partitions: Vec<_> = package
10195 .partitioned_families
10196 .iter()
10197 .filter(|family| !package.invalidated.contains(&family.source_id))
10198 .cloned()
10199 .collect();
10200
10201 let mut replay_disposition = crate::engine::FormulaReplayDisposition::default();
10202 for partition in &eligible_partitions {
10203 replay_disposition
10204 .register_partition(partition, false)
10205 .map_err(|reason| {
10206 ExcelError::new(ExcelErrorKind::Value).with_message(reason)
10207 })?;
10208 }
10209 replay_disposition
10210 .extend_suppressed_excel_coords(package.suppressed.iter().copied());
10211 let replayed = package
10212 .replay
10213 .lock()
10214 .map_err(|_| {
10215 ExcelError::new(ExcelErrorKind::Value)
10216 .with_message("deferred formula spool lock poisoned")
10217 })?
10218 .replay(&replay_disposition)
10219 .map_err(|message| {
10220 ExcelError::new(ExcelErrorKind::Value).with_message(message)
10221 })?;
10222 replayed_records = replayed;
10223 let mut report = package.accounting_report();
10224 report.source_spool_replays = report.source_spool_replays.saturating_add(1);
10225 deferred_fallback = Some((report, package.families.clone(), eligible_partitions));
10226 }
10227
10228 let n_entries = staged.entries.len() + replayed_records.len();
10229 let entries = staged
10230 .entries
10231 .iter()
10232 .cloned()
10233 .map(|(row, col, text)| (row, col, text, None))
10234 .chain(replayed_records.into_iter().map(|record| {
10235 (
10236 record.row,
10237 record.col,
10238 record.text,
10239 Some((record.source_order, record.family, record.partition_owner)),
10240 )
10241 }));
10242 let mut formulas = Vec::with_capacity(n_entries);
10243 let staged_order_base = u64::MAX.saturating_sub(n_entries as u64);
10244 for (entry_index, (row, col, txt, source_proof)) in entries.enumerate() {
10245 let key = if txt.starts_with('=') {
10246 txt
10247 } else {
10248 format!("={txt}")
10249 };
10250 let staged_record = if let Some(cached) = cache.get(&key) {
10251 cached.map(|ast_id| {
10252 self.note_staged_formula(&mut grouper, row, col, ast_id);
10253 FormulaIngestRecord::new(row, col, ast_id, Some(Arc::<str>::from(key)))
10254 })
10255 } else {
10256 let parsed = match formualizer_parse::parser::parse(&key) {
10257 Ok(parsed) => Some(parsed),
10258 Err(error) => self.handle_formula_parse_error(
10259 sheet,
10260 row,
10261 col,
10262 &key,
10263 error.to_string(),
10264 )?,
10265 };
10266 match parsed {
10267 Some(ast) => {
10268 let record = self.stage_formula_ast(&mut grouper, row, col, &ast, None);
10269 if !record.is_family_member() && self.formula_may_fail_planning(&ast) {
10270 may_fail.insert(record.ast_id);
10271 }
10272 if record.is_family_member() {
10275 Some(record)
10276 } else {
10277 let ast_id = record.ast_id;
10278 cache.insert(key.clone(), Some(ast_id));
10279 Some(FormulaIngestRecord::new(
10280 row,
10281 col,
10282 ast_id,
10283 Some(Arc::<str>::from(key)),
10284 ))
10285 }
10286 }
10287 None => {
10288 cache.insert(key, None);
10289 None
10290 }
10291 }
10292 };
10293
10294 if let Some(mut formula) = staged_record {
10295 if let Some((order, family, owner)) = source_proof {
10296 formula = formula.with_source_proof(order, family, owner);
10297 } else if deferred_source.is_some() {
10298 formula = formula.with_source_proof(
10299 crate::engine::SourceFormulaOrder::new(
10300 staged_order_base.saturating_add(entry_index as u64),
10301 ),
10302 None,
10303 None,
10304 );
10305 }
10306 formulas.push(formula);
10307 }
10308 }
10309
10310 let batch = FormulaIngestBatch::new(sheet.clone(), formulas);
10311 if let Some((report, preparation)) = deferred_source {
10312 direct.push((batch, report, preparation));
10313 } else if let Some((report, families, partitions)) = deferred_fallback {
10314 let source_batch = crate::engine::FormulaCompressedSourceBatch::with_proposals(
10315 batch.sheet_name.clone(),
10316 report,
10317 families,
10318 partitions,
10319 );
10320 compressed.push((batch, source_batch));
10321 } else if !batch.is_empty() {
10322 ordinary.push(batch);
10323 }
10324 }
10325 Ok((ordinary, compressed, direct, may_fail))
10326 }
10327
10328 fn formula_may_fail_planning(&self, ast: &formualizer_parse::parser::ASTNode) -> bool {
10333 use formualizer_parse::parser::{ASTNodeType, ReferenceType};
10334 let sheet_missing = |sheet: &Option<String>| {
10335 sheet
10336 .as_deref()
10337 .is_some_and(|s| self.graph.sheet_id(s).is_none())
10338 };
10339 match &ast.node_type {
10340 ASTNodeType::Literal(_) | ASTNodeType::Omitted => false,
10341 ASTNodeType::Reference { reference, .. } => match reference {
10342 ReferenceType::Cell { sheet, .. } => sheet_missing(sheet),
10343 ReferenceType::Range {
10344 sheet,
10345 start_row,
10346 start_col,
10347 end_row,
10348 end_col,
10349 ..
10350 } => {
10351 sheet_missing(sheet)
10352 || matches!((start_row, end_row), (Some(a), Some(b)) if a > b)
10353 || matches!((start_col, end_col), (Some(a), Some(b)) if a > b)
10354 }
10355 ReferenceType::NamedRange(_) => false,
10356 _ => true,
10357 },
10358 ASTNodeType::UnaryOp { expr, .. } => self.formula_may_fail_planning(expr),
10359 ASTNodeType::BinaryOp { left, right, .. } => {
10360 self.formula_may_fail_planning(left) || self.formula_may_fail_planning(right)
10361 }
10362 ASTNodeType::Function { args, .. } => {
10363 args.iter().any(|a| self.formula_may_fail_planning(a))
10364 }
10365 ASTNodeType::Call { callee, args } => {
10366 self.formula_may_fail_planning(callee)
10367 || args.iter().any(|a| self.formula_may_fail_planning(a))
10368 }
10369 ASTNodeType::Array(rows) => rows
10370 .iter()
10371 .flatten()
10372 .any(|a| self.formula_may_fail_planning(a)),
10373 }
10374 }
10375
10376 pub fn begin_bulk_ingest_arrow(
10378 &mut self,
10379 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
10380 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
10381 }
10382
10383 pub fn begin_bulk_update_arrow(
10385 &mut self,
10386 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
10387 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
10388 }
10389
10390 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
10391 self.graph.sheet_id(sheet).ok_or(
10392 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
10393 name: sheet.to_string(),
10394 reason: "Unknown sheet".to_string(),
10395 },
10396 )
10397 }
10398
10399 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
10400 if row_1based == 0 {
10401 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10402 }
10403 Ok(row_1based - 1)
10404 }
10405
10406 fn normalize_row_range_1based(
10407 start_row_1based: u32,
10408 end_row_1based: u32,
10409 ) -> Result<(u32, u32), crate::engine::EditorError> {
10410 if start_row_1based == 0 || end_row_1based == 0 {
10411 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
10412 }
10413 if start_row_1based > end_row_1based {
10414 return Err(crate::engine::EditorError::TransactionFailed {
10415 reason: "Row range start is greater than end".to_string(),
10416 });
10417 }
10418 Ok((start_row_1based - 1, end_row_1based - 1))
10419 }
10420
10421 fn invalidate_row_visibility_mask_cache(&self) {
10422 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10423 cache.clear();
10424 }
10425 }
10426
10427 fn set_row_hidden_by_sheet_id(
10428 &mut self,
10429 sheet_id: SheetId,
10430 row0: u32,
10431 hidden: bool,
10432 source: RowVisibilitySource,
10433 ) -> bool {
10434 let changed = {
10435 let state = self.row_visibility.entry(sheet_id).or_default();
10436 state.set_row_hidden(row0, hidden, source)
10437 };
10438
10439 let remove_entry = self
10440 .row_visibility
10441 .get(&sheet_id)
10442 .map(|state| state.is_empty())
10443 .unwrap_or(false);
10444 if remove_entry {
10445 self.row_visibility.remove(&sheet_id);
10446 }
10447
10448 if changed {
10449 self.invalidate_row_visibility_mask_cache();
10450 }
10451
10452 changed
10453 }
10454
10455 fn set_rows_hidden_by_sheet_id(
10456 &mut self,
10457 sheet_id: SheetId,
10458 start_row0: u32,
10459 end_row0: u32,
10460 hidden: bool,
10461 source: RowVisibilitySource,
10462 ) -> bool {
10463 let changed = {
10464 let state = self.row_visibility.entry(sheet_id).or_default();
10465 state.set_rows_hidden(start_row0, end_row0, hidden, source)
10466 };
10467
10468 let remove_entry = self
10469 .row_visibility
10470 .get(&sheet_id)
10471 .map(|state| state.is_empty())
10472 .unwrap_or(false);
10473 if remove_entry {
10474 self.row_visibility.remove(&sheet_id);
10475 }
10476
10477 if changed {
10478 self.invalidate_row_visibility_mask_cache();
10479 }
10480
10481 changed
10482 }
10483
10484 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
10485 if count == 0 {
10486 return;
10487 }
10488 let mut changed = false;
10489 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10490 changed = state.insert_rows(before0, count);
10491 state.is_empty()
10492 } else {
10493 false
10494 };
10495 if remove_entry {
10496 self.row_visibility.remove(&sheet_id);
10497 }
10498 if changed {
10499 self.invalidate_row_visibility_mask_cache();
10500 }
10501 }
10502
10503 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
10504 if count == 0 {
10505 return;
10506 }
10507 let mut changed = false;
10508 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
10509 changed = state.delete_rows(start0, count);
10510 state.is_empty()
10511 } else {
10512 false
10513 };
10514 if remove_entry {
10515 self.row_visibility.remove(&sheet_id);
10516 }
10517 if changed {
10518 self.invalidate_row_visibility_mask_cache();
10519 }
10520 }
10521
10522 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10523 if let crate::engine::ChangeEvent::SetRowVisibility {
10524 sheet_id,
10525 row0,
10526 source,
10527 old_hidden,
10528 ..
10529 } = event
10530 {
10531 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
10532 }
10533 }
10534
10535 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
10536 if let crate::engine::ChangeEvent::SetRowVisibility {
10537 sheet_id,
10538 row0,
10539 source,
10540 new_hidden,
10541 ..
10542 } = event
10543 {
10544 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
10545 }
10546 }
10547
10548 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10549 for event in events.iter().rev() {
10550 self.apply_inverse_row_visibility_event(event);
10551 }
10552 }
10553
10554 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
10555 for event in events {
10556 self.apply_forward_row_visibility_event(event);
10557 }
10558 }
10559
10560 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10561 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10562 sheet,
10563 row,
10564 col,
10565 old,
10566 ..
10567 } = event
10568 {
10569 self.apply_staged_formula_cell(sheet, *row, *col, old.as_deref());
10570 }
10571 }
10572
10573 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
10574 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
10575 sheet,
10576 row,
10577 col,
10578 new,
10579 ..
10580 } = event
10581 {
10582 self.apply_staged_formula_cell(sheet, *row, *col, new.as_deref());
10583 }
10584 }
10585
10586 fn apply_staged_formula_cell(&mut self, sheet: &str, row: u32, col: u32, target: Option<&str>) {
10589 match target {
10590 Some(text) => self.stage_formula_text(sheet, row, col, text.to_string()),
10591 None => {
10592 self.clear_staged_formula_text(sheet, row, col);
10593 }
10594 }
10595 }
10596
10597 pub fn set_row_hidden(
10598 &mut self,
10599 sheet: &str,
10600 row_1based: u32,
10601 hidden: bool,
10602 source: RowVisibilitySource,
10603 ) -> Result<(), crate::engine::EditorError> {
10604 self.observe_function_semantic_epoch()
10605 .map_err(crate::engine::EditorError::Excel)?;
10606 self.observe_function_semantic_epoch()
10607 .map_err(crate::engine::EditorError::Excel)?;
10608 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10609 let row0 = Self::normalize_row_1based(row_1based)?;
10610 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
10611 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10612 sheet_id, row0,
10613 )));
10614 self.mark_data_edited();
10615 }
10616 Ok(())
10617 }
10618
10619 pub fn set_rows_hidden(
10620 &mut self,
10621 sheet: &str,
10622 start_row_1based: u32,
10623 end_row_1based: u32,
10624 hidden: bool,
10625 source: RowVisibilitySource,
10626 ) -> Result<(), crate::engine::EditorError> {
10627 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10628 let (start_row0, end_row0) =
10629 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
10630 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
10631 if start_row0 == end_row0 {
10632 self.record_structural_change(StructuralScope::Region(Region::whole_row(
10633 sheet_id, start_row0,
10634 )));
10635 } else {
10636 self.record_structural_change(StructuralScope::Sheet(sheet_id));
10637 }
10638 self.mark_data_edited();
10639 }
10640 Ok(())
10641 }
10642
10643 pub fn is_row_hidden(
10644 &self,
10645 sheet: &str,
10646 row_1based: u32,
10647 source: Option<RowVisibilitySource>,
10648 ) -> Option<bool> {
10649 let sheet_id = self.graph.sheet_id(sheet)?;
10650 let row0 = row_1based.checked_sub(1)?;
10651 Some(
10652 self.row_visibility
10653 .get(&sheet_id)
10654 .map(|state| state.is_row_hidden(row0, source))
10655 .unwrap_or(false),
10656 )
10657 }
10658
10659 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
10660 let sheet_id = self.graph.sheet_id(sheet)?;
10661 Some(
10662 self.row_visibility
10663 .get(&sheet_id)
10664 .map(|state| state.version())
10665 .unwrap_or(0),
10666 )
10667 }
10668
10669 fn nested_subtotal_cells_for_view(
10675 &self,
10676 view: &RangeView<'_>,
10677 include_aggregate: bool,
10678 ) -> Option<Vec<(usize, usize, usize)>> {
10679 if !view.is_sheet_backed() {
10680 return None;
10681 }
10682 let (rows, cols) = view.dims();
10683 if rows == 0 || cols == 0 {
10684 return Some(Vec::new());
10685 }
10686 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
10687 let (r0, c0) = (view.start_row(), view.start_col());
10688 let mask = if include_aggregate {
10689 crate::engine::arena::SUBTOTAL_CALL | crate::engine::arena::AGGREGATE_CALL
10690 } else {
10691 crate::engine::arena::SUBTOTAL_CALL
10692 };
10693 let ds = self.graph.data_store();
10694 let mut found: Vec<(usize, usize, usize)> = self
10695 .graph
10696 .formula_intervals_in_region(
10697 sheet_id,
10698 r0 as u32,
10699 (r0 + rows - 1) as u32,
10700 c0 as u32,
10701 (c0 + cols - 1) as u32,
10702 |template| ds.ast_subtotal_calls(template) & mask != 0,
10703 )
10704 .into_iter()
10705 .map(|(col, lo, hi)| (col as usize - c0, lo as usize - r0, hi as usize - r0))
10706 .collect();
10707 found.sort_unstable();
10708 let mut out: Vec<(usize, usize, usize)> = Vec::with_capacity(found.len());
10711 for (col, lo, hi) in found {
10712 match out.last_mut() {
10713 Some(last) if last.0 == col && lo <= last.2 + 1 => last.2 = last.2.max(hi),
10714 _ => out.push((col, lo, hi)),
10715 }
10716 }
10717 Some(out)
10718 }
10719
10720 fn build_row_visibility_mask_for_view(
10721 &self,
10722 view: &RangeView<'_>,
10723 mode: VisibilityMaskMode,
10724 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
10725 let sheet_rows = view.sheet().nrows as usize;
10726 if sheet_rows == 0 || view.start_row() >= sheet_rows {
10727 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
10728 }
10729
10730 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
10731 let start_row0 = view.start_row() as u32;
10732 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
10733 let version = self
10734 .row_visibility
10735 .get(&sheet_id)
10736 .map(|state| state.version())
10737 .unwrap_or(0);
10738 let key = VisibilityMaskCacheKey {
10739 sheet_id,
10740 start_row0,
10741 end_row0,
10742 mode,
10743 version,
10744 };
10745
10746 if let Ok(cache) = self.row_visibility_mask_cache.read()
10747 && let Some(mask) = cache.get(&key)
10748 {
10749 #[cfg(test)]
10750 visibility_mask_test_hooks::inc_hit();
10751 return Some(mask.clone());
10752 }
10753
10754 #[cfg(test)]
10755 visibility_mask_test_hooks::inc_miss();
10756
10757 let state = self.row_visibility.get(&sheet_id);
10758 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
10759 for row0 in start_row0..=end_row0 {
10760 let manual_hidden = state
10761 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
10762 .unwrap_or(false);
10763 let filter_hidden = state
10764 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
10765 .unwrap_or(false);
10766
10767 let include = match mode {
10768 VisibilityMaskMode::IncludeAll => true,
10769 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
10770 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
10771 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
10772 !(manual_hidden || filter_hidden)
10773 }
10774 };
10775 out.push(include);
10776 }
10777
10778 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
10779 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
10780 const MAX_CACHE_ENTRIES: usize = 4096;
10781 if cache.len() >= MAX_CACHE_ENTRIES {
10782 cache.clear();
10783 #[cfg(test)]
10784 visibility_mask_test_hooks::inc_eviction();
10785 }
10786 cache.insert(key, mask.clone());
10787 }
10788
10789 Some(mask)
10790 }
10791
10792 fn observe_function_semantic_epoch(&mut self) -> Result<bool, ExcelError> {
10793 let changes =
10794 crate::function_registry::semantic_changes_since(self.function_semantic_epoch_seen);
10795 let global_changed = changes.epoch != self.function_semantic_epoch_seen;
10796 let provider_revision = self.resolver.planning_semantic_revision();
10797 let provider_changed = provider_revision != self.function_provider_revision_seen;
10798 if !global_changed && !provider_changed {
10799 return Ok(false);
10800 }
10801
10802 let changed = !changes.keys.is_empty();
10803 if global_changed && changed || provider_changed {
10804 self.cached_static_schedule = None;
10805 self.recent_schedules.clear();
10806 self.base_schedule = None;
10807 }
10808 self.function_semantic_epoch_seen = changes.epoch;
10809 self.function_provider_revision_seen = provider_revision;
10810 Ok(false)
10811 }
10812
10813 pub(crate) fn ast_uses_changed_function(
10814 ast: &ASTNode,
10815 changed: &BTreeSet<(String, String)>,
10816 ) -> bool {
10817 match &ast.node_type {
10818 ASTNodeType::Function { name, args } => {
10819 let normalized = name.to_uppercase();
10820 let mut spellings = vec![(String::new(), normalized.clone())];
10821 let mut stripped = normalized.as_str();
10822 while let Some(rest) = ["_XLFN.", "_XLL.", "_XLWS."]
10823 .iter()
10824 .find_map(|prefix| stripped.strip_prefix(prefix))
10825 {
10826 stripped = rest;
10827 spellings.push((String::new(), stripped.to_string()));
10828 }
10829 let resolved = crate::function_registry::resolve("", name);
10830 let directly_changed = spellings.iter().any(|spelling| changed.contains(spelling))
10831 || resolved.as_ref().is_some_and(|resolved| {
10832 changed.contains(&(
10833 resolved.namespace.clone(),
10834 resolved.canonical_name.clone(),
10835 ))
10836 });
10837 directly_changed
10838 || resolved.is_none()
10839 || args
10840 .iter()
10841 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10842 }
10843 ASTNodeType::Call { callee, args } => {
10844 Self::ast_uses_changed_function(callee, changed)
10845 || args
10846 .iter()
10847 .any(|arg| Self::ast_uses_changed_function(arg, changed))
10848 }
10849 ASTNodeType::UnaryOp { expr, .. } => Self::ast_uses_changed_function(expr, changed),
10850 ASTNodeType::BinaryOp { left, right, .. } => {
10851 Self::ast_uses_changed_function(left, changed)
10852 || Self::ast_uses_changed_function(right, changed)
10853 }
10854 ASTNodeType::Array(rows) => rows
10855 .iter()
10856 .flatten()
10857 .any(|node| Self::ast_uses_changed_function(node, changed)),
10858 _ => false,
10859 }
10860 }
10861
10862 fn ast_contains_function(ast: &ASTNode) -> bool {
10863 match &ast.node_type {
10864 ASTNodeType::Function { .. } => true,
10865 ASTNodeType::Call { callee, args } => {
10866 Self::ast_contains_function(callee) || args.iter().any(Self::ast_contains_function)
10867 }
10868 ASTNodeType::UnaryOp { expr, .. } => Self::ast_contains_function(expr),
10869 ASTNodeType::BinaryOp { left, right, .. } => {
10870 Self::ast_contains_function(left) || Self::ast_contains_function(right)
10871 }
10872 ASTNodeType::Array(rows) => rows.iter().flatten().any(Self::ast_contains_function),
10873 ASTNodeType::Literal(_) | ASTNodeType::Omitted | ASTNodeType::Reference { .. } => false,
10874 }
10875 }
10876
10877 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
10878 Region::rows_from(sheet_id, start_row0)
10879 }
10880
10881 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
10882 Region::cols_from(sheet_id, start_col0)
10883 }
10884
10885 #[cfg(test)]
10886 pub(crate) fn force_non_cycle_schedule_fallback_for_test(&mut self) {
10887 self.force_non_cycle_schedule_fallback_for_test = true;
10888 }
10889
10890 fn materialize_deferred_sheet_before_structural_edit(
10891 &mut self,
10892 sheet: &str,
10893 ) -> Result<(), crate::engine::EditorError> {
10894 if self.staged_formulas.contains_key(sheet) {
10895 self.build_graph_for_sheets([sheet])?;
10896 }
10897 Ok(())
10898 }
10899
10900 fn structural_row_occupancy(
10901 &self,
10902 sheet: &str,
10903 sheet_id: SheetId,
10904 ) -> crate::engine::graph::StructuralOccupancy {
10905 if !self.graph.has_compressed_range_readers() {
10906 return crate::engine::graph::StructuralOccupancy::default();
10907 }
10908 let mut occupancy = self.graph.structural_occupancy(sheet_id);
10909 if let Some(arrow_sheet) = self.arrow_sheets.sheet(sheet) {
10910 occupancy.include_arrow_sheet(arrow_sheet);
10911 occupancy
10912 } else {
10913 crate::engine::graph::StructuralOccupancy::conservative()
10915 }
10916 }
10917
10918 fn structural_column_occupancy(&self) -> crate::engine::graph::StructuralOccupancy {
10919 crate::engine::graph::StructuralOccupancy::conservative()
10923 }
10924
10925 pub fn insert_rows(
10927 &mut self,
10928 sheet: &str,
10929 before: u32,
10930 count: u32,
10931 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10932 {
10933 if count == 0 {
10934 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10935 }
10936 self.observe_function_semantic_epoch()
10937 .map_err(crate::engine::EditorError::Excel)?;
10938 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10939 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10940 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10941 let before0 = before.saturating_sub(1);
10942 let affected_region = Self::structural_row_region(sheet_id, before0);
10943 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10944 let summary = {
10945 let mut editor =
10946 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10947 editor.insert_rows(sheet_id, before0, count)?
10948 };
10949 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10950 let before0 = before0 as usize;
10951 asheet.insert_rows(before0, count as usize);
10952 }
10953 self.purge_derived_formats_after_row(sheet_id, before0);
10954 self.mark_moved_formula_vertices_dirty(&summary);
10955 self.clear_computed_overlay_after_row(sheet, before0 as usize);
10956 self.shift_row_visibility_insert(sheet_id, before0, count);
10957 self.record_structural_change(StructuralScope::Region(affected_region));
10958 self.mark_topology_edited();
10959 Ok(summary)
10960 }
10961
10962 pub fn delete_rows(
10964 &mut self,
10965 sheet: &str,
10966 start: u32,
10967 count: u32,
10968 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
10969 {
10970 if count == 0 {
10971 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
10972 }
10973 self.observe_function_semantic_epoch()
10974 .map_err(crate::engine::EditorError::Excel)?;
10975 use crate::engine::graph::editor::vertex_editor::VertexEditor;
10976 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
10977 let sheet_id = self.ensure_known_sheet_id(sheet)?;
10978 let start0 = start.saturating_sub(1);
10979 let affected_region = Self::structural_row_region(sheet_id, start0);
10980 let occupancy = self.structural_row_occupancy(sheet, sheet_id);
10981 let summary = {
10982 let mut editor =
10983 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
10984 editor.delete_rows(sheet_id, start0, count)?
10985 };
10986 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
10987 let start0 = start0 as usize;
10988 asheet.delete_rows(start0, count as usize);
10989 }
10990 self.purge_derived_formats_after_row(sheet_id, start0);
10991 self.mark_moved_formula_vertices_dirty(&summary);
10992 self.clear_computed_overlay_after_row(sheet, start0 as usize);
10993 self.shift_row_visibility_delete(sheet_id, start0, count);
10994 self.record_structural_change(StructuralScope::Region(affected_region));
10995 self.mark_topology_edited();
10996 Ok(summary)
10997 }
10998
10999 pub fn insert_columns(
11001 &mut self,
11002 sheet: &str,
11003 before: u32,
11004 count: u32,
11005 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
11006 {
11007 if count == 0 {
11008 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
11009 }
11010 self.observe_function_semantic_epoch()
11011 .map_err(crate::engine::EditorError::Excel)?;
11012 use crate::engine::graph::editor::vertex_editor::VertexEditor;
11013 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
11014 let sheet_id = self.graph.sheet_id(sheet).ok_or(
11015 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
11016 name: sheet.to_string(),
11017 reason: "Unknown sheet".to_string(),
11018 },
11019 )?;
11020 let before0 = before.saturating_sub(1);
11021 let affected_region = Self::structural_col_region(sheet_id, before0);
11022 let occupancy = self.structural_column_occupancy();
11023 let summary = {
11024 let mut editor =
11025 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
11026 editor.insert_columns(sheet_id, before0, count)?
11027 };
11028 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
11029 let before0 = before0 as usize;
11030 asheet.insert_columns(before0, count as usize);
11031 }
11032 self.purge_derived_formats_after_col(sheet_id, before0);
11033 self.mark_moved_formula_vertices_dirty(&summary);
11034 self.clear_computed_overlay_after_col(sheet, before0 as usize);
11035 self.record_structural_change(StructuralScope::Region(affected_region));
11036 self.mark_topology_edited();
11037 Ok(summary)
11038 }
11039
11040 pub fn delete_columns(
11042 &mut self,
11043 sheet: &str,
11044 start: u32,
11045 count: u32,
11046 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
11047 {
11048 if count == 0 {
11049 return Ok(crate::engine::graph::editor::vertex_editor::ShiftSummary::default());
11050 }
11051 self.observe_function_semantic_epoch()
11052 .map_err(crate::engine::EditorError::Excel)?;
11053 use crate::engine::graph::editor::vertex_editor::VertexEditor;
11054 self.materialize_deferred_sheet_before_structural_edit(sheet)?;
11055 let sheet_id = self.graph.sheet_id(sheet).ok_or(
11056 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
11057 name: sheet.to_string(),
11058 reason: "Unknown sheet".to_string(),
11059 },
11060 )?;
11061 let start0 = start.saturating_sub(1);
11062 let affected_region = Self::structural_col_region(sheet_id, start0);
11063 let occupancy = self.structural_column_occupancy();
11064 let summary = {
11065 let mut editor =
11066 VertexEditor::new(&mut self.graph).with_structural_occupancy(occupancy);
11067 editor.delete_columns(sheet_id, start0, count)?
11068 };
11069 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
11070 let start0 = start0 as usize;
11071 asheet.delete_columns(start0, count as usize);
11072 }
11073 self.purge_derived_formats_after_col(sheet_id, start0);
11074 self.mark_moved_formula_vertices_dirty(&summary);
11075 self.clear_computed_overlay_after_col(sheet, start0 as usize);
11076 self.record_structural_change(StructuralScope::Region(affected_region));
11077 self.mark_topology_edited();
11078 Ok(summary)
11079 }
11080 fn arrow_used_row_bounds(
11082 &self,
11083 sheet: &str,
11084 start_col: u32,
11085 end_col: u32,
11086 ) -> Option<(u32, u32)> {
11087 let a = self.sheet_store().sheet(sheet)?;
11088 if a.columns.is_empty() {
11089 return None;
11090 }
11091 let sc0 = start_col.saturating_sub(1) as usize;
11092 let ec0 = end_col.saturating_sub(1) as usize;
11093 let col_hi = a.columns.len().saturating_sub(1);
11094 if sc0 > col_hi {
11095 return None;
11096 }
11097 let ec0 = ec0.min(col_hi);
11098 let snap = self.data_snapshot_id();
11100 let mut min_r0: Option<usize> = None;
11101 for ci in sc0..=ec0 {
11102 let sheet_id = self.graph.sheet_id(sheet)?;
11103 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
11104 g.as_ref()
11105 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
11106 }) {
11107 let mv = mv as usize;
11108 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
11109 continue;
11110 }
11111 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
11113 if let Ok(mut g) = self.row_bounds_cache.write() {
11114 g.get_or_insert_with(|| RowBoundsCache::new(snap))
11115 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
11116 }
11117 if let Some(m) = min_c {
11118 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
11119 }
11120 }
11121 min_r0?;
11122 let mut max_r0: Option<usize> = None;
11123 for ci in sc0..=ec0 {
11124 let sheet_id = self.graph.sheet_id(sheet)?;
11125 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
11126 g.as_ref()
11127 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
11128 }) {
11129 let mv = mv as usize;
11130 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
11131 continue;
11132 }
11133 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
11134 if let Ok(mut g) = self.row_bounds_cache.write() {
11135 g.get_or_insert_with(|| RowBoundsCache::new(snap))
11136 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
11137 }
11138 if let Some(m) = max_c {
11139 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
11140 }
11141 }
11142 match (min_r0, max_r0) {
11143 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
11144 _ => None,
11145 }
11146 }
11147
11148 fn scan_column_used_bounds(
11149 a: &crate::arrow_store::ArrowSheet,
11150 ci: usize,
11151 ) -> (Option<u32>, Option<u32>) {
11152 let col = &a.columns[ci];
11153
11154 let mut min_r0: Option<u32> = None;
11156 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
11157 let tags = chunk.type_tag.values();
11158 for (off, &t) in tags.iter().enumerate() {
11159 let overlay_non_empty = chunk
11160 .overlay
11161 .get(off)
11162 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11163 .unwrap_or(false)
11164 || chunk
11165 .computed_overlay
11166 .get(off)
11167 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11168 .unwrap_or(false);
11169 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
11170 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11171 break;
11172 };
11173 let row0 = chunk_start + off;
11174 min_r0 = Some(row0 as u32);
11175 break;
11176 }
11177 }
11178 if min_r0.is_some() {
11179 break;
11180 }
11181 }
11182 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
11183 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
11184 sparse_idxs.sort_unstable();
11185 for chunk_idx in sparse_idxs {
11186 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
11187 continue;
11188 };
11189 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11190 continue;
11191 };
11192 let tags = chunk.type_tag.values();
11193 for (off, &t) in tags.iter().enumerate() {
11194 let overlay_non_empty = chunk
11195 .overlay
11196 .get(off)
11197 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11198 .unwrap_or(false)
11199 || chunk
11200 .computed_overlay
11201 .get(off)
11202 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11203 .unwrap_or(false);
11204 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
11205 let row0 = chunk_start + off;
11206 min_r0 = Some(row0 as u32);
11207 break;
11208 }
11209 }
11210 if min_r0.is_some() {
11211 break;
11212 }
11213 }
11214 }
11215
11216 let mut max_r0: Option<u32> = None;
11218 if !col.sparse_chunks.is_empty() {
11219 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
11220 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
11221 for chunk_idx in sparse_idxs {
11222 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
11223 continue;
11224 };
11225 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11226 continue;
11227 };
11228 let tags = chunk.type_tag.values();
11229 for (rev_idx, &t) in tags.iter().enumerate().rev() {
11230 let overlay_non_empty = chunk
11231 .overlay
11232 .get(rev_idx)
11233 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11234 .unwrap_or(false)
11235 || chunk
11236 .computed_overlay
11237 .get(rev_idx)
11238 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11239 .unwrap_or(false);
11240 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
11241 let row0 = chunk_start + rev_idx;
11242 max_r0 = Some(row0 as u32);
11243 break;
11244 }
11245 }
11246 if max_r0.is_some() {
11247 break;
11248 }
11249 }
11250 }
11251 if max_r0.is_none() {
11252 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
11253 let tags = chunk.type_tag.values();
11254 for (rev_idx, &t) in tags.iter().enumerate().rev() {
11255 let overlay_non_empty = chunk
11256 .overlay
11257 .get(rev_idx)
11258 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11259 .unwrap_or(false)
11260 || chunk
11261 .computed_overlay
11262 .get(rev_idx)
11263 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11264 .unwrap_or(false);
11265 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
11266 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11267 break;
11268 };
11269 let row0 = chunk_start + rev_idx;
11270 max_r0 = Some(row0 as u32);
11271 break;
11272 }
11273 }
11274 if max_r0.is_some() {
11275 break;
11276 }
11277 }
11278 }
11279
11280 (min_r0, max_r0)
11281 }
11282
11283 fn arrow_used_col_bounds(
11285 &self,
11286 sheet: &str,
11287 start_row: u32,
11288 end_row: u32,
11289 ) -> Option<(u32, u32)> {
11290 let a = self.sheet_store().sheet(sheet)?;
11291 if a.columns.is_empty() {
11292 return None;
11293 }
11294 let sr0 = start_row.saturating_sub(1) as usize;
11295 let er0 = end_row.saturating_sub(1) as usize;
11296 if sr0 > er0 {
11297 return None;
11298 }
11299 let mut min_c0: Option<usize> = None;
11302 let mut max_c0: Option<usize> = None;
11303 for (ci, col) in a.columns.iter().enumerate() {
11305 let mut any_in_range = false;
11306
11307 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
11308 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
11309 return false;
11310 };
11311 let chunk_len = chunk.type_tag.len();
11312 if chunk_len == 0 {
11313 return false;
11314 }
11315 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
11316 if sr0 > chunk_end || er0 < chunk_start {
11318 return false;
11319 }
11320 let start_off = sr0.max(chunk_start) - chunk_start;
11321 let end_off = er0.min(chunk_end) - chunk_start;
11322 let tags = chunk.type_tag.values();
11323 for off in start_off..=end_off {
11324 let overlay_non_empty = chunk
11325 .overlay
11326 .get(off)
11327 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11328 .unwrap_or(false)
11329 || chunk
11330 .computed_overlay
11331 .get(off)
11332 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
11333 .unwrap_or(false);
11334 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
11335 return true;
11336 }
11337 }
11338 false
11339 };
11340
11341 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
11342 if scan_chunk(chunk_idx, chunk) {
11343 any_in_range = true;
11344 break;
11345 }
11346 }
11347
11348 if !any_in_range && !col.sparse_chunks.is_empty() {
11349 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
11350 if scan_chunk(chunk_idx, chunk) {
11351 any_in_range = true;
11352 break;
11353 }
11354 }
11355 }
11356
11357 if any_in_range {
11358 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
11359 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
11360 }
11361 }
11362 match (min_c0, max_c0) {
11363 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
11364 _ => None,
11365 }
11366 }
11367
11368 fn formula_row_bounds_for_columns(
11369 &self,
11370 sheet: &str,
11371 start_col: u32,
11372 end_col: u32,
11373 ) -> Option<(u32, u32)> {
11374 let sheet_id = self.graph.sheet_id(sheet)?;
11375 let sc0 = start_col.saturating_sub(1);
11376 let ec0 = end_col.saturating_sub(1);
11377 let mut min_r0: Option<u32> = None;
11378 let mut max_r0: Option<u32> = None;
11379
11380 if self.graph.sheet_index(sheet_id).is_some() {
11381 for vid in self.graph.vertices_in_cols(sheet_id, sc0, ec0) {
11382 if !matches!(
11383 self.graph.get_vertex_kind(vid),
11384 VertexKind::FormulaScalar | VertexKind::FormulaArray
11385 ) {
11386 continue;
11387 }
11388 let Some(row0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.row()) else {
11389 continue;
11390 };
11391 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11392 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11393 }
11394 } else {
11395 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11396 if !matches!(
11397 self.graph.get_vertex_kind(vid),
11398 VertexKind::FormulaScalar | VertexKind::FormulaArray
11399 ) {
11400 continue;
11401 }
11402 let col0 = coord.col();
11403 if col0 < sc0 || col0 > ec0 {
11404 continue;
11405 }
11406 let row0 = coord.row();
11407 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
11408 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
11409 }
11410 }
11411
11412 match (min_r0, max_r0) {
11413 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11414 _ => None,
11415 }
11416 }
11417
11418 fn formula_col_bounds_for_rows(
11419 &self,
11420 sheet: &str,
11421 start_row: u32,
11422 end_row: u32,
11423 ) -> Option<(u32, u32)> {
11424 let sheet_id = self.graph.sheet_id(sheet)?;
11425 let sr0 = start_row.saturating_sub(1);
11426 let er0 = end_row.saturating_sub(1);
11427 let mut min_c0: Option<u32> = None;
11428 let mut max_c0: Option<u32> = None;
11429
11430 if self.graph.sheet_index(sheet_id).is_some() {
11431 for vid in self.graph.vertices_in_rows(sheet_id, sr0, er0) {
11432 if !matches!(
11433 self.graph.get_vertex_kind(vid),
11434 VertexKind::FormulaScalar | VertexKind::FormulaArray
11435 ) {
11436 continue;
11437 }
11438 let Some(col0) = self.graph.vertex_grid_addr(vid).map(|addr| addr.col()) else {
11439 continue;
11440 };
11441 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11442 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11443 }
11444 } else {
11445 for (vid, coord) in self.graph.grid_vertices_in_sheet(sheet_id) {
11446 if !matches!(
11447 self.graph.get_vertex_kind(vid),
11448 VertexKind::FormulaScalar | VertexKind::FormulaArray
11449 ) {
11450 continue;
11451 }
11452 let row0 = coord.row();
11453 if row0 < sr0 || row0 > er0 {
11454 continue;
11455 }
11456 let col0 = coord.col();
11457 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
11458 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
11459 }
11460 }
11461
11462 match (min_c0, max_c0) {
11463 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
11464 _ => None,
11465 }
11466 }
11467
11468 fn union_used_bounds(
11469 first: Option<(u32, u32)>,
11470 second: Option<(u32, u32)>,
11471 ) -> Option<(u32, u32)> {
11472 match (first, second) {
11473 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
11474 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
11475 (None, None) => None,
11476 }
11477 }
11478
11479 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
11482 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11483 return;
11484 }
11485 if self.arrow_sheets.sheet(sheet).is_none() {
11486 self.arrow_sheets
11487 .sheets
11488 .push(crate::arrow_store::ArrowSheet {
11489 name: std::sync::Arc::<str>::from(sheet),
11490 date_system: self.config.date_system,
11491 columns: Vec::new(),
11492 nrows: 0,
11493 chunk_starts: Vec::new(),
11494 chunk_rows: 32 * 1024,
11495 });
11496 }
11497
11498 let row0 = row.saturating_sub(1) as usize;
11499 let col0 = col.saturating_sub(1) as usize;
11500
11501 let asheet = self
11502 .arrow_sheets
11503 .sheet_mut(sheet)
11504 .expect("ArrowSheet must exist");
11505
11506 let cur_cols = asheet.columns.len();
11507 if col0 >= cur_cols {
11508 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11509 }
11510
11511 if row0 >= asheet.nrows as usize {
11512 if asheet.columns.is_empty() {
11513 asheet.insert_columns(0, 1);
11514 }
11515 asheet.ensure_row_capacity(row0 + 1);
11516 }
11517 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
11518 let ov =
11519 crate::arrow_store::OverlayValue::from_literal_value(value, asheet.date_system);
11520 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
11521 let _ = ch.overlay.set(in_off, ov);
11522 let format = match value {
11523 LiteralValue::Date(_) => Some(crate::format::FormatId::DATE),
11524 LiteralValue::DateTime(_) => Some(crate::format::FormatId::DATETIME),
11525 LiteralValue::Time(_) => Some(crate::format::FormatId::TIME),
11526 LiteralValue::Duration(_) => Some(crate::format::FormatId::DURATION),
11527 _ => None,
11528 };
11529 ch.overlay.set_format(in_off, format);
11530 ch.computed_overlay.remove(in_off)
11535 } else {
11536 return;
11537 };
11538 let abs_threshold = 1024usize;
11540 let frac_den = 50usize;
11541 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
11542 if freed > 0 {
11543 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
11544 }
11545 self.adjust_computed_overlay_bytes(computed_delta);
11546 }
11547 }
11548
11549 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
11554 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11555 return;
11556 }
11557 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11558 return;
11559 };
11560 let row0 = row.saturating_sub(1) as usize;
11561 let col0 = col.saturating_sub(1) as usize;
11562 if row0 >= asheet.nrows as usize {
11563 return;
11564 }
11565 if col0 >= asheet.columns.len() {
11566 return;
11567 }
11568 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11569 return;
11570 };
11571 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
11572 let _ = ch.overlay.remove(in_off);
11573 }
11574 }
11575
11576 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
11577 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11578 return;
11579 }
11580
11581 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11582 return;
11583 };
11584 if start_row0 >= asheet.nrows as usize {
11585 return;
11586 }
11587
11588 let starts = asheet.chunk_starts.clone();
11589 let nrows = asheet.nrows as usize;
11590 let mut delta = 0isize;
11591 for col in &mut asheet.columns {
11592 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
11593 let Some(&chunk_start) = starts.get(chunk_idx) else {
11594 continue;
11595 };
11596 let chunk_end = starts
11597 .get(chunk_idx + 1)
11598 .copied()
11599 .unwrap_or(nrows)
11600 .min(chunk_start.saturating_add(ch.len()));
11601 if chunk_end <= start_row0 {
11602 continue;
11603 }
11604 if chunk_start >= start_row0 {
11605 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11606 } else {
11607 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11608 delta = delta
11609 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11610 }
11611 }
11612
11613 for (chunk_idx, ch) in &mut col.sparse_chunks {
11614 let Some(&chunk_start) = starts.get(*chunk_idx) else {
11615 continue;
11616 };
11617 let chunk_end = starts
11618 .get(*chunk_idx + 1)
11619 .copied()
11620 .unwrap_or(nrows)
11621 .min(chunk_start.saturating_add(ch.len()));
11622 if chunk_end <= start_row0 {
11623 continue;
11624 }
11625 if chunk_start >= start_row0 {
11626 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11627 } else {
11628 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
11629 delta = delta
11630 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
11631 }
11632 }
11633 }
11634 self.adjust_computed_overlay_bytes(delta);
11635 }
11636
11637 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
11638 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
11639 return;
11640 }
11641
11642 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
11643 return;
11644 };
11645 if start_col0 >= asheet.columns.len() {
11646 return;
11647 }
11648
11649 let mut delta = 0isize;
11650 for col in asheet.columns.iter_mut().skip(start_col0) {
11651 for ch in &mut col.chunks {
11652 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11653 }
11654 for ch in col.sparse_chunks.values_mut() {
11655 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
11656 }
11657 }
11658 self.adjust_computed_overlay_bytes(delta);
11659 }
11660
11661 #[inline]
11662 fn literal_to_overlay_value(
11663 value: &LiteralValue,
11664 date_system: crate::engine::DateSystem,
11665 ) -> crate::arrow_store::OverlayValue {
11666 crate::arrow_store::OverlayValue::from_literal_value(value, date_system)
11667 }
11668
11669 fn arrow_sheet_date_system(&self, sheet: &str) -> crate::engine::DateSystem {
11670 self.arrow_sheets
11671 .sheet(sheet)
11672 .map(|sheet| sheet.date_system)
11673 .unwrap_or(self.config.date_system)
11674 }
11675
11676 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11679 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11680 return None;
11681 }
11682 let asheet = self.arrow_sheets.sheet(sheet)?;
11683 let row0 = row.saturating_sub(1) as usize;
11684 let col0 = col.saturating_sub(1) as usize;
11685 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11686 return None;
11687 }
11688 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11689 let ch = asheet.columns[col0].chunk(ch_idx)?;
11690 ch.overlay
11691 .get_scalar(in_off)
11692 .map(|ov| ov.to_literal_for(asheet.date_system))
11693 }
11694
11695 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
11698 if !(self.config.arrow_storage_enabled
11699 && self.config.delta_overlay_enabled
11700 && self.config.write_formula_overlay_enabled)
11701 {
11702 return None;
11703 }
11704 let asheet = self.arrow_sheets.sheet(sheet)?;
11705 let row0 = row.saturating_sub(1) as usize;
11706 let col0 = col.saturating_sub(1) as usize;
11707 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
11708 return None;
11709 }
11710 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
11711 let ch = asheet.columns[col0].chunk(ch_idx)?;
11712 ch.computed_overlay
11713 .get_scalar(in_off)
11714 .map(|ov| ov.to_literal_for(asheet.date_system))
11715 }
11716
11717 fn set_delta_overlay_cell_raw(
11718 &mut self,
11719 sheet: &str,
11720 row: u32,
11721 col: u32,
11722 value: Option<LiteralValue>,
11723 ) {
11724 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
11725 return;
11726 }
11727
11728 self.ensure_arrow_sheet(sheet);
11729 let date_system = self.arrow_sheet_date_system(sheet);
11730 let ov_opt = value
11731 .as_ref()
11732 .map(|value| Self::literal_to_overlay_value(value, date_system));
11733 let row0 = row.saturating_sub(1) as usize;
11734 let col0 = col.saturating_sub(1) as usize;
11735 let asheet = self
11736 .arrow_sheets
11737 .sheet_mut(sheet)
11738 .expect("ArrowSheet must exist");
11739
11740 let cur_cols = asheet.columns.len();
11741 if col0 >= cur_cols {
11742 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11743 }
11744 if row0 >= asheet.nrows as usize {
11745 if asheet.columns.is_empty() {
11746 asheet.insert_columns(0, 1);
11747 }
11748 asheet.ensure_row_capacity(row0 + 1);
11749 }
11750
11751 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11752 return;
11753 };
11754 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11755 return;
11756 };
11757
11758 if let Some(ov) = ov_opt {
11759 let _ = ch.overlay.set(in_off, ov);
11760 } else {
11761 let _ = ch.overlay.remove(in_off);
11762 }
11763 }
11764
11765 fn set_computed_overlay_cell_raw(
11766 &mut self,
11767 sheet: &str,
11768 row: u32,
11769 col: u32,
11770 value: Option<LiteralValue>,
11771 ) {
11772 if !(self.config.arrow_storage_enabled
11773 && self.config.delta_overlay_enabled
11774 && self.config.write_formula_overlay_enabled)
11775 {
11776 return;
11777 }
11778
11779 self.ensure_arrow_sheet(sheet);
11780 let date_system = self.arrow_sheet_date_system(sheet);
11781 let ov_opt = value
11782 .as_ref()
11783 .map(|value| Self::literal_to_overlay_value(value, date_system));
11784 let row0 = row.saturating_sub(1) as usize;
11785 let col0 = col.saturating_sub(1) as usize;
11786 let asheet = self
11787 .arrow_sheets
11788 .sheet_mut(sheet)
11789 .expect("ArrowSheet must exist");
11790
11791 let cur_cols = asheet.columns.len();
11792 if col0 >= cur_cols {
11793 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
11794 }
11795 if row0 >= asheet.nrows as usize {
11796 if asheet.columns.is_empty() {
11797 asheet.insert_columns(0, 1);
11798 }
11799 asheet.ensure_row_capacity(row0 + 1);
11800 }
11801
11802 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
11803 return;
11804 };
11805 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
11806 return;
11807 };
11808
11809 let delta = if let Some(ov) = ov_opt {
11810 ch.computed_overlay.set(in_off, ov)
11811 } else {
11812 ch.computed_overlay.remove(in_off)
11813 };
11814 self.adjust_computed_overlay_bytes(delta);
11815 }
11816
11817 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
11818 use crate::engine::ArrowOp;
11819
11820 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
11821 Box::new(batch.ops.iter().rev())
11822 } else {
11823 Box::new(batch.ops.iter())
11824 };
11825
11826 for op in iter {
11827 match op {
11828 ArrowOp::SetDeltaCell {
11829 sheet_id,
11830 row0,
11831 col0,
11832 old,
11833 new,
11834 } => {
11835 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11836 let v = if undo { old.clone() } else { new.clone() };
11837 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11838 }
11839 ArrowOp::SetComputedCell {
11840 sheet_id,
11841 row0,
11842 col0,
11843 old,
11844 new,
11845 } => {
11846 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11847 let v = if undo { old.clone() } else { new.clone() };
11848 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
11849 }
11850 ArrowOp::RestoreComputedRect {
11851 sheet_id,
11852 sr0,
11853 sc0,
11854 er0,
11855 ec0,
11856 old,
11857 new,
11858 } => {
11859 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11860 let vals = if undo { old } else { new };
11861 let height = (*er0).saturating_sub(*sr0) as usize + 1;
11862 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
11863 for r in 0..height {
11864 for c in 0..width {
11865 let v = vals
11866 .get(r)
11867 .and_then(|row| row.get(c))
11868 .cloned()
11869 .unwrap_or(LiteralValue::Empty);
11870 self.set_computed_overlay_cell_raw(
11871 &sheet,
11872 *sr0 + 1 + r as u32,
11873 *sc0 + 1 + c as u32,
11874 Some(v),
11875 );
11876 }
11877 }
11878 }
11879 ArrowOp::InsertRows {
11880 sheet_id,
11881 before0,
11882 count,
11883 } => {
11884 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11885 self.ensure_arrow_sheet(&sheet);
11886 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11887 if undo {
11888 asheet.delete_rows(*before0 as usize, *count as usize);
11889 } else {
11890 asheet.insert_rows(*before0 as usize, *count as usize);
11891 }
11892 }
11893 self.purge_derived_formats_after_row(*sheet_id, *before0);
11894 }
11895 ArrowOp::InsertCols {
11896 sheet_id,
11897 before0,
11898 count,
11899 } => {
11900 let sheet = self.graph.sheet_name(*sheet_id).to_string();
11901 self.ensure_arrow_sheet(&sheet);
11902 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
11903 if undo {
11904 asheet.delete_columns(*before0 as usize, *count as usize);
11905 } else {
11906 asheet.insert_columns(*before0 as usize, *count as usize);
11907 }
11908 }
11909 self.purge_derived_formats_after_col(*sheet_id, *before0);
11910 }
11911 }
11912 }
11913 }
11914
11915 fn record_spill_ops_into_arrow_undo(
11916 &mut self,
11917 undo: &mut crate::engine::ArrowUndoBatch,
11918 events: &[crate::engine::ChangeEvent],
11919 ) {
11920 use crate::engine::ChangeEvent;
11921 use formualizer_common::LiteralValue;
11922
11923 #[allow(clippy::type_complexity)]
11924 let rect_from_snapshot =
11925 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
11926 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
11927 if snap.target_cells.is_empty() {
11928 return None;
11929 }
11930 let sheet_id = snap.target_cells[0].sheet_id;
11931 let sr0 = snap.target_cells[0].coord.row();
11932 let sc0 = snap.target_cells[0].coord.col();
11933 if snap.values.is_empty() || snap.values[0].is_empty() {
11934 return None;
11935 }
11936 let h = snap.values.len() as u32;
11937 let w = snap.values[0].len() as u32;
11938 let er0 = sr0.saturating_add(h.saturating_sub(1));
11939 let ec0 = sc0.saturating_add(w.saturating_sub(1));
11940 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
11941 };
11942
11943 for ev in events {
11944 match ev {
11945 ChangeEvent::SpillCommitted { old, new, .. } => {
11946 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
11947 let old_vals = if let Some(old_snap) = old {
11948 rect_from_snapshot(old_snap)
11949 .map(|(_, _, _, _, _, v)| v)
11950 .unwrap_or_else(|| {
11951 vec![
11952 vec![LiteralValue::Empty; new_vals[0].len()];
11953 new_vals.len()
11954 ]
11955 })
11956 } else {
11957 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
11958 };
11959 undo.record_restore_computed_rect(
11960 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11961 );
11962 }
11963 }
11964 ChangeEvent::SpillCleared { old, .. } => {
11965 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
11966 let new_vals =
11967 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
11968 undo.record_restore_computed_rect(
11969 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
11970 );
11971 }
11972 }
11973 _ => {}
11974 }
11975 }
11976 }
11977
11978 fn mirror_value_to_computed_overlay(
11983 &mut self,
11984 sheet: &str,
11985 row: u32,
11986 col: u32,
11987 value: &LiteralValue,
11988 ) {
11989 if !(self.config.arrow_storage_enabled
11990 && self.config.delta_overlay_enabled
11991 && self.config.write_formula_overlay_enabled)
11992 {
11993 return;
11994 }
11995 if self.computed_overlay_mirroring_disabled {
11996 return;
11997 }
11998
11999 let date_system = self.arrow_sheet_date_system(sheet);
12000 let ov = Self::literal_to_overlay_value(value, date_system);
12001 self.write_computed_overlay_value_0based(
12002 sheet,
12003 row.saturating_sub(1),
12004 col.saturating_sub(1),
12005 ov,
12006 );
12007 }
12008
12009 fn record_derived_format(&self, vertex_id: VertexId, format: Option<crate::format::FormatId>) {
12010 if let Some(cell) = self.graph.get_cell_ref(vertex_id) {
12011 self.record_derived_format_at(cell, format);
12012 }
12013 }
12014
12015 fn record_derived_format_at(&self, cell: CellRef, format: Option<crate::format::FormatId>) {
12016 #[cfg(test)]
12017 self.derived_format_operations_for_test
12018 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
12019 let format = format.filter(|id| *id != crate::format::FormatId::GENERAL);
12020 self.derived_formats.set(cell, format);
12021 }
12022
12023 fn clear_cell_format_state(&mut self, sheet: &str, cell: CellRef) {
12024 self.derived_formats.set(cell, None);
12025 if let Some(arrow) = self.arrow_sheets.sheet_mut(sheet) {
12026 arrow.clear_format(cell.coord.row() as usize, cell.coord.col() as usize);
12027 }
12028 }
12029
12030 fn clear_logged_cell_format_states(&mut self, events: &[ChangeEvent]) {
12031 let cells = events
12032 .iter()
12033 .filter_map(|event| match event {
12034 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
12035 Some(*addr)
12036 }
12037 _ => None,
12038 })
12039 .collect::<FxHashSet<_>>();
12040 for cell in cells {
12041 let sheet = self.graph.sheet_name(cell.sheet_id).to_string();
12042 self.clear_cell_format_state(&sheet, cell);
12043 }
12044 }
12045
12046 fn purge_derived_formats_after_row(&mut self, sheet_id: SheetId, start0: u32) {
12047 self.derived_formats
12048 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.row() < start0);
12049 }
12050
12051 fn purge_derived_formats_after_col(&mut self, sheet_id: SheetId, start0: u32) {
12052 self.derived_formats
12053 .retain(|cell| cell.sheet_id != sheet_id || cell.coord.col() < start0);
12054 }
12055
12056 fn purge_derived_formats_for_sheet(&mut self, sheet_id: SheetId) {
12057 self.derived_formats
12058 .retain(|cell| cell.sheet_id != sheet_id);
12059 }
12060
12061 #[cfg(test)]
12062 pub(crate) fn debug_computed_overlay_format_0based(
12063 &self,
12064 sheet: &str,
12065 row0: u32,
12066 col0: u32,
12067 ) -> Option<crate::format::FormatId> {
12068 let sheet = self.arrow_sheets.sheet(sheet)?;
12069 let (chunk_idx, row_in_chunk) = sheet.chunk_of_row(row0 as usize)?;
12070 sheet
12071 .columns
12072 .get(col0 as usize)?
12073 .chunk(chunk_idx)?
12074 .computed_overlay
12075 .get_format(row_in_chunk)
12076 }
12077
12078 #[cfg(test)]
12079 pub(crate) fn debug_computed_overlay_chunk_has_formats_0based(
12080 &self,
12081 sheet: &str,
12082 row0: u32,
12083 col0: u32,
12084 ) -> bool {
12085 let Some(sheet) = self.arrow_sheets.sheet(sheet) else {
12086 return false;
12087 };
12088 let Some((chunk_idx, _)) = sheet.chunk_of_row(row0 as usize) else {
12089 return false;
12090 };
12091 sheet
12092 .columns
12093 .get(col0 as usize)
12094 .and_then(|column| column.chunk(chunk_idx))
12095 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
12096 }
12097
12098 #[cfg(test)]
12099 pub(crate) fn debug_clear_derived_format_0based(&mut self, sheet: &str, row0: u32, col0: u32) {
12100 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
12101 self.derived_formats
12102 .set(CellRef::new_absolute(sheet_id, row0, col0), None);
12103 }
12104 }
12105
12106 #[cfg(test)]
12107 pub(crate) fn debug_record_derived_format_0based(
12108 &self,
12109 sheet: &str,
12110 row0: u32,
12111 col0: u32,
12112 format: Option<crate::format::FormatId>,
12113 ) {
12114 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
12115 self.record_derived_format_at(CellRef::new_absolute(sheet_id, row0, col0), format);
12116 }
12117 }
12118
12119 #[cfg(test)]
12120 pub(crate) fn debug_derived_format_0based(
12121 &self,
12122 sheet: &str,
12123 row0: u32,
12124 col0: u32,
12125 ) -> Option<crate::format::FormatId> {
12126 let sheet_id = self.graph.sheet_id(sheet)?;
12127 self.derived_formats
12128 .get(&CellRef::new_absolute(sheet_id, row0, col0))
12129 }
12130
12131 #[cfg(test)]
12132 pub(crate) fn debug_reset_format_write_operation_counts(&mut self) {
12133 self.derived_format_operations_for_test
12134 .store(0, std::sync::atomic::Ordering::Relaxed);
12135 self.computed_overlay_set_explicit_entry_operations_for_test = 0;
12136 self.computed_overlay_stale_clear_range_effects_for_test = 0;
12137 self.computed_overlay_stale_clear_offset_attempts_for_test = 0;
12138 self.computed_format_vector_allocations_for_test
12139 .store(0, std::sync::atomic::Ordering::Relaxed);
12140 }
12141
12142 #[cfg(test)]
12143 pub(crate) fn debug_format_write_operation_counts(&self) -> (u64, u64, u64, u64, u64) {
12144 (
12145 self.derived_format_operations_for_test
12146 .load(std::sync::atomic::Ordering::Relaxed),
12147 self.computed_overlay_set_explicit_entry_operations_for_test,
12148 self.computed_format_vector_allocations_for_test
12149 .load(std::sync::atomic::Ordering::Relaxed),
12150 self.computed_overlay_stale_clear_range_effects_for_test,
12151 self.computed_overlay_stale_clear_offset_attempts_for_test,
12152 )
12153 }
12154
12155 fn write_computed_overlay_format_0based(
12156 &mut self,
12157 sheet: &str,
12158 row0: u32,
12159 col0: u32,
12160 format: Option<crate::format::FormatId>,
12161 ) {
12162 self.ensure_arrow_sheet(sheet);
12163 let (row0, col0) = (row0 as usize, col0 as usize);
12164 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
12165 return;
12166 };
12167 if col0 >= asheet.columns.len() {
12168 asheet.insert_columns(asheet.columns.len(), col0 + 1 - asheet.columns.len());
12169 }
12170 if row0 >= asheet.nrows as usize {
12171 asheet.ensure_row_capacity(row0 + 1);
12172 }
12173 let Some((chunk, offset)) = asheet.chunk_of_row(row0) else {
12174 return;
12175 };
12176 if let Some(chunk) = asheet.ensure_column_chunk_mut(col0, chunk) {
12177 chunk.computed_overlay.set_format(offset, format);
12178 }
12179 }
12180
12181 fn write_computed_cell_0based(
12186 &mut self,
12187 cell: CellRef,
12188 value: &LiteralValue,
12189 format: Option<crate::format::FormatId>,
12190 ) {
12191 if !(self.config.arrow_storage_enabled
12192 && self.config.delta_overlay_enabled
12193 && self.config.write_formula_overlay_enabled)
12194 || self.computed_overlay_mirroring_disabled
12195 {
12196 return;
12197 }
12198 let sheet = self.graph.sheet_name(cell.sheet_id);
12199 let index = match self
12200 .arrow_sheets
12201 .sheets
12202 .iter()
12203 .position(|s| s.name.as_ref() == sheet)
12204 {
12205 Some(index) => index,
12206 None => {
12207 let sheet = sheet.to_string();
12208 self.ensure_arrow_sheet(&sheet);
12209 self.arrow_sheets.sheets.len() - 1
12210 }
12211 };
12212 let (row0, col0) = (cell.coord.row() as usize, cell.coord.col() as usize);
12213 let asheet = &mut self.arrow_sheets.sheets[index];
12214 let ov = Self::literal_to_overlay_value(value, asheet.date_system);
12215 let cur_cols = asheet.columns.len();
12216 if col0 >= cur_cols {
12217 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
12218 }
12219 if row0 >= asheet.nrows as usize {
12220 asheet.ensure_row_capacity(row0 + 1);
12221 }
12222 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
12223 return;
12224 };
12225 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
12226 return;
12227 };
12228 let delta = ch.computed_overlay.set_scalar(in_off, ov);
12229 ch.computed_overlay.set_format(in_off, format);
12230 self.adjust_computed_overlay_bytes(delta);
12231 if let Some(cap) = self.config.max_overlay_memory_bytes
12232 && self.computed_overlay_bytes_estimate > cap
12233 {
12234 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12235 }
12236 }
12237
12238 fn write_computed_overlay_value_0based(
12239 &mut self,
12240 sheet: &str,
12241 row0: u32,
12242 col0: u32,
12243 value: OverlayValue,
12244 ) {
12245 if !(self.config.arrow_storage_enabled
12246 && self.config.delta_overlay_enabled
12247 && self.config.write_formula_overlay_enabled)
12248 {
12249 return;
12250 }
12251 if self.computed_overlay_mirroring_disabled {
12252 return;
12253 }
12254
12255 self.ensure_arrow_sheet(sheet);
12256
12257 let row0 = row0 as usize;
12258 let col0 = col0 as usize;
12259 let asheet = self
12260 .arrow_sheets
12261 .sheet_mut(sheet)
12262 .expect("ArrowSheet must exist");
12263
12264 let cur_cols = asheet.columns.len();
12265 if col0 >= cur_cols {
12266 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
12267 }
12268
12269 if row0 >= asheet.nrows as usize {
12270 if asheet.columns.is_empty() {
12271 asheet.insert_columns(0, 1);
12272 }
12273 asheet.ensure_row_capacity(row0 + 1);
12274 }
12275
12276 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
12277 return;
12278 };
12279 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
12280 return;
12281 };
12282
12283 let delta = ch.computed_overlay.set_scalar(in_off, value);
12284 self.adjust_computed_overlay_bytes(delta);
12285
12286 if let Some(cap) = self.config.max_overlay_memory_bytes
12287 && self.computed_overlay_bytes_estimate > cap
12288 {
12289 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12290 }
12291 }
12292
12293 pub(crate) fn plan_computed_write_coalescing(
12294 &self,
12295 buffer: &ComputedWriteBuffer,
12296 ) -> ComputedWriteCoalescingPlan {
12297 self.plan_computed_write_coalescing_from_writes(
12298 buffer.writes().iter().cloned(),
12299 buffer.formats_present,
12300 )
12301 }
12302
12303 fn plan_owned_computed_write_coalescing(
12304 &self,
12305 writes: Vec<ComputedWrite>,
12306 formats_present: bool,
12307 ) -> ComputedWriteCoalescingPlan {
12308 self.plan_computed_write_coalescing_from_writes(writes, formats_present)
12309 }
12310
12311 fn plan_computed_write_coalescing_from_writes(
12312 &self,
12313 writes: impl IntoIterator<Item = ComputedWrite>,
12314 formats_present: bool,
12315 ) -> ComputedWriteCoalescingPlan {
12316 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
12317 BTreeMap::new();
12318 let mut input_cells = 0usize;
12319 let mut located: Option<(SheetId, Option<&crate::arrow_store::ArrowSheet>)> = None;
12321
12322 for write in writes {
12323 match write {
12324 ComputedWrite::Cell {
12325 seq,
12326 sheet_id,
12327 row0,
12328 col0,
12329 value,
12330 format_id,
12331 } => {
12332 input_cells = input_cells.saturating_add(1);
12333 let sheet = match located {
12334 Some((id, sheet)) if id == sheet_id => sheet,
12335 _ => {
12336 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12337 located = Some((sheet_id, sheet));
12338 sheet
12339 }
12340 };
12341 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12342 Some(sheet) => {
12343 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize)
12344 }
12345 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12346 row0 as usize,
12347 32 * 1024,
12348 ),
12349 };
12350 groups
12351 .entry(ComputedWriteChunkKey {
12352 sheet_id,
12353 col0,
12354 chunk_idx,
12355 chunk_start_row0,
12356 })
12357 .or_default()
12358 .push(ComputedWriteChunkEntryPlan {
12359 row_in_chunk,
12360 seq,
12361 value,
12362 format_id,
12363 });
12364 }
12365 ComputedWrite::Run {
12366 seq,
12367 sheet_id,
12368 row0,
12369 col0,
12370 entries,
12371 } => {
12372 input_cells = input_cells.saturating_add(entries.len());
12373 let sheet = match located {
12374 Some((id, sheet)) if id == sheet_id => sheet,
12375 _ => {
12376 let sheet = self.arrow_sheets.sheet(self.graph.sheet_name(sheet_id));
12377 located = Some((sheet_id, sheet));
12378 sheet
12379 }
12380 };
12381 let mut segment: Vec<ComputedWriteChunkEntryPlan> = Vec::new();
12385 let mut segment_key: Option<ComputedWriteChunkKey> = None;
12386 for (k, (value, format_id)) in entries.into_iter().enumerate() {
12387 let row = row0.saturating_add(k as u32) as usize;
12388 let (chunk_idx, chunk_start_row0, row_in_chunk) = match sheet {
12389 Some(sheet) => {
12390 Self::locate_row_in_sheet_for_computed_write_plan(sheet, row)
12391 }
12392 None => Self::locate_row_in_empty_sheet_for_computed_write_plan(
12393 row,
12394 32 * 1024,
12395 ),
12396 };
12397 let key = ComputedWriteChunkKey {
12398 sheet_id,
12399 col0,
12400 chunk_idx,
12401 chunk_start_row0,
12402 };
12403 if segment_key != Some(key)
12404 && let Some(done) = segment_key.replace(key)
12405 {
12406 groups.entry(done).or_default().append(&mut segment);
12407 }
12408 segment.push(ComputedWriteChunkEntryPlan {
12409 row_in_chunk,
12410 seq,
12411 value,
12412 format_id,
12413 });
12414 }
12415 if let Some(done) = segment_key {
12416 groups.entry(done).or_default().append(&mut segment);
12417 }
12418 }
12419 ComputedWrite::Rect {
12420 seq,
12421 sheet_id,
12422 sr0,
12423 sc0,
12424 values,
12425 } => {
12426 for (r_off, row) in values.into_iter().enumerate() {
12427 for (c_off, value) in row.into_iter().enumerate() {
12428 input_cells = input_cells.saturating_add(1);
12429 self.push_computed_write_plan_entry(
12430 &mut groups,
12431 seq,
12432 sheet_id,
12433 sr0.saturating_add(r_off as u32),
12434 sc0.saturating_add(c_off as u32),
12435 value,
12436 None,
12437 );
12438 }
12439 }
12440 }
12441 }
12442 }
12443
12444 let mut plan = ComputedWriteCoalescingPlan {
12445 chunks: Vec::with_capacity(groups.len()),
12446 input_cells,
12447 coalesced_cells: 0,
12448 overwritten_cells: 0,
12449 };
12450 for (key, entries) in groups {
12451 let computed_lane_has_formats =
12452 self.computed_overlay_chunk_has_formats(key.sheet_id, key.col0, key.chunk_idx);
12453 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(
12454 key,
12455 entries,
12456 formats_present,
12457 computed_lane_has_formats,
12458 );
12459 #[cfg(test)]
12460 if matches!(
12461 &chunk_plan.format_effect,
12462 ComputedWriteChunkFormatEffect::SetExplicit(_)
12463 ) {
12464 self.computed_format_vector_allocations_for_test
12465 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
12466 }
12467 plan.coalesced_cells = plan
12468 .coalesced_cells
12469 .saturating_add(chunk_plan.entries.len());
12470 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
12471 plan.chunks.push(chunk_plan);
12472 }
12473 debug_assert_eq!(
12474 plan.input_cells,
12475 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
12476 );
12477 plan
12478 }
12479
12480 fn push_computed_write_plan_entry(
12481 &self,
12482 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
12483 seq: u64,
12484 sheet_id: SheetId,
12485 row0: u32,
12486 col0: u32,
12487 value: OverlayValue,
12488 format_id: Option<crate::format::FormatId>,
12489 ) {
12490 let (chunk_idx, chunk_start_row0, row_in_chunk) =
12491 self.locate_computed_write_chunk(sheet_id, row0);
12492 let key = ComputedWriteChunkKey {
12493 sheet_id,
12494 col0,
12495 chunk_idx,
12496 chunk_start_row0,
12497 };
12498 groups
12499 .entry(key)
12500 .or_default()
12501 .push(ComputedWriteChunkEntryPlan {
12502 row_in_chunk,
12503 seq,
12504 value,
12505 format_id,
12506 });
12507 }
12508
12509 fn computed_overlay_chunk_has_formats(
12510 &self,
12511 sheet_id: SheetId,
12512 col0: u32,
12513 chunk_idx: usize,
12514 ) -> bool {
12515 let sheet_name = self.graph.sheet_name(sheet_id);
12516 self.arrow_sheets
12517 .sheet(sheet_name)
12518 .and_then(|sheet| sheet.columns.get(col0 as usize))
12519 .and_then(|column| column.chunk(chunk_idx))
12520 .is_some_and(|chunk| chunk.computed_overlay.has_formats())
12521 }
12522
12523 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
12524 let sheet_name = self.graph.sheet_name(sheet_id);
12525 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
12526 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
12527 }
12528 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
12529 }
12530
12531 fn locate_row_in_sheet_for_computed_write_plan(
12532 sheet: &crate::arrow_store::ArrowSheet,
12533 row0: usize,
12534 ) -> (usize, u32, usize) {
12535 if row0 < sheet.nrows as usize
12536 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
12537 {
12538 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
12539 return (chunk_idx, chunk_start as u32, row_in_chunk);
12540 }
12541
12542 let chunk_rows = sheet.chunk_rows.max(1);
12543 if sheet.chunk_starts.is_empty() {
12544 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
12545 }
12546
12547 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
12548 let mut chunk_start = sheet.chunk_starts[chunk_idx];
12549 while chunk_start.saturating_add(chunk_rows) <= row0 {
12550 chunk_idx = chunk_idx.saturating_add(1);
12551 chunk_start = chunk_start.saturating_add(chunk_rows);
12552 }
12553 (
12554 chunk_idx,
12555 chunk_start as u32,
12556 row0.saturating_sub(chunk_start),
12557 )
12558 }
12559
12560 fn locate_row_in_empty_sheet_for_computed_write_plan(
12561 row0: usize,
12562 chunk_rows: usize,
12563 ) -> (usize, u32, usize) {
12564 let chunk_rows = chunk_rows.max(1);
12565 let chunk_idx = row0 / chunk_rows;
12566 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
12567 (
12568 chunk_idx,
12569 chunk_start as u32,
12570 row0.saturating_sub(chunk_start),
12571 )
12572 }
12573
12574 #[cfg(test)]
12575 pub(crate) fn debug_plan_computed_write_coalescing(
12576 &self,
12577 buffer: &ComputedWriteBuffer,
12578 ) -> ComputedWriteCoalescingPlan {
12579 self.plan_computed_write_coalescing(buffer)
12580 }
12581
12582 pub(crate) fn flush_computed_write_buffer(
12583 &mut self,
12584 buffer: &mut ComputedWriteBuffer,
12585 ) -> Result<(), ExcelError> {
12586 if buffer.is_empty() {
12587 return Ok(());
12588 }
12589
12590 self.resource_checkpoint(0)?;
12595 let commit_started = self.preflight_evaluation_commit_window(buffer.len())?;
12596 let (writes, formats_present) = buffer.take_writes();
12597 let plan = self.plan_owned_computed_write_coalescing(writes, formats_present);
12598 self.flush_computed_write_plan(plan);
12599 self.observe_evaluation_commit_window(commit_started);
12600
12601 Ok(())
12602 }
12603
12604 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
12605 for chunk in plan.chunks {
12606 self.flush_computed_write_chunk_plan(chunk);
12607 }
12608 }
12609
12610 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
12611 match &chunk.shape {
12612 ComputedWriteChunkPlanShape::Point => {
12613 self.flush_computed_write_chunk_plan_as_points(chunk);
12614 }
12615 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
12616 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
12617 }
12618 ComputedWriteChunkPlanShape::DenseRange { .. } => {
12619 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12620 }
12621 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
12622 if Self::should_emit_computed_run_fragment(*len, *runs) {
12623 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
12624 } else {
12625 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
12626 }
12627 }
12628 }
12629 }
12630
12631 #[inline]
12632 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
12633 runs <= len / 2
12634 }
12635
12636 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
12637 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
12638 for entry in chunk.entries {
12639 let row0 = chunk
12640 .chunk_start_row0
12641 .saturating_add(entry.row_in_chunk as u32);
12642 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
12643 }
12644 self.apply_computed_overlay_format_effect(
12645 chunk.sheet_id,
12646 chunk.col0,
12647 chunk.chunk_idx,
12648 chunk.format_effect,
12649 );
12650 }
12651
12652 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
12653 &mut self,
12654 chunk: ComputedWriteChunkPlan,
12655 ) {
12656 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
12657 let sheet_id = chunk.sheet_id;
12658 let col0 = chunk.col0;
12659 let chunk_idx = chunk.chunk_idx;
12660 let chunk_start_row0 = chunk.chunk_start_row0;
12661 let format_effect = chunk.format_effect;
12662 let items: Vec<(usize, OverlayValue)> = chunk
12663 .entries
12664 .into_iter()
12665 .map(|entry| (entry.row_in_chunk, entry.value))
12666 .collect();
12667 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
12668 items,
12669 point_estimate,
12670 ) {
12671 Some(Ok(fragment)) => {
12672 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
12673 }
12674 Some(Err(cells)) => {
12675 self.flush_computed_overlay_cells_as_points(
12676 sheet_id,
12677 col0,
12678 chunk_start_row0,
12679 cells,
12680 );
12681 }
12682 None => {}
12683 }
12684 self.apply_computed_overlay_format_effect(sheet_id, col0, chunk_idx, format_effect);
12685 }
12686
12687 #[inline]
12688 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
12689 chunk
12690 .entries
12691 .iter()
12692 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
12693 .fold(0usize, usize::saturating_add)
12694 }
12695
12696 fn flush_computed_overlay_cells_as_points(
12697 &mut self,
12698 sheet_id: SheetId,
12699 col0: u32,
12700 chunk_start_row0: u32,
12701 cells: Vec<(usize, OverlayValue)>,
12702 ) {
12703 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12704 for (row_in_chunk, value) in cells {
12705 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
12706 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
12707 }
12708 }
12709
12710 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12711 if chunk.entries.is_empty() {
12712 return;
12713 }
12714 let start = chunk.entries[0].row_in_chunk;
12715 let values: Vec<OverlayValue> =
12716 chunk.entries.into_iter().map(|entry| entry.value).collect();
12717 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
12718 self.apply_computed_overlay_fragment(
12719 chunk.sheet_id,
12720 chunk.col0,
12721 chunk.chunk_idx,
12722 fragment,
12723 );
12724 }
12725 self.apply_computed_overlay_format_effect(
12726 chunk.sheet_id,
12727 chunk.col0,
12728 chunk.chunk_idx,
12729 chunk.format_effect,
12730 );
12731 }
12732
12733 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
12734 if chunk.entries.is_empty() {
12735 return;
12736 }
12737 let start = chunk.entries[0].row_in_chunk;
12738 let values: Vec<OverlayValue> =
12739 chunk.entries.into_iter().map(|entry| entry.value).collect();
12740 if let Some(fragment) = OverlayFragment::run_range(start, values) {
12741 self.apply_computed_overlay_fragment(
12742 chunk.sheet_id,
12743 chunk.col0,
12744 chunk.chunk_idx,
12745 fragment,
12746 );
12747 }
12748 self.apply_computed_overlay_format_effect(
12749 chunk.sheet_id,
12750 chunk.col0,
12751 chunk.chunk_idx,
12752 chunk.format_effect,
12753 );
12754 }
12755
12756 fn apply_computed_overlay_format_effect(
12757 &mut self,
12758 sheet_id: SheetId,
12759 col0: u32,
12760 chunk_idx: usize,
12761 effect: ComputedWriteChunkFormatEffect,
12762 ) {
12763 if !(self.config.arrow_storage_enabled
12764 && self.config.delta_overlay_enabled
12765 && self.config.write_formula_overlay_enabled)
12766 || self.computed_overlay_mirroring_disabled
12767 {
12768 return;
12769 }
12770
12771 let sheet_name = self.graph.sheet_name(sheet_id);
12772 let Some(sheet) = self.arrow_sheets.sheet_mut(sheet_name) else {
12773 return;
12774 };
12775 let Some(chunk) = sheet
12776 .columns
12777 .get_mut(col0 as usize)
12778 .and_then(|column| column.chunk_mut(chunk_idx))
12779 else {
12780 return;
12781 };
12782 match effect {
12783 ComputedWriteChunkFormatEffect::NoFormatWork => {}
12784 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Range {
12785 start,
12786 end,
12787 }) => {
12788 #[cfg(test)]
12789 {
12790 self.computed_overlay_stale_clear_range_effects_for_test = self
12791 .computed_overlay_stale_clear_range_effects_for_test
12792 .saturating_add(1);
12793 }
12794 chunk.computed_overlay.clear_format_range(start, end);
12795 }
12796 ComputedWriteChunkFormatEffect::ClearStale(ComputedWriteFormatClear::Offsets(
12797 offsets,
12798 )) => {
12799 #[cfg(test)]
12800 {
12801 self.computed_overlay_stale_clear_offset_attempts_for_test = self
12802 .computed_overlay_stale_clear_offset_attempts_for_test
12803 .saturating_add(offsets.len() as u64);
12804 }
12805 chunk.computed_overlay.clear_format_offsets(&offsets);
12806 }
12807 ComputedWriteChunkFormatEffect::SetExplicit(formats) => {
12808 #[cfg(test)]
12809 {
12810 self.computed_overlay_set_explicit_entry_operations_for_test = self
12811 .computed_overlay_set_explicit_entry_operations_for_test
12812 .saturating_add(formats.len() as u64);
12813 }
12814 for (row_in_chunk, format_id) in formats {
12815 chunk.computed_overlay.set_format(row_in_chunk, format_id);
12816 }
12817 }
12818 }
12819 }
12820
12821 fn apply_computed_overlay_fragment(
12822 &mut self,
12823 sheet_id: SheetId,
12824 col0: u32,
12825 chunk_idx: usize,
12826 fragment: OverlayFragment,
12827 ) {
12828 if !(self.config.arrow_storage_enabled
12829 && self.config.delta_overlay_enabled
12830 && self.config.write_formula_overlay_enabled)
12831 {
12832 return;
12833 }
12834 if self.computed_overlay_mirroring_disabled {
12835 return;
12836 }
12837
12838 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
12839 self.ensure_arrow_sheet(&sheet_name);
12840
12841 let col0 = col0 as usize;
12842 let asheet = self
12843 .arrow_sheets
12844 .sheet_mut(&sheet_name)
12845 .expect("ArrowSheet must exist");
12846
12847 let cur_cols = asheet.columns.len();
12848 if col0 >= cur_cols {
12849 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
12850 }
12851
12852 let start_row0 = asheet
12853 .chunk_starts
12854 .get(chunk_idx)
12855 .copied()
12856 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
12857 let required_rows =
12858 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
12859 if required_rows > asheet.nrows as usize {
12860 if asheet.columns.is_empty() {
12861 asheet.insert_columns(0, 1);
12862 }
12863 asheet.ensure_row_capacity(required_rows);
12864 }
12865
12866 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
12867 return;
12868 };
12869 let delta = ch.computed_overlay.apply_fragment(fragment);
12870 self.adjust_computed_overlay_bytes(delta);
12871
12872 if let Some(cap) = self.config.max_overlay_memory_bytes
12873 && self.computed_overlay_bytes_estimate > cap
12874 {
12875 self.disable_computed_overlay_mirroring_due_to_budget(cap);
12876 }
12877 }
12878
12879 #[inline]
12880 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
12881 if delta >= 0 {
12882 self.computed_overlay_bytes_estimate = self
12883 .computed_overlay_bytes_estimate
12884 .saturating_add(delta as usize);
12885 } else {
12886 self.computed_overlay_bytes_estimate = self
12887 .computed_overlay_bytes_estimate
12888 .saturating_sub((-delta) as usize);
12889 }
12890 }
12891
12892 fn clear_all_computed_overlays(&mut self) {
12893 let mut freed_total = 0usize;
12894 for sh in self.arrow_sheets.sheets.iter_mut() {
12895 for col in sh.columns.iter_mut() {
12896 for ch in col.chunks.iter_mut() {
12897 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12898 }
12899 for ch in col.sparse_chunks.values_mut() {
12900 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
12901 }
12902 }
12903 }
12904 self.computed_overlay_bytes_estimate = self
12905 .computed_overlay_bytes_estimate
12906 .saturating_sub(freed_total);
12907 }
12908
12909 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
12910 self.compact_all_computed_overlays();
12913 }
12914
12915 fn compact_all_computed_overlays(&mut self) {
12918 let mut freed_total = 0usize;
12919 for sheet in self.arrow_sheets.sheets.iter_mut() {
12920 for col_idx in 0..sheet.columns.len() {
12921 let num_dense = sheet.columns[col_idx].chunks.len();
12923 for ch_idx in 0..num_dense {
12924 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
12925 }
12926 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
12928 .sparse_chunks
12929 .keys()
12930 .copied()
12931 .collect();
12932 for ch_idx in sparse_keys {
12933 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
12934 }
12935 }
12936 }
12937 self.computed_overlay_bytes_estimate = self
12938 .computed_overlay_bytes_estimate
12939 .saturating_sub(freed_total);
12940 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
12941 }
12942
12943 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
12944 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
12945 }
12946
12947 fn record_vertex_value_to_overlay(
12948 &mut self,
12949 vertex_id: VertexId,
12950 value: &LiteralValue,
12951 computed_writes: Option<&mut ComputedWriteBuffer>,
12952 ) -> Result<(), ExcelError> {
12953 if !(self.config.arrow_storage_enabled
12954 && self.config.delta_overlay_enabled
12955 && self.config.write_formula_overlay_enabled)
12956 {
12957 return Ok(());
12958 }
12959 if self.computed_overlay_mirroring_disabled {
12960 return Ok(());
12961 }
12962 if !matches!(
12963 self.graph.get_vertex_kind(vertex_id),
12964 VertexKind::FormulaScalar | VertexKind::FormulaArray
12965 ) {
12966 return Ok(());
12967 }
12968 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
12969 return Ok(());
12970 };
12971 let Some(buffer) = computed_writes else {
12972 let format_id = self.derived_formats.get(&cell);
12974 self.write_computed_cell_0based(cell, value, format_id);
12975 return Ok(());
12976 };
12977 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12978 let date_system = self.arrow_sheet_date_system(&sheet_name);
12979 let ov = Self::literal_to_overlay_value(value, date_system);
12980 {
12981 let format_id = self.derived_formats.get(&cell);
12982 buffer.push_cell_with_format(
12983 cell.sheet_id,
12984 cell.coord.row(),
12985 cell.coord.col(),
12986 ov,
12987 format_id,
12988 );
12989 if self.should_flush_computed_write_buffer(buffer) {
12990 self.flush_computed_write_buffer(buffer)?;
12991 }
12992 }
12993 Ok(())
12994 }
12995
12996 #[inline]
12997 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
12998 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
12999 if cap == 0 {
13000 return false;
13001 }
13002 self.computed_overlay_bytes_estimate
13003 .saturating_add(buffer.estimated_bytes())
13004 > cap
13005 })
13006 }
13007
13008 pub fn overlay_memory_usage(&self) -> usize {
13010 self.computed_overlay_bytes_estimate
13011 }
13012
13013 #[cfg(test)]
13014 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
13015 self.overlay_compactions
13016 }
13017
13018 #[cfg(test)]
13019 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
13020 let mut total = 0usize;
13021 for sheet in &self.arrow_sheets.sheets {
13022 for column in &sheet.columns {
13023 for chunk in &column.chunks {
13024 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
13025 }
13026 for chunk in column.sparse_chunks.values() {
13027 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
13028 }
13029 }
13030 }
13031 self.computed_overlay_bytes_estimate = total;
13032 total
13033 }
13034
13035 fn resolve_sheet_locator_for_write(
13036 &mut self,
13037 loc: formualizer_common::SheetLocator<'_>,
13038 current_sheet: &str,
13039 ) -> Result<SheetId, ExcelError> {
13040 Ok(match loc {
13041 formualizer_common::SheetLocator::Id(id) => id,
13042 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
13043 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
13044 })
13045 }
13046
13047 fn resolve_sheet_locator_for_read(
13048 &self,
13049 loc: formualizer_common::SheetLocator<'_>,
13050 current_sheet: &str,
13051 ) -> Result<SheetId, ExcelError> {
13052 match loc {
13053 formualizer_common::SheetLocator::Id(id) => Ok(id),
13054 formualizer_common::SheetLocator::Name(name) => self
13055 .graph
13056 .sheet_id(name.as_ref())
13057 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
13058 formualizer_common::SheetLocator::Current => self
13059 .graph
13060 .sheet_id(current_sheet)
13061 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
13062 }
13063 }
13064
13065 pub fn set_cell_value(
13067 &mut self,
13068 sheet: &str,
13069 row: u32,
13070 col: u32,
13071 value: LiteralValue,
13072 ) -> Result<(), ExcelError> {
13073 self.observe_function_semantic_epoch()?;
13074 let sheet_existed = self.graph.sheet_id(sheet).is_some();
13075 let sheet_id = self.graph.sheet_id_mut(sheet);
13076 let cell_ref = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13077 let replaced = self
13078 .graph
13079 .get_vertex_id_for_address(&cell_ref)
13080 .filter(|&vertex| {
13081 matches!(
13082 self.graph.get_vertex_kind(vertex),
13083 VertexKind::FormulaScalar | VertexKind::FormulaArray
13084 )
13085 });
13086 let replaced_formula = replaced.is_some();
13087 if let Some(anchor) = replaced
13091 && self.graph.spill_registry_has_anchor(anchor)
13092 {
13093 self.clear_spill_projection_and_mirror(anchor, None);
13094 }
13095 self.graph.set_cell_value(sheet, row, col, value.clone())?;
13096 self.clear_cell_format_state(sheet, cell_ref);
13097 self.record_changed_cell(sheet, row, col);
13098 if !sheet_existed || replaced_formula {
13099 self.mark_topology_edited();
13100 }
13101 self.mirror_value_to_overlay(sheet, row, col, &value);
13103 self.mark_data_edited();
13105 Ok(())
13106 }
13107
13108 fn record_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
13110 let sheet_id = self.graph.sheet_id_mut(sheet);
13111 self.record_structural_change(StructuralScope::Cell {
13112 sheet: sheet_id,
13113 row: row.saturating_sub(1),
13114 col: col.saturating_sub(1),
13115 });
13116 }
13117
13118 fn record_change_for_event(&mut self, event: &ChangeEvent) {
13119 match event {
13120 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
13121 self.record_structural_change(StructuralScope::Cell {
13122 sheet: addr.sheet_id,
13123 row: addr.coord.row(),
13124 col: addr.coord.col(),
13125 });
13126 }
13127 ChangeEvent::SpillCommitted { new, .. } => {
13128 if let Some(scope) = Self::structural_scope_from_cells(&new.target_cells) {
13129 self.record_structural_change(scope);
13130 }
13131 }
13132 ChangeEvent::SpillCleared { old, .. } => {
13133 if let Some(scope) = Self::structural_scope_from_cells(&old.target_cells) {
13134 self.record_structural_change(scope);
13135 }
13136 }
13137 ChangeEvent::DefineName { .. }
13138 | ChangeEvent::UpdateName { .. }
13139 | ChangeEvent::DeleteName { .. }
13140 | ChangeEvent::NamedRangeAdjusted { .. } => {
13141 self.record_structural_change(StructuralScope::AllSheets);
13145 }
13146 ChangeEvent::VertexMoved { .. } | ChangeEvent::FormulaAdjusted { .. } => {
13147 }
13150 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
13151 self.record_structural_change(StructuralScope::Region(Region::whole_row(
13152 *sheet_id, *row0,
13153 )));
13154 }
13155 ChangeEvent::AddVertex { .. }
13156 | ChangeEvent::RemoveVertex { .. }
13157 | ChangeEvent::EdgeAdded { .. }
13158 | ChangeEvent::EdgeRemoved { .. }
13159 | ChangeEvent::CompoundStart { .. }
13160 | ChangeEvent::CompoundEnd { .. }
13161 | ChangeEvent::StagedFormulaCellChanged { .. } => {}
13162 }
13163 }
13164
13165 fn record_structural_change(&mut self, scope: StructuralScope) {
13166 if let StructuralScope::Cell { sheet, row, col } = scope {
13167 self.wake_spill_owner_of_entered_formula(sheet, row, col);
13168 }
13169 self.wake_spill_blocker_waiters(scope);
13170 self.invalidate_pending_spills(scope);
13171 }
13172
13173 fn wake_spill_blocker_waiters(&mut self, scope: StructuralScope) {
13177 if self.spill_blocker_waiters.is_empty() {
13178 return;
13179 }
13180 let woken = self.spill_blocker_waiters.take_affected(scope);
13181 let woken: Vec<VertexId> = woken
13182 .into_iter()
13183 .filter(|&vertex| {
13184 self.graph.vertex_exists(vertex)
13185 && matches!(
13186 self.graph.get_vertex_kind(vertex),
13187 VertexKind::FormulaScalar | VertexKind::FormulaArray
13188 )
13189 })
13190 .collect();
13191 if !woken.is_empty() {
13192 self.graph.mark_dirty_many(&woken);
13193 }
13194 }
13195
13196 fn wake_spill_owner_of_entered_formula(&mut self, sheet: SheetId, row: u32, col: u32) {
13200 let cell = self.graph.make_cell_ref_internal(sheet, row, col);
13201 if let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
13202 && self.graph.is_foreign_formula_cell(&cell, owner)
13203 {
13204 self.graph.mark_dirty_many(&[owner]);
13205 }
13206 }
13207
13208 fn structural_scope_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
13209 let first = cells.first()?;
13210 let sheet_id = first.sheet_id;
13211 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
13212 return Some(StructuralScope::OpaqueGlobal);
13213 }
13214 let mut row_start = first.coord.row();
13215 let mut row_end = row_start;
13216 let mut col_start = first.coord.col();
13217 let mut col_end = col_start;
13218 for cell in cells.iter().skip(1) {
13219 row_start = row_start.min(cell.coord.row());
13220 row_end = row_end.max(cell.coord.row());
13221 col_start = col_start.min(cell.coord.col());
13222 col_end = col_end.max(cell.coord.col());
13223 }
13224 Some(StructuralScope::Region(Region::rect(
13225 sheet_id, row_start, row_end, col_start, col_end,
13226 )))
13227 }
13228
13229 pub fn set_cell_value_ref(
13230 &mut self,
13231 cell: formualizer_common::SheetCellRef<'_>,
13232 current_sheet: &str,
13233 value: LiteralValue,
13234 ) -> Result<(), ExcelError> {
13235 let owned = cell.into_owned();
13236 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
13237 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
13238 self.set_cell_value(
13239 &sheet_name,
13240 owned.coord.row() + 1,
13241 owned.coord.col() + 1,
13242 value,
13243 )
13244 }
13245
13246 pub fn set_cell_formula_ref(
13247 &mut self,
13248 cell: formualizer_common::SheetCellRef<'_>,
13249 current_sheet: &str,
13250 ast: ASTNode,
13251 ) -> Result<(), ExcelError> {
13252 let owned = cell.into_owned();
13253 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
13254 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
13255 self.set_cell_formula(
13256 &sheet_name,
13257 owned.coord.row() + 1,
13258 owned.coord.col() + 1,
13259 ast,
13260 )
13261 }
13262
13263 pub fn get_cell_value_ref(
13264 &self,
13265 cell: formualizer_common::SheetCellRef<'_>,
13266 current_sheet: &str,
13267 ) -> Result<Option<LiteralValue>, ExcelError> {
13268 let owned = cell.into_owned();
13269 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
13270 let sheet_name = self.graph.sheet_name(sheet_id);
13271 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
13272 }
13273
13274 pub fn resolve_range_view_sheet_ref<'c>(
13275 &'c self,
13276 r: &formualizer_common::SheetRef<'_>,
13277 current_sheet: &str,
13278 ) -> Result<RangeView<'c>, ExcelError> {
13279 use formualizer_common::SheetLocator;
13280
13281 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
13282 match loc {
13283 SheetLocator::Current => Ok(None),
13284 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
13285 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
13286 }
13287 };
13288
13289 let rt = match r {
13290 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
13291 sheet: sheet_to_opt_name(cell.sheet.clone())?,
13292 row: cell.coord.row() + 1,
13293 col: cell.coord.col() + 1,
13294 row_abs: cell.coord.row_abs(),
13295 col_abs: cell.coord.col_abs(),
13296 },
13297 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
13298 sheet: sheet_to_opt_name(range.sheet.clone())?,
13299 start_row: range.start_row.map(|b| b.index + 1),
13300 start_col: range.start_col.map(|b| b.index + 1),
13301 end_row: range.end_row.map(|b| b.index + 1),
13302 end_col: range.end_col.map(|b| b.index + 1),
13303 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
13304 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
13305 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
13306 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
13307 },
13308 };
13309
13310 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
13311 }
13312
13313 pub fn set_cell_formula(
13315 &mut self,
13316 sheet: &str,
13317 row: u32,
13318 col: u32,
13319 ast: ASTNode,
13320 ) -> Result<(), ExcelError> {
13321 self.observe_function_semantic_epoch()?;
13322 let sheet_id = self.graph.sheet_id_mut(sheet);
13323 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13324 let ingested = {
13325 let mut pipeline = self.ingest_pipeline();
13326 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
13327 };
13328 self.graph.set_cell_formula_with_plan(
13329 sheet,
13330 row,
13331 col,
13332 ingested.ast_id,
13333 &ingested.dep_plan,
13334 ingested.dep_plan.volatile,
13335 ingested.dep_plan.dynamic,
13336 )?;
13337 self.clear_cell_format_state(sheet, placement);
13338 self.record_changed_cell(sheet, row, col);
13339
13340 self.clear_delta_overlay_cell(sheet, row, col);
13345
13346 self.mark_topology_edited();
13348 Ok(())
13349 }
13350
13351 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
13353 where
13354 I: IntoIterator<Item = (u32, u32, ASTNode)>,
13355 {
13356 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
13357 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
13358 let sheet_id = self.graph.sheet_id_mut(sheet);
13359 let ingested = {
13360 let mut pipeline = self.ingest_pipeline();
13361 let inputs = collected.into_iter().map(|(row, col, ast)| {
13362 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13363 (FormulaAstInput::Tree(ast), placement, None)
13364 });
13365 pipeline.ingest_batch(inputs)?
13366 };
13367 let planned: Vec<(u32, u32, AstNodeId, DependencyPlanRow)> = ingested
13368 .into_iter()
13369 .map(|formula| {
13370 (
13371 formula.placement.coord.row() + 1,
13372 formula.placement.coord.col() + 1,
13373 formula.ast_id,
13374 formula.dep_plan,
13375 )
13376 })
13377 .collect();
13378 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
13379 for (row, col) in edited_cells {
13380 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13381 self.clear_cell_format_state(sheet, cell);
13382 self.record_changed_cell(sheet, row, col);
13383 }
13384 if n > 0 {
13386 self.mark_topology_edited();
13387 }
13388 Ok(n)
13389 }
13390
13391 pub fn declare_dynamic_array_anchor(
13420 &mut self,
13421 sheet: &str,
13422 row: u32,
13423 col: u32,
13424 ) -> Result<(), ExcelError> {
13425 let not_a_formula = || {
13426 ExcelError::new(ExcelErrorKind::Ref)
13427 .with_message("Declared dynamic array anchor must be a formula cell")
13428 };
13429 if row == 0 || col == 0 {
13430 return Err(not_a_formula());
13431 }
13432 if self.get_staged_formula_text(sheet, row, col).is_some() {
13433 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
13434 "Declared dynamic array anchor is still staged; build the graph first",
13435 ));
13436 }
13437 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(not_a_formula)?;
13438 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13439 let vertex = self
13440 .graph
13441 .cell_vertex_mut(&cell)
13442 .filter(|v| self.graph.vertex_has_formula(*v))
13443 .ok_or_else(not_a_formula)?;
13444 if self.graph.declare_dynamic_anchor(vertex, cell) {
13445 self.graph.mark_dirty_many(&[vertex]);
13446 self.mark_topology_edited();
13447 }
13448 Ok(())
13449 }
13450
13451 pub fn declare_fixed_array_formula(
13457 &mut self,
13458 sheet: &str,
13459 row: u32,
13460 col: u32,
13461 rows: u32,
13462 cols: u32,
13463 ) -> Result<(), ExcelError> {
13464 if self.config.family_execution {
13465 return Err(ExcelError::new(ExcelErrorKind::Ref)
13466 .with_message("Fixed arrays require family_execution = false"));
13467 }
13468 if row == 0
13469 || col == 0
13470 || rows == 0
13471 || cols == 0
13472 || row.checked_add(rows - 1).is_none_or(|r| r > 1_048_576)
13473 || col.checked_add(cols - 1).is_none_or(|c| c > 16_384)
13474 || u64::from(rows) * u64::from(cols) > u64::from(self.config.spill.max_spill_cells)
13475 {
13476 return Err(ExcelError::new(ExcelErrorKind::Ref)
13477 .with_message("Invalid or over-cap fixed array extent"));
13478 }
13479 if self.get_staged_formula_text(sheet, row, col).is_some() {
13480 return Err(ExcelError::new(ExcelErrorKind::Ref)
13481 .with_message("Fixed array formula is still staged; build the graph first"));
13482 }
13483 let sheet_id = self.graph.sheet_id(sheet).ok_or_else(|| {
13484 ExcelError::new(ExcelErrorKind::Ref).with_message("Unknown fixed array sheet")
13485 })?;
13486 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13487 let vertex = self
13488 .graph
13489 .cell_vertex_mut(&cell)
13490 .filter(|v| self.graph.vertex_has_formula(*v))
13491 .ok_or_else(|| {
13492 ExcelError::new(ExcelErrorKind::Ref)
13493 .with_message("Fixed array anchor must be a formula cell")
13494 })?;
13495 if (rows == 1 && cols == 1 && self.graph.fixed_single_arrays.contains(&vertex))
13496 || self.graph.fixed_array_shapes.get(&vertex) == Some(&(rows, cols))
13497 {
13498 return Ok(());
13499 }
13500 if self.graph.fixed_single_arrays.contains(&vertex)
13501 || self.graph.spill_registry_has_anchor(vertex)
13502 || self.graph.is_current_declared_dynamic_anchor(vertex)
13503 {
13504 return Err(ExcelError::new(ExcelErrorKind::Ref)
13505 .with_message("Array declaration already exists; replace the formula first"));
13506 }
13507 if rows == 1 && cols == 1 {
13508 self.graph.fixed_single_arrays.insert(vertex);
13509 } else {
13510 let mut targets = Vec::with_capacity((rows * cols) as usize);
13511 for r in 0..rows {
13512 for c in 0..cols {
13513 targets.push(self.graph.make_cell_ref(sheet, row + r, col + c));
13514 }
13515 }
13516 self.graph.plan_spill_region(vertex, &targets)?;
13517 self.graph
13519 .commit_spill_region_atomic_with_fault(vertex, targets, vec![], None)?;
13520 self.graph.fixed_array_shapes.insert(vertex, (rows, cols));
13521 }
13522 self.graph.mark_dirty_many(&[vertex]);
13523 self.mark_topology_edited();
13524 Ok(())
13525 }
13526
13527 #[inline]
13528 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
13529 match v {
13530 LiteralValue::Empty => None,
13531 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
13532 other => Some(other),
13533 }
13534 }
13535
13536 fn materialize_temporal_egress(
13537 value: LiteralValue,
13538 class: Option<&formualizer_common::numfmt::FormatClass>,
13539 policy: crate::engine::TemporalEgress,
13540 date_system: crate::engine::DateSystem,
13541 ) -> LiteralValue {
13542 use formualizer_common::numfmt::FormatClass;
13543 if policy == crate::engine::TemporalEgress::Serial {
13544 return value;
13545 }
13546 let LiteralValue::Number(serial) = value else {
13547 return value;
13548 };
13549 match class {
13550 Some(FormatClass::Date) => {
13551 formualizer_common::try_serial_to_date_for(date_system, serial)
13552 .map(LiteralValue::Date)
13553 .unwrap_or(LiteralValue::Number(serial))
13554 }
13555 Some(FormatClass::DateTime) => {
13556 formualizer_common::try_serial_to_datetime_for(date_system, serial)
13557 .map(LiteralValue::DateTime)
13558 .unwrap_or(LiteralValue::Number(serial))
13559 }
13560 Some(FormatClass::Time) => {
13561 let seconds = (serial.rem_euclid(1.0) * 86_400.0).round() as u32 % 86_400;
13562 chrono::NaiveTime::from_num_seconds_from_midnight_opt(seconds, 0)
13563 .map(LiteralValue::Time)
13564 .unwrap_or(LiteralValue::Number(serial))
13565 }
13566 Some(FormatClass::Duration) => {
13567 let nanos = (serial * 86_400.0 * 1_000_000_000.0).round();
13568 if nanos.is_finite() && nanos >= i64::MIN as f64 && nanos <= i64::MAX as f64 {
13569 LiteralValue::Duration(chrono::Duration::nanoseconds(nanos as i64))
13570 } else {
13571 LiteralValue::Number(serial)
13572 }
13573 }
13574 _ => LiteralValue::Number(serial),
13575 }
13576 }
13577
13578 pub(crate) fn effective_format_id(
13579 &self,
13580 sheet: &str,
13581 row: u32,
13582 col: u32,
13583 ) -> Option<crate::format::FormatId> {
13584 let arrow = self.arrow_sheets.sheet(sheet).and_then(|arrow| {
13585 arrow.format_id(
13586 row.saturating_sub(1) as usize,
13587 col.saturating_sub(1) as usize,
13588 )
13589 });
13590 arrow.or_else(|| {
13591 let sheet_id = self.graph.sheet_id(sheet)?;
13592 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
13593 self.derived_formats.get(&cell)
13594 })
13595 }
13596
13597 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13599 let raw = self.read_cell_value(sheet, row, col)?;
13600 let format = self.effective_format_id(sheet, row, col);
13601 let class = format.and_then(|id| self.format_registry.class(id));
13602 Self::normalize_public_cell_read(Self::materialize_temporal_egress(
13603 raw,
13604 class,
13605 self.config.temporal_egress,
13606 self.config.date_system,
13607 ))
13608 }
13609
13610 pub fn get_range_values(
13612 &self,
13613 sheet: &str,
13614 sr: u32,
13615 sc: u32,
13616 er: u32,
13617 ec: u32,
13618 ) -> Vec<Vec<LiteralValue>> {
13619 let height = er.saturating_sub(sr).saturating_add(1) as usize;
13620 let width = ec.saturating_sub(sc).saturating_add(1) as usize;
13621 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13622 return vec![vec![LiteralValue::Empty; width]; height];
13623 };
13624 let view = asheet.range_view(
13625 sr.saturating_sub(1) as usize,
13626 sc.saturating_sub(1) as usize,
13627 er.saturating_sub(1) as usize,
13628 ec.saturating_sub(1) as usize,
13629 );
13630 let sheet_id = self.graph.sheet_id(sheet);
13631 let derived_formats = &self.derived_formats;
13632 let has_derived_formats =
13633 sheet_id.is_some_and(|sheet_id| derived_formats.any(|cell| cell.sheet_id == sheet_id));
13634 let mut out = Vec::with_capacity(height);
13635 if !asheet.has_formats() && !has_derived_formats {
13636 for rr in 0..height {
13637 let mut row = Vec::with_capacity(width);
13638 for cc in 0..width {
13639 row.push(view.get_cell(rr, cc));
13640 }
13641 out.push(row);
13642 }
13643 return out;
13644 }
13645 let format_registry = &self.format_registry;
13646 for rr in 0..height {
13647 let mut row = Vec::with_capacity(width);
13648 for cc in 0..width {
13649 let raw = view.get_cell(rr, cc);
13650 let row0 = sr.saturating_sub(1).saturating_add(rr as u32);
13651 let col0 = sc.saturating_sub(1).saturating_add(cc as u32);
13652 let format = asheet.format_id(row0 as usize, col0 as usize).or_else(|| {
13653 let cell = CellRef::new(sheet_id?, Coord::new(row0, col0, true, true));
13654 derived_formats.get(&cell)
13655 });
13656 let class = format.and_then(|id| format_registry.class(id));
13657 row.push(Self::materialize_temporal_egress(
13658 raw,
13659 class,
13660 self.config.temporal_egress,
13661 self.config.date_system,
13662 ));
13663 }
13664 out.push(row);
13665 }
13666 out
13667 }
13668
13669 fn declared_anchor_holds_scalar(&self, anchor: CellRef) -> bool {
13675 let sheet = self.graph.sheet_name(anchor.sheet_id);
13676 if sheet.is_empty() {
13677 return false;
13678 }
13679 matches!(
13680 self.read_cell_value(sheet, anchor.coord.row() + 1, anchor.coord.col() + 1),
13681 Some(value) if !matches!(
13682 value,
13683 LiteralValue::Error(_)
13684 | LiteralValue::Array(_)
13685 | LiteralValue::Pending
13686 | LiteralValue::Empty
13687 )
13688 )
13689 }
13690
13691 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
13692 let asheet = self.sheet_store().sheet(sheet)?;
13693 let r0 = row.saturating_sub(1) as usize;
13694 let c0 = col.saturating_sub(1) as usize;
13695 let v = asheet.get_cell_value(r0, c0);
13696 if matches!(v, LiteralValue::Empty) {
13697 None
13698 } else {
13699 Some(v)
13700 }
13701 }
13702
13703 pub(crate) fn read_range_values(
13705 &self,
13706 sheet: &str,
13707 sr: u32,
13708 sc: u32,
13709 er: u32,
13710 ec: u32,
13711 ) -> RangeView<'_> {
13712 let Some(asheet) = self.sheet_store().sheet(sheet) else {
13713 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
13714 };
13715 if er < sr || ec < sc {
13716 return asheet.range_view(1, 1, 0, 0);
13717 }
13718 let sr0 = sr.saturating_sub(1) as usize;
13719 let sc0 = sc.saturating_sub(1) as usize;
13720 let er0 = er.saturating_sub(1) as usize;
13721 let ec0 = ec.saturating_sub(1) as usize;
13722 asheet.range_view(sr0, sc0, er0, ec0)
13723 }
13724
13725 pub fn get_cell(
13727 &self,
13728 sheet: &str,
13729 row: u32,
13730 col: u32,
13731 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
13732 let v = self.get_cell_value(sheet, row, col);
13733 let sheet_id = self.graph.sheet_id(sheet)?;
13734 let coord = Coord::from_excel(row, col, true, true);
13735 let cell = CellRef::new(sheet_id, coord);
13736 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
13737 let ast = self.graph.get_formula(vid);
13738 Some((ast, v))
13739 } else if v.is_some() || self.graph.had_legacy_cell_vertex(&cell) {
13740 Some((None, v))
13744 } else {
13745 None
13746 }
13747 }
13748
13749 pub fn begin_batch(&mut self) {
13751 self.graph.begin_batch();
13752 }
13753
13754 pub fn end_batch(&mut self) {
13756 self.graph.end_batch();
13757 }
13758
13759 pub fn begin_deferred_dirty(&mut self) {
13768 self.graph.begin_deferred_dirty();
13769 }
13770
13771 pub fn end_deferred_dirty(&mut self) {
13774 let _ = self.graph.end_deferred_dirty();
13775 }
13776
13777 pub fn dirty_propagation_visits(&self) -> u64 {
13781 self.graph.dirty_propagation_visits()
13782 }
13783
13784 #[inline]
13787 fn record_cell_if_changed(
13788 delta: &mut DeltaCollector,
13789 cell: &CellRef,
13790 old: &LiteralValue,
13791 new: &LiteralValue,
13792 ) {
13793 if old != new {
13794 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13795 }
13796 }
13797
13798 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
13799 self.observe_evaluation_resource_request(EvaluationRequestKind::Vertex, |engine| {
13800 engine.observe_function_semantic_epoch()?;
13801 engine.resource_checkpoint(1)?;
13803 if !engine.graph.vertex_exists(vertex_id) {
13804 return engine.evaluate_vertex_impl(vertex_id, None);
13805 }
13806 let is_formula = matches!(
13807 engine.graph.get_vertex_kind(vertex_id),
13808 VertexKind::FormulaScalar | VertexKind::FormulaArray
13809 );
13810 if is_formula {
13811 engine.begin_evaluation_request();
13812 #[cfg(any(test, feature = "legacy_oracle"))]
13813 engine.graph.flush_pending_edge_deltas();
13814 let roots = [crate::engine::target_preparation::TargetProducer::Legacy(
13815 vertex_id,
13816 )];
13817 engine.evaluate_legacy_target_roots(&roots, None)?;
13818 }
13819 engine.evaluate_vertex_impl(vertex_id, None)
13820 })
13821 }
13822
13823 fn publish_oversized_spill(
13825 &mut self,
13826 vertex_id: VertexId,
13827 error: ExcelError,
13828 mut delta: Option<&mut DeltaCollector>,
13829 ) -> LiteralValue {
13830 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
13831 let anchor = self
13832 .graph
13833 .get_cell_ref(vertex_id)
13834 .expect("cell ref for vertex");
13835 let spill_val = LiteralValue::Error(error);
13836 if let Some(d) = delta {
13837 let old = self
13838 .read_cell_value(
13839 self.graph.sheet_name(anchor.sheet_id),
13840 anchor.coord.row() + 1,
13841 anchor.coord.col() + 1,
13842 )
13843 .unwrap_or(LiteralValue::Empty);
13844 if old != spill_val {
13845 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
13846 }
13847 }
13848 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
13849 if self.config.arrow_storage_enabled
13850 && self.config.delta_overlay_enabled
13851 && self.config.write_formula_overlay_enabled
13852 {
13853 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
13854 self.mirror_value_to_computed_overlay(
13855 &sheet_name,
13856 anchor.coord.row() + 1,
13857 anchor.coord.col() + 1,
13858 &spill_val,
13859 );
13860 }
13861 spill_val
13862 }
13863
13864 fn evaluate_vertex_impl(
13865 &mut self,
13866 vertex_id: VertexId,
13867 delta: Option<&mut DeltaCollector>,
13868 ) -> Result<LiteralValue, ExcelError> {
13869 if !self.graph.vertex_exists(vertex_id) {
13873 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
13874 .with_message(format!("Vertex not found: {vertex_id:?}")));
13875 }
13876 if (self.active_resource_ledger.is_some()
13877 || self.graph.fixed_array_shapes.contains_key(&vertex_id))
13878 && matches!(
13879 self.graph.get_vertex_kind(vertex_id),
13880 VertexKind::FormulaScalar | VertexKind::FormulaArray
13881 )
13882 {
13883 let value = self
13884 .evaluate_vertex_immutable(vertex_id)
13885 .unwrap_or_else(LiteralValue::Error);
13886 let effects = self.plan_vertex_effects(vertex_id, value.clone())?;
13887 self.resource_checkpoint(0)?;
13889 let mut delta = delta;
13890 for effect in &effects {
13891 self.apply_effect_with_computed_writes(effect, delta.as_deref_mut(), None, None)?;
13892 }
13893 return Ok(value);
13894 }
13895
13896 let mut delta = delta;
13897
13898 let kind = self.graph.get_vertex_kind(vertex_id);
13900 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
13901
13902 let view = match kind {
13903 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
13904 if let Some(view) = self.graph.formula_view(vertex_id) {
13905 view
13906 } else {
13907 return Ok(LiteralValue::Number(0.0));
13908 }
13909 }
13910 VertexKind::Empty | VertexKind::Cell => {
13911 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
13912 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
13913 let row = cell_ref.coord.row() + 1;
13914 let col = cell_ref.coord.col() + 1;
13915 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
13916 return Ok(v);
13917 }
13918 }
13919 return Ok(LiteralValue::Number(0.0));
13920 }
13921 VertexKind::NamedScalar => {
13922 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
13923 return Ok(value);
13924 }
13925 VertexKind::NamedArray => {
13926 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
13927 return Ok(value);
13928 }
13929 VertexKind::InfiniteRange
13930 | VertexKind::Range
13931 | VertexKind::External
13932 | VertexKind::Table => {
13933 return Ok(LiteralValue::Number(0.0));
13935 }
13936 };
13937
13938 let sheet_name = self.graph.sheet_name(sheet_id);
13940 let cell_ref = self
13941 .graph
13942 .get_cell_ref(vertex_id)
13943 .expect("cell ref for vertex");
13944 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
13945
13946 let result = interpreter.evaluate_formula_view(
13947 view,
13948 self.graph.data_store(),
13949 self.graph.sheet_reg(),
13950 );
13951
13952 match result {
13954 Ok(cv) => {
13955 let derived_format = cv.format_id();
13956 self.record_derived_format(vertex_id, derived_format);
13957 let oversized_range = if matches!(cv, crate::traits::CalcValue::Range(_))
13958 && self.graph.fixed_single_arrays.contains(&vertex_id)
13959 {
13960 None
13961 } else {
13962 crate::engine::result_finalization::range_spill_error(
13963 &cv,
13964 self.config.spill.max_spill_cells,
13965 )
13966 };
13967 let is_oversized_range = oversized_range.is_some();
13968 let result_literal = if let Some(error) = oversized_range {
13969 drop(cv);
13970 LiteralValue::Error(error)
13971 } else {
13972 self.materialize_formula_result(vertex_id, cv)
13973 };
13974 let output_sheet_name = sheet_name.to_string();
13975 self.write_computed_overlay_format_0based(
13976 &output_sheet_name,
13977 cell_ref.coord.row(),
13978 cell_ref.coord.col(),
13979 derived_format,
13980 );
13981 if is_oversized_range {
13982 let LiteralValue::Error(error) = result_literal else {
13983 unreachable!()
13984 };
13985 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
13986 return Ok(self.publish_oversized_spill(
13987 vertex_id,
13988 error,
13989 delta.as_deref_mut(),
13990 ));
13991 }
13992 match result_literal {
13993 LiteralValue::Array(rows) => {
13994 self.graph
13996 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
13997 let anchor = self
13999 .graph
14000 .get_cell_ref(vertex_id)
14001 .expect("cell ref for vertex");
14002 let sheet_id = anchor.sheet_id;
14003 let h = rows.len() as u32;
14004 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
14005
14006 let spill_cells = (h as u64).saturating_mul(w as u64);
14008 if spill_cells > self.config.spill.max_spill_cells as u64 {
14009 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
14010 .with_message("SpillTooLarge")
14011 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
14012 expected_rows: h,
14013 expected_cols: w,
14014 });
14015 return Ok(self.publish_oversized_spill(
14016 vertex_id,
14017 spill_err,
14018 delta.as_deref_mut(),
14019 ));
14020 }
14021 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);
14025 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
14026 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
14027 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
14028 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
14029 .with_message("Spill exceeds sheet bounds")
14030 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
14031 expected_rows: h,
14032 expected_cols: w,
14033 });
14034 let spill_val = LiteralValue::Error(spill_err.clone());
14035 if let Some(d) = delta.as_deref_mut() {
14036 let old = self
14037 .read_cell_value(
14038 self.graph.sheet_name(anchor.sheet_id),
14039 anchor.coord.row() + 1,
14040 anchor.coord.col() + 1,
14041 )
14042 .unwrap_or(LiteralValue::Empty);
14043 if old != spill_val {
14044 d.record_cell(
14045 anchor.sheet_id,
14046 anchor.coord.row(),
14047 anchor.coord.col(),
14048 );
14049 }
14050 }
14051 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
14052 if self.config.arrow_storage_enabled
14053 && self.config.delta_overlay_enabled
14054 && self.config.write_formula_overlay_enabled
14055 {
14056 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
14057 self.mirror_value_to_computed_overlay(
14058 &sheet_name,
14059 anchor.coord.row() + 1,
14060 anchor.coord.col() + 1,
14061 &spill_val,
14062 );
14063 }
14064 return Ok(spill_val);
14065 }
14066 let mut targets = Vec::new();
14067 for r in 0..h {
14068 for c in 0..w {
14069 targets.push(self.graph.make_cell_ref_internal(
14070 sheet_id,
14071 anchor.coord.row() + r,
14072 anchor.coord.col() + c,
14073 ));
14074 }
14075 }
14076
14077 match self.spill_mgr.reserve(
14079 vertex_id,
14080 anchor,
14081 SpillShape { rows: h, cols: w },
14082 SpillMeta {
14083 epoch: self.recalc_epoch,
14084 config: self.config.spill,
14085 },
14086 ) {
14087 Ok(()) => {
14088 if let Err(e) = self.commit_spill_and_mirror(
14092 vertex_id,
14093 &targets,
14094 rows.clone(),
14095 delta.as_deref_mut(),
14096 ) {
14097 if e.kind != ExcelErrorKind::Spill {
14098 return Err(e);
14099 }
14100 self.clear_spill_projection_and_mirror(
14102 vertex_id,
14103 delta.as_deref_mut(),
14104 );
14105 if let Some(d) = delta.as_deref_mut() {
14106 let old = self
14107 .read_cell_value(
14108 self.graph.sheet_name(anchor.sheet_id),
14109 anchor.coord.row() + 1,
14110 anchor.coord.col() + 1,
14111 )
14112 .unwrap_or(LiteralValue::Empty);
14113 let new = LiteralValue::Error(e.clone());
14114 if old != new {
14115 d.record_cell(
14116 anchor.sheet_id,
14117 anchor.coord.row(),
14118 anchor.coord.col(),
14119 );
14120 }
14121 }
14122 let err_val = LiteralValue::Error(e.clone());
14123 self.graph.update_vertex_value_ref(vertex_id, &err_val);
14124 if self.config.arrow_storage_enabled
14125 && self.config.delta_overlay_enabled
14126 && self.config.write_formula_overlay_enabled
14127 {
14128 let sheet_name =
14129 self.graph.sheet_name(anchor.sheet_id).to_string();
14130 self.mirror_value_to_computed_overlay(
14131 &sheet_name,
14132 anchor.coord.row() + 1,
14133 anchor.coord.col() + 1,
14134 &err_val,
14135 );
14136 }
14137 return Ok(err_val);
14138 }
14139 let top_left = rows
14141 .first()
14142 .and_then(|r| r.first())
14143 .cloned()
14144 .unwrap_or(LiteralValue::Empty);
14145 self.graph.update_vertex_value_ref(vertex_id, &top_left);
14146 Ok(top_left)
14147 }
14148 Err(e) => {
14149 self.clear_spill_projection_and_mirror(
14150 vertex_id,
14151 delta.as_deref_mut(),
14152 );
14153 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
14154 .with_message(
14155 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
14156 )
14157 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
14158 expected_rows: h,
14159 expected_cols: w,
14160 });
14161 let spill_val = LiteralValue::Error(spill_err.clone());
14162 if let Some(d) = delta.as_deref_mut() {
14163 let old = self
14164 .read_cell_value(
14165 self.graph.sheet_name(anchor.sheet_id),
14166 anchor.coord.row() + 1,
14167 anchor.coord.col() + 1,
14168 )
14169 .unwrap_or(LiteralValue::Empty);
14170 if old != spill_val {
14171 d.record_cell(
14172 anchor.sheet_id,
14173 anchor.coord.row(),
14174 anchor.coord.col(),
14175 );
14176 }
14177 }
14178 self.graph.update_vertex_value_ref(vertex_id, &spill_val);
14179 if self.config.arrow_storage_enabled
14180 && self.config.delta_overlay_enabled
14181 && self.config.write_formula_overlay_enabled
14182 {
14183 let sheet_name =
14184 self.graph.sheet_name(anchor.sheet_id).to_string();
14185 self.mirror_value_to_computed_overlay(
14186 &sheet_name,
14187 anchor.coord.row() + 1,
14188 anchor.coord.col() + 1,
14189 &spill_val,
14190 );
14191 }
14192 Ok(spill_val)
14193 }
14194 }
14195 }
14196 other => {
14197 let spill_cells = self.graph.spill_cells_to_clear(vertex_id);
14199 if let Some(d) = delta.as_deref_mut()
14200 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
14201 {
14202 if spill_cells.is_empty() {
14203 let old = self
14204 .read_cell_value(
14205 self.graph.sheet_name(anchor.sheet_id),
14206 anchor.coord.row() + 1,
14207 anchor.coord.col() + 1,
14208 )
14209 .unwrap_or(LiteralValue::Empty);
14210 if old != other {
14211 d.record_cell(
14212 anchor.sheet_id,
14213 anchor.coord.row(),
14214 anchor.coord.col(),
14215 );
14216 }
14217 } else {
14218 for cell in spill_cells.iter() {
14219 let sheet_name = self.graph.sheet_name(cell.sheet_id);
14220 let old = self
14221 .get_cell_value(
14222 sheet_name,
14223 cell.coord.row() + 1,
14224 cell.coord.col() + 1,
14225 )
14226 .unwrap_or(LiteralValue::Empty);
14227 let new = if cell.sheet_id == anchor.sheet_id
14228 && cell.coord.row() == anchor.coord.row()
14229 && cell.coord.col() == anchor.coord.col()
14230 {
14231 other.clone()
14232 } else {
14233 LiteralValue::Empty
14234 };
14235 Self::record_cell_if_changed(d, cell, &old, &new);
14236 }
14237 }
14238 }
14239 self.graph.clear_spill_region(vertex_id);
14240 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
14241 self.record_structural_change(scope);
14242 }
14243 if self.config.arrow_storage_enabled
14244 && self.config.delta_overlay_enabled
14245 && self.config.write_formula_overlay_enabled
14246 {
14247 let empty = LiteralValue::Empty;
14248 for cell in spill_cells.iter() {
14249 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
14250 self.mirror_value_to_computed_overlay(
14251 &sheet_name,
14252 cell.coord.row() + 1,
14253 cell.coord.col() + 1,
14254 &empty,
14255 );
14256 }
14257 }
14258 self.graph.update_vertex_value_ref(vertex_id, &other);
14259 if self.config.arrow_storage_enabled
14261 && self.config.delta_overlay_enabled
14262 && self.config.write_formula_overlay_enabled
14263 {
14264 let anchor = self
14265 .graph
14266 .get_cell_ref(vertex_id)
14267 .expect("cell ref for vertex");
14268 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
14269 self.mirror_value_to_computed_overlay(
14270 &sheet_name,
14271 anchor.coord.row() + 1,
14272 anchor.coord.col() + 1,
14273 &other,
14274 );
14275 }
14276 Ok(other)
14277 }
14278 }
14279 }
14280 Err(e) => {
14281 let spill_cells = self.graph.spill_cells_to_clear(vertex_id);
14284 let err_val = LiteralValue::Error(e.clone());
14285 if let Some(d) = delta
14286 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
14287 {
14288 if spill_cells.is_empty() {
14289 let old = self
14290 .read_cell_value(
14291 self.graph.sheet_name(anchor.sheet_id),
14292 anchor.coord.row() + 1,
14293 anchor.coord.col() + 1,
14294 )
14295 .unwrap_or(LiteralValue::Empty);
14296 if old != err_val {
14297 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
14298 }
14299 } else {
14300 for cell in spill_cells.iter() {
14301 let sheet_name = self.graph.sheet_name(cell.sheet_id);
14302 let old = self
14303 .get_cell_value(
14304 sheet_name,
14305 cell.coord.row() + 1,
14306 cell.coord.col() + 1,
14307 )
14308 .unwrap_or(LiteralValue::Empty);
14309 let new = if cell.sheet_id == anchor.sheet_id
14310 && cell.coord.row() == anchor.coord.row()
14311 && cell.coord.col() == anchor.coord.col()
14312 {
14313 err_val.clone()
14314 } else {
14315 LiteralValue::Empty
14316 };
14317 Self::record_cell_if_changed(d, cell, &old, &new);
14318 }
14319 }
14320 }
14321 self.graph.clear_spill_region(vertex_id);
14322 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
14323 self.record_structural_change(scope);
14324 }
14325 if self.config.arrow_storage_enabled
14326 && self.config.delta_overlay_enabled
14327 && self.config.write_formula_overlay_enabled
14328 {
14329 let empty = LiteralValue::Empty;
14330 for cell in spill_cells.iter() {
14331 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
14332 self.mirror_value_to_computed_overlay(
14333 &sheet_name,
14334 cell.coord.row() + 1,
14335 cell.coord.col() + 1,
14336 &empty,
14337 );
14338 }
14339 }
14340 self.graph.update_vertex_value_ref(vertex_id, &err_val);
14341 if self.config.arrow_storage_enabled
14342 && self.config.delta_overlay_enabled
14343 && self.config.write_formula_overlay_enabled
14344 {
14345 let anchor = self
14346 .graph
14347 .get_cell_ref(vertex_id)
14348 .expect("cell ref for vertex");
14349 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
14350 self.mirror_value_to_computed_overlay(
14351 &sheet_name,
14352 anchor.coord.row() + 1,
14353 anchor.coord.col() + 1,
14354 &err_val,
14355 );
14356 }
14357 Ok(err_val)
14358 }
14359 }
14360 }
14361
14362 fn evaluate_named_scalar(
14363 &mut self,
14364 vertex_id: VertexId,
14365 sheet_id: SheetId,
14366 ) -> Result<LiteralValue, ExcelError> {
14367 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
14368 ExcelError::new(ExcelErrorKind::Name)
14369 .with_message("Named range metadata missing".to_string())
14370 })?;
14371
14372 match &named_range.definition {
14373 NamedDefinition::Cell(cell_ref) => {
14374 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
14375 let row = cell_ref.coord.row() + 1;
14376 let col = cell_ref.coord.col() + 1;
14377
14378 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
14379 && matches!(
14380 self.graph.get_vertex_kind(dep_vertex),
14381 VertexKind::FormulaScalar | VertexKind::FormulaArray
14382 )
14383 {
14384 let value = self.evaluate_vertex(dep_vertex)?;
14386 self.graph.update_vertex_value_ref(vertex_id, &value);
14387 Ok(value)
14388 } else {
14389 let value = self
14390 .get_cell_value(sheet_name, row, col)
14391 .unwrap_or(LiteralValue::Empty);
14392 self.graph.update_vertex_value_ref(vertex_id, &value);
14393 Ok(value)
14394 }
14395 }
14396 NamedDefinition::Literal(v) => {
14397 let out = v.clone();
14398 self.graph.update_vertex_value_ref(vertex_id, &out);
14399 Ok(out)
14400 }
14401 NamedDefinition::Formula { ast, .. } => {
14402 let context_sheet = match named_range.scope {
14403 NameScope::Sheet(id) => id,
14404 NameScope::Workbook => sheet_id,
14405 };
14406 let sheet_name = self.graph.sheet_name(context_sheet);
14407 let cell_ref = self
14408 .graph
14409 .get_cell_ref(vertex_id)
14410 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
14411 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
14412 match interpreter.evaluate_ast(ast) {
14413 Ok(cv) => {
14414 let value = cv.into_literal();
14415 match value {
14416 LiteralValue::Array(_) => {
14417 let err = ExcelError::new(ExcelErrorKind::NImpl)
14418 .with_message("Array result in scalar named range".to_string());
14419 let err_val = LiteralValue::Error(err.clone());
14420 self.graph.update_vertex_value_ref(vertex_id, &err_val);
14421 Ok(err_val)
14422 }
14423 other => {
14424 self.graph.update_vertex_value_ref(vertex_id, &other);
14425 Ok(other)
14426 }
14427 }
14428 }
14429 Err(err) => {
14430 let err_val = LiteralValue::Error(err.clone());
14431 self.graph.update_vertex_value_ref(vertex_id, &err_val);
14432 Ok(err_val)
14433 }
14434 }
14435 }
14436 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
14437 .with_message("Range-valued name evaluated as scalar".to_string())),
14438 }
14439 }
14440
14441 fn evaluate_named_array(
14442 &mut self,
14443 vertex_id: VertexId,
14444 sheet_id: SheetId,
14445 ) -> Result<LiteralValue, ExcelError> {
14446 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
14447 ExcelError::new(ExcelErrorKind::Name)
14448 .with_message("Named range metadata missing".to_string())
14449 })?;
14450
14451 let out = match &named_range.definition {
14452 NamedDefinition::Range(range_ref) => {
14453 if range_ref.start.sheet_id != range_ref.end.sheet_id {
14454 return Err(ExcelError::new(ExcelErrorKind::Ref)
14455 .with_message("Named range cannot span sheets".to_string()));
14456 }
14457
14458 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
14459 let sr0 = range_ref.start.coord.row();
14460 let sc0 = range_ref.start.coord.col();
14461 let er0 = range_ref.end.coord.row();
14462 let ec0 = range_ref.end.coord.col();
14463 if sr0 > er0 || sc0 > ec0 {
14464 return Err(ExcelError::new(ExcelErrorKind::Ref)
14465 .with_message("Invalid named range bounds".to_string()));
14466 }
14467
14468 let h = (er0 - sr0 + 1) as usize;
14469 let w = (ec0 - sc0 + 1) as usize;
14470 let cell_count = (h as u64).saturating_mul(w as u64);
14471 if cell_count > self.config.spill.max_spill_cells as u64 {
14472 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
14473 "Named range too large to materialize as an array".to_string(),
14474 ));
14475 }
14476
14477 let mut rows = Vec::with_capacity(h);
14478 for r0 in sr0..=er0 {
14479 let mut row = Vec::with_capacity(w);
14480 for c0 in sc0..=ec0 {
14481 let v = self
14482 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
14483 .unwrap_or(LiteralValue::Empty);
14484 row.push(v);
14485 }
14486 rows.push(row);
14487 }
14488 LiteralValue::Array(rows)
14489 }
14490 NamedDefinition::Cell(cell_ref) => {
14491 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
14492 let row = cell_ref.coord.row() + 1;
14493 let col = cell_ref.coord.col() + 1;
14494 let v = self
14495 .get_cell_value(sheet_name, row, col)
14496 .unwrap_or(LiteralValue::Empty);
14497 LiteralValue::Array(vec![vec![v]])
14498 }
14499 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
14500 NamedDefinition::Formula { ast, .. } => {
14501 let context_sheet = match named_range.scope {
14502 NameScope::Sheet(id) => id,
14503 NameScope::Workbook => sheet_id,
14504 };
14505 let sheet_name = self.graph.sheet_name(context_sheet);
14506 let cell_ref = self
14507 .graph
14508 .get_cell_ref(vertex_id)
14509 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
14510 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
14511 match interpreter.evaluate_ast(ast) {
14512 Ok(cv) => {
14513 let v = cv.into_literal();
14514 match v {
14515 LiteralValue::Array(_) => v,
14516 other => LiteralValue::Array(vec![vec![other]]),
14517 }
14518 }
14519 Err(err) => LiteralValue::Error(err),
14520 }
14521 }
14522 };
14523
14524 self.graph.update_vertex_value_ref(vertex_id, &out);
14525 Ok(out)
14526 }
14527
14528 fn replan_exhausted_error(&self, limit: usize, context: &str) -> ExcelError {
14529 crate::engine::ResourceLedgerError::Exhausted(
14530 formualizer_common::ResourceExhaustionDetail {
14531 reason: formualizer_common::ResourceExhaustionReason::WorkUnits,
14532 limit: limit as u64,
14533 observed: limit.saturating_add(1) as u64,
14534 request_id: self
14535 .active_evaluation_resource_request
14536 .as_ref()
14537 .map(|stats| stats.request_id),
14538 },
14539 )
14540 .into_excel_error()
14541 .with_message(format!("{context} did not converge after {limit} replans"))
14542 }
14543
14544 fn transient_target_preparation_stale(error: &ExcelError) -> bool {
14545 matches!(
14546 &error.extra,
14547 formualizer_common::ExcelErrorExtra::PreparationStale {
14548 reason: formualizer_common::PreparationStaleReason::Semantic
14549 | formualizer_common::PreparationStaleReason::Provider
14550 }
14551 )
14552 }
14553
14554 fn prepare_graph_for_routed_evaluation(
14555 &mut self,
14556 targets: &[crate::engine::EvaluationTarget],
14557 options: &crate::engine::TargetEvalOptions<'_>,
14558 ) -> Result<crate::engine::PreparedTargetGraphReport, ExcelError> {
14559 const MAX_TRANSIENT_PREPARATION_RETRIES: usize = 2;
14560 let mut retries = 0usize;
14561 loop {
14562 match self.prepare_graph_for_targets_unobserved(targets, options) {
14563 Err(error)
14564 if Self::transient_target_preparation_stale(&error)
14565 && retries < MAX_TRANSIENT_PREPARATION_RETRIES =>
14566 {
14567 retries = retries.saturating_add(1);
14568 }
14569 result => return result,
14570 }
14571 }
14572 }
14573
14574 fn evaluate_mixed_targets(
14575 &mut self,
14576 targets: &[crate::engine::EvaluationTarget],
14577 delta: Option<&mut DeltaCollector>,
14578 ) -> Result<EvalResult, ExcelError> {
14579 let _source_cache = self.source_cache_session();
14580 let cancel = self.active_cancel_flag.clone();
14581 let options = crate::engine::TargetEvalOptions {
14582 request_id: self
14583 .active_evaluation_resource_request
14584 .as_ref()
14585 .map(|stats| stats.request_id),
14586 cancel,
14587 deadline: None,
14588 budgets: None,
14589 opaque_policy: crate::engine::OpaquePreparePolicy::Widen,
14590 };
14591 self.prepare_and_execute_target_recipe(targets, &options, delta)
14592 }
14593
14594 fn prepare_and_execute_target_recipe(
14595 &mut self,
14596 targets: &[crate::engine::EvaluationTarget],
14597 options: &crate::engine::TargetEvalOptions<'_>,
14598 delta: Option<&mut DeltaCollector>,
14599 ) -> Result<EvalResult, ExcelError> {
14600 let preparation = self.prepare_graph_for_routed_evaluation(targets, options)?;
14601 self.execute_prepared_target_recipe(targets, &preparation.widened_scope, delta)
14602 }
14603
14604 fn execute_prepared_target_recipe(
14605 &mut self,
14606 targets: &[crate::engine::EvaluationTarget],
14607 scope: &crate::engine::PrepareScope,
14608 delta: Option<&mut DeltaCollector>,
14609 ) -> Result<EvalResult, ExcelError> {
14610 self.require_unified_authority()?;
14611 if matches!(scope, crate::engine::PrepareScope::Workbook)
14612 && let Some(stats) = self.active_evaluation_resource_request.as_mut()
14613 {
14614 stats.workbook_exact_attempts = stats.workbook_exact_attempts.max(1);
14615 }
14616 let mut roots = self.resolve_target_producers(targets)?;
14617 if let crate::engine::PrepareScope::Sheets(sheets) = scope {
14618 let request_id = self
14619 .active_evaluation_resource_request
14620 .as_ref()
14621 .map(|request| request.request_id);
14622 let root_count = roots.len();
14623 let mut widened_roots =
14624 OrderedTargetProducers::from_ordered(std::mem::take(&mut roots))
14625 .map_err(|_| target_root_allocation_error(root_count, request_id))?;
14626 let sheet_ids = sheets
14627 .iter()
14628 .filter_map(|sheet| self.graph.sheet_id(sheet))
14629 .collect::<FxHashSet<_>>();
14630 for vertex in self.graph.formula_vertices() {
14631 if sheet_ids.contains(&self.graph.get_vertex_sheet_id(vertex)) {
14632 widened_roots
14633 .push(crate::engine::target_preparation::TargetProducer::Legacy(
14634 vertex,
14635 ))
14636 .map_err(|_| {
14637 target_root_allocation_error(widened_roots.len() + 1, request_id)
14638 })?;
14639 }
14640 }
14641 roots = widened_roots.into_vec();
14642 }
14643 self.begin_evaluation_request();
14644 #[cfg(any(test, feature = "legacy_oracle"))]
14645 self.graph.flush_pending_edge_deltas();
14646 let workbook_scope = matches!(scope, crate::engine::PrepareScope::Workbook);
14647 if workbook_scope {
14648 if let Some(delta) = delta {
14649 self.evaluate_all_with_delta_collector(delta)
14650 } else {
14651 self.evaluate_all_legacy_impl()
14652 }
14653 } else {
14654 self.evaluate_legacy_target_roots(&roots, delta)
14655 }
14656 }
14657
14658 fn legacy_coordinate_targets(
14659 &mut self,
14660 targets: &[(&str, u32, u32)],
14661 ) -> Vec<crate::engine::EvaluationTarget> {
14662 targets
14663 .iter()
14664 .map(|(sheet, row, col)| {
14665 self.graph.sheet_id_mut(sheet);
14668 crate::engine::EvaluationTarget::Cell {
14669 sheet: (*sheet).to_string(),
14670 row: *row,
14671 col: *col,
14672 }
14673 })
14674 .collect()
14675 }
14676
14677 pub fn evaluate_targets(
14679 &mut self,
14680 targets: &[crate::engine::EvaluationTarget],
14681 ) -> Result<EvalResult, ExcelError> {
14682 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14683 engine.observe_function_semantic_epoch()?;
14684 engine.validate_deterministic_mode()?;
14685 engine.evaluate_mixed_targets(targets, None)
14686 })
14687 }
14688
14689 pub fn evaluate_targets_with_options(
14691 &mut self,
14692 targets: &[crate::engine::EvaluationTarget],
14693 options: crate::engine::TargetEvalOptions<'_>,
14694 ) -> Result<EvalResult, ExcelError> {
14695 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14696 engine.active_cancel_flag = options.cancel.clone();
14697 engine.active_evaluation_deadline = options.deadline;
14698 let result = (|| {
14699 engine.cancellation_checkpoint("Evaluation cancelled before target preparation")?;
14700 engine.observe_function_semantic_epoch()?;
14701 engine.validate_deterministic_mode()?;
14702 let _source_cache = engine.source_cache_session();
14703 engine.prepare_and_execute_target_recipe(targets, &options, None)
14704 })();
14705 engine.active_cancel_flag = None;
14706 engine.active_evaluation_deadline = None;
14707 result
14708 })
14709 }
14710
14711 pub fn evaluate_targets_with_delta(
14713 &mut self,
14714 targets: &[crate::engine::EvaluationTarget],
14715 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
14716 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
14717 engine.observe_function_semantic_epoch()?;
14718 engine.validate_deterministic_mode()?;
14719 let mut collector = DeltaCollector::new(DeltaMode::Cells);
14720 let result = engine.evaluate_mixed_targets(targets, Some(&mut collector))?;
14721 Ok((result, collector.finish_target()))
14722 })
14723 }
14724
14725 pub fn evaluate_until(
14727 &mut self,
14728 targets: &[(&str, u32, u32)],
14729 ) -> Result<EvalResult, ExcelError> {
14730 self.observe_evaluation_resource_request(EvaluationRequestKind::Targeted, |engine| {
14731 engine.evaluate_until_unobserved(targets)
14732 })
14733 }
14734
14735 fn evaluate_until_unobserved(
14736 &mut self,
14737 targets: &[(&str, u32, u32)],
14738 ) -> Result<EvalResult, ExcelError> {
14739 self.observe_function_semantic_epoch()?;
14740 let targets = self.legacy_coordinate_targets(targets);
14741 self.evaluate_mixed_targets(&targets, None)
14742 }
14743
14744 fn evaluate_until_with_delta_collector(
14745 &mut self,
14746 targets: &[(&str, u32, u32)],
14747 delta: &mut DeltaCollector,
14748 ) -> Result<EvalResult, ExcelError> {
14749 let targets = self.legacy_coordinate_targets(targets);
14750 self.evaluate_mixed_targets(&targets, Some(delta))
14751 }
14752
14753 fn evaluate_legacy_target_roots(
14754 &mut self,
14755 roots: &[crate::engine::target_preparation::TargetProducer],
14756 mut delta: Option<&mut DeltaCollector>,
14757 ) -> Result<EvalResult, ExcelError> {
14758 use crate::engine::target_preparation::TargetProducer;
14759 #[cfg(any(test, feature = "benchmark_internal"))]
14760 {
14761 self.recalc_reuse_probe
14762 .get_mut()
14763 .unwrap()
14764 .legacy_target_requests += 1;
14765 }
14766 let start = crate::instant::FzInstant::now();
14767 let root_vertices = roots
14768 .iter()
14769 .filter_map(|root| match root {
14770 TargetProducer::Legacy(vertex) | TargetProducer::Symbol(vertex) => Some(*vertex),
14771 TargetProducer::ValueOnly(_) => None,
14772 })
14773 .collect::<Vec<_>>();
14774 let mut computed_vertices = 0usize;
14775 let mut cycle_errors = 0usize;
14776 let mut replans = 0usize;
14777 const MAX_REPLAN: usize = 5;
14778 self.graph.authority_sync();
14779 loop {
14780 let (precedents_to_eval, old_vdeps) = self.demand_subgraph(&root_vertices)?;
14781 if precedents_to_eval.is_empty() {
14782 break;
14783 }
14784 #[cfg(any(test, feature = "benchmark_internal"))]
14785 {
14786 self.recalc_reuse_probe
14787 .get_mut()
14788 .unwrap()
14789 .target_schedule_builds += 1;
14790 }
14791 let schedule = {
14792 self.graph.authority_sync();
14793 let mut ledger = self.active_resource_ledger.take();
14794 let result = self.create_authority_schedule(
14795 &precedents_to_eval,
14796 &old_vdeps,
14797 ledger.as_mut(),
14798 );
14799 self.active_resource_ledger = ledger;
14800 result?
14801 };
14802 self.begin_pass(&schedule);
14803 for (unit_index, &unit) in schedule.units.iter().enumerate() {
14804 self.cancellation_checkpoint("Evaluation cancelled before target schedule unit")?;
14805 match unit {
14806 ScheduleUnit::Cycle(index) => {
14807 if self.handle_cycle_unit(
14808 schedule.unit_cycle(index),
14809 delta.as_deref_mut(),
14810 None,
14811 None,
14812 )? > 0
14813 {
14814 cycle_errors = cycle_errors.saturating_add(1);
14815 }
14816 }
14817 ScheduleUnit::Layer(index) => {
14818 let layer = schedule.unit_layer(index);
14819 let evaluated = if let Some(delta) = delta.as_deref_mut() {
14820 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14821 self.evaluate_layer_parallel_with_delta(layer, delta)?
14822 } else {
14823 self.evaluate_layer_sequential_with_delta(layer, delta)?
14824 }
14825 } else if self.thread_pool.is_some() && layer.vertices.len() > 1 {
14826 self.evaluate_layer_parallel(layer)?
14827 } else {
14828 self.evaluate_layer_sequential(layer)?
14829 };
14830 computed_vertices = computed_vertices.saturating_add(evaluated);
14831 }
14832 }
14833 if self.stop_after_unit(&schedule, unit_index) {
14834 break;
14835 }
14836 }
14837 let changed = self.changed_virtual_dep_vertices(&precedents_to_eval, &old_vdeps);
14838 self.resource_checkpoint(0)?;
14839 if !self.finish_target_pass_dirty(&precedents_to_eval, &changed) {
14840 break;
14841 }
14842 if replans >= MAX_REPLAN {
14843 return Err(self.replan_exhausted_error(
14844 MAX_REPLAN,
14845 "targeted legacy dynamic dependency evaluation",
14846 ));
14847 }
14848 replans = replans.saturating_add(1);
14849 }
14850 self.redirty_for_next_recalc();
14851 Ok(EvalResult {
14852 computed_vertices,
14853 cycle_errors,
14854 elapsed: start.elapsed(),
14855 })
14856 }
14857
14858 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
14860 if self.has_staged_formulas() || self.staged_formula_index.has_packages() {
14861 return Err(
14862 Self::plan_stale(formualizer_common::PlanStaleReason::Staged).with_message(
14863 "compatibility recalculation plans require all staged formulas to be prepared",
14864 ),
14865 );
14866 }
14867 let key = self.recalc_plan_key();
14868 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
14869 vertices.sort_unstable();
14870 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
14871 let schedule = if vertices.is_empty() {
14872 crate::engine::Schedule {
14873 units: Vec::new(),
14874 layers: Vec::new(),
14875 cycles: Vec::new(),
14876 }
14877 } else {
14878 self.create_evaluation_schedule_uncached(&vertices, None)?.0
14879 };
14880 self.validate_recalc_plan_key(&key)?;
14881 Ok(RecalcPlan {
14882 key,
14883 kind: RecalcPlanKind::CompatibilityFull {
14884 schedule,
14885 has_dynamic_refs,
14886 },
14887 })
14888 }
14889
14890 pub fn build_recalc_plan_for_targets(
14892 &mut self,
14893 targets: &[crate::engine::EvaluationTarget],
14894 ) -> Result<RecalcPlan, ExcelError> {
14895 self.build_recalc_plan_for_targets_with_options(
14896 targets,
14897 crate::engine::TargetEvalOptions::default(),
14898 )
14899 }
14900
14901 pub fn build_recalc_plan_for_targets_with_options(
14902 &mut self,
14903 targets: &[crate::engine::EvaluationTarget],
14904 options: crate::engine::TargetEvalOptions<'_>,
14905 ) -> Result<RecalcPlan, ExcelError> {
14906 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14907 engine.observe_function_semantic_epoch()?;
14908 engine.validate_deterministic_mode()?;
14909 let _source_cache = engine.source_cache_session();
14910 let preparation = engine.prepare_graph_for_routed_evaluation(targets, &options)?;
14911 #[cfg(any(test, feature = "legacy_oracle"))]
14912 engine.graph.flush_pending_edge_deltas();
14913 let topology = if matches!(
14914 preparation.widened_scope,
14915 crate::engine::PrepareScope::Workbook
14916 ) {
14917 RecalcTopology::Workbook
14918 } else {
14919 RecalcTopology::RunLocalRecipe
14920 };
14921 Ok(RecalcPlan {
14922 key: engine.recalc_plan_key(),
14923 kind: RecalcPlanKind::Target {
14924 targets: targets.to_vec(),
14925 scope: preparation.widened_scope,
14926 topology,
14927 dynamic_policy: DynamicPlanPolicy::BoundedTargetReplan,
14928 },
14929 })
14930 })
14931 }
14932
14933 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
14935 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14936 engine.evaluate_recalc_plan_unobserved(plan)
14937 })
14938 }
14939
14940 pub fn evaluate_recalc_plan_with_controls(
14941 &mut self,
14942 plan: &RecalcPlan,
14943 cancel: Option<crate::engine::CancelToken>,
14944 deadline: Option<Instant>,
14945 ) -> Result<EvalResult, ExcelError> {
14946 self.observe_evaluation_resource_request(EvaluationRequestKind::RecalcPlan, |engine| {
14947 engine.active_cancel_flag = cancel.clone();
14948 engine.active_evaluation_deadline = deadline;
14949 let result = engine.evaluate_recalc_plan_unobserved(plan);
14950 engine.active_cancel_flag = None;
14951 engine.active_evaluation_deadline = None;
14952 result
14953 })
14954 }
14955
14956 fn evaluate_recalc_plan_unobserved(
14957 &mut self,
14958 plan: &RecalcPlan,
14959 ) -> Result<EvalResult, ExcelError> {
14960 #[cfg(any(test, feature = "legacy_oracle"))]
14961 self.graph.flush_pending_edge_deltas();
14962 self.validate_recalc_plan_key(&plan.key)?;
14963 self.cancellation_checkpoint("Evaluation cancelled before recalculation plan execution")?;
14964 self.validate_deterministic_mode()?;
14965
14966 match &plan.kind {
14967 RecalcPlanKind::Target {
14968 targets,
14969 scope,
14970 topology,
14971 dynamic_policy,
14972 } => {
14973 debug_assert_eq!(*dynamic_policy, DynamicPlanPolicy::BoundedTargetReplan);
14974 debug_assert_eq!(
14975 matches!(topology, RecalcTopology::Workbook),
14976 matches!(scope, crate::engine::PrepareScope::Workbook)
14977 );
14978 let _source_cache = self.source_cache_session();
14979 self.execute_prepared_target_recipe(targets, scope, None)
14980 }
14981 RecalcPlanKind::CompatibilityFull {
14982 schedule,
14983 has_dynamic_refs,
14984 } => {
14985 let _source_cache = self.source_cache_session();
14986 self.begin_evaluation_request();
14987 if *has_dynamic_refs {
14988 self.virtual_dep_fallback_activations =
14989 self.virtual_dep_fallback_activations.saturating_add(1);
14990 return self.evaluate_all_coordinator();
14991 }
14992
14993 let start = crate::instant::FzInstant::now();
14994 let dirty_vertices = self.graph.get_evaluation_vertices();
14995 if dirty_vertices.is_empty() {
14996 return Ok(EvalResult {
14997 computed_vertices: 0,
14998 cycle_errors: 0,
14999 elapsed: start.elapsed(),
15000 });
15001 }
15002
15003 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
15004 let mut computed_vertices = 0;
15005 let mut cycle_errors = 0;
15006 for &unit in &schedule.units {
15007 self.cancellation_checkpoint(
15008 "Evaluation cancelled before recalculation plan schedule unit",
15009 )?;
15010 match unit {
15011 ScheduleUnit::Cycle(i) => {
15012 let stamped = self.handle_cycle_unit(
15013 schedule.unit_cycle(i),
15014 None,
15015 Some(&dirty_set),
15016 None,
15017 )?;
15018 if stamped > 0 {
15019 cycle_errors += 1;
15020 }
15021 }
15022 ScheduleUnit::Layer(i) => {
15023 let work: Vec<VertexId> = schedule
15024 .unit_layer(i)
15025 .vertices
15026 .iter()
15027 .copied()
15028 .filter(|v| dirty_set.contains(v))
15029 .collect();
15030 if work.is_empty() {
15031 continue;
15032 }
15033 let temp_layer = crate::engine::scheduler::Layer::new(work);
15034 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
15035 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
15036 } else {
15037 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
15038 }
15039 }
15040 }
15041 }
15042
15043 self.resource_checkpoint(0)?;
15044 self.graph.clear_dirty_flags(&dirty_vertices);
15045 self.redirty_for_next_recalc();
15046 Ok(EvalResult {
15047 computed_vertices,
15048 cycle_errors,
15049 elapsed: start.elapsed(),
15050 })
15051 }
15052 }
15053 }
15054}
15055
15056impl<R> Engine<R>
15057where
15058 R: EvaluationContext,
15059{
15060 fn require_unified_authority(&mut self) -> Result<(), ExcelError> {
15065 self.graph
15066 .authority()
15067 .map(|_| ())
15068 .map_err(Self::authority_excel_error)
15069 }
15070
15071 fn authority_excel_error(error: crate::engine::authority::store::AuthorityError) -> ExcelError {
15072 use crate::engine::authority::store::AuthorityError;
15073 #[cfg(test)]
15077 if std::env::var_os("FZ_AUTHORITY_DEFERRED_TRACE").is_some() {
15078 eprintln!("M1B_AUTHORITY_ERROR {error:?}");
15079 }
15080 let kind = match error {
15081 AuthorityError::Unsupported { .. } => ExcelErrorKind::NImpl,
15082 _ => ExcelErrorKind::Error,
15083 };
15084 ExcelError::new(kind).with_message(format!("unified_authority: {error:?}"))
15085 }
15086
15087 pub fn evaluate_all_for_snapshot(
15101 &mut self,
15102 cancel: Option<crate::engine::CancelToken>,
15103 ) -> Result<EvalResult, ExcelError> {
15104 struct SnapshotScope<'e, R: EvaluationContext>(&'e mut Engine<R>);
15108 impl<R: EvaluationContext> Drop for SnapshotScope<'_, R> {
15109 fn drop(&mut self) {
15110 self.0.snapshot_evaluation_active = false;
15111 if std::thread::panicking() {
15112 self.0.snapshot_volatile_redirty_pending = true;
15113 }
15114 }
15115 }
15116 self.snapshot_evaluation_active = true;
15117 let result = {
15118 let scope = SnapshotScope(self);
15119 match cancel {
15120 Some(cancel) => scope.0.evaluate_all_cancellable(cancel),
15121 None => scope.0.evaluate_all(),
15122 }
15123 };
15124 if result.is_err() {
15125 self.snapshot_volatile_redirty_pending = false;
15126 self.graph.redirty_volatiles();
15127 }
15128 result
15129 }
15130
15131 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
15133 self.observe_evaluation_resource_request(EvaluationRequestKind::Full, |engine| {
15135 engine.evaluate_all_unobserved()
15136 })
15137 }
15138
15139 fn evaluate_all_unobserved(&mut self) -> Result<EvalResult, ExcelError> {
15140 debug_assert!(
15141 !self.graph.deferred_dirty_active(),
15142 "deferred-dirty scope leaked into evaluate_all: a begin_deferred_dirty \
15143 was not balanced by end_deferred_dirty"
15144 );
15145 self.observe_function_semantic_epoch()?;
15146 self.lookup_index_cache.reset_counters();
15147 let _source_cache = self.source_cache_session();
15148 self.validate_deterministic_mode()?;
15149 if self.config.defer_graph_building {
15150 self.build_graph_all()?;
15152 }
15153 self.evaluate_all_coordinator()
15154 }
15155
15156 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
15159 self.require_unified_authority()?;
15160 self.begin_evaluation_request();
15161 self.evaluate_all_legacy_impl()
15162 }
15163
15164 fn legacy_pass_run_units(
15170 &mut self,
15171 schedule: &crate::engine::scheduler::Schedule,
15172 ) -> Result<(usize, usize), ExcelError> {
15173 let mut computed_vertices = 0;
15174 let mut cycle_count = 0;
15175 self.begin_pass(schedule);
15176 for (unit_index, &unit) in schedule.units.iter().enumerate() {
15177 match unit {
15178 ScheduleUnit::Cycle(i) => {
15179 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
15180 cycle_count += 1;
15181 }
15182 }
15183 ScheduleUnit::Layer(i) => {
15184 let layer = schedule.unit_layer(i);
15185 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
15186 computed_vertices += self.evaluate_layer_parallel(layer)?;
15187 } else {
15188 computed_vertices += self.evaluate_layer_sequential(layer)?;
15189 }
15190 }
15191 }
15192 if self.stop_after_unit(schedule, unit_index) {
15193 break;
15194 }
15195 }
15196 Ok((computed_vertices, cycle_count))
15197 }
15198
15199 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
15206 self.reset_virtual_dep_telemetry_if_disabled();
15207 let _span_eval =
15208 crate::engine::trace::fz_span!(tracing::Level::INFO, "evaluate", "evaluate.legacy");
15209 let start = crate::instant::FzInstant::now();
15210 let mut computed_vertices = 0;
15211 let mut cycle_errors = 0;
15212 let mut replan_iterations = 0;
15213 const MAX_REPLAN: usize = 5;
15214 let mut telemetry = self
15215 .config
15216 .enable_virtual_dep_telemetry
15217 .then(|| self.start_virtual_dep_telemetry());
15218
15219 loop {
15220 let to_evaluate = self.graph.get_evaluation_vertices();
15221 if to_evaluate.is_empty() {
15222 if let Some(t) = telemetry.as_mut()
15223 && t.bailout_reason.is_none()
15224 {
15225 t.bailout_reason = Some("no_work");
15226 }
15227 break;
15228 }
15229
15230 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
15231 if let Some(t) = telemetry.as_mut() {
15232 Self::accumulate_schedule_meta(t, &meta);
15233 }
15234
15235 let (pass_computed, pass_cycles) = self.legacy_pass_run_units(&schedule)?;
15236 computed_vertices += pass_computed;
15237 cycle_errors += pass_cycles;
15238
15239 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
15241 if let Some(t) = telemetry.as_mut() {
15242 t.changed_vdeps_total += changed_vertices.len();
15243 }
15244
15245 self.resource_checkpoint(0)?;
15246 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
15247 if let Some(t) = telemetry.as_mut() {
15248 t.bailout_reason = Some("converged");
15249 }
15250 break;
15251 }
15252 if replan_iterations >= MAX_REPLAN {
15253 if let Some(mut t) = telemetry.take() {
15254 t.bailout_reason = Some("max_replan");
15255 t.replan_iterations = replan_iterations;
15256 self.last_virtual_dep_telemetry = t;
15257 }
15258 return Err(
15259 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
15260 );
15261 }
15262
15263 replan_iterations += 1;
15264 }
15265
15266 if let Some(mut t) = telemetry {
15267 t.replan_iterations = replan_iterations;
15268 self.last_virtual_dep_telemetry = t;
15269 }
15270
15271 self.redirty_for_next_recalc();
15273
15274 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
15276
15277 Ok(EvalResult {
15278 computed_vertices,
15279 cycle_errors,
15280 elapsed: start.elapsed(),
15281 })
15282 }
15283
15284 pub fn evaluate_all_with_target_delta(
15285 &mut self,
15286 ) -> Result<(EvalResult, crate::engine::TargetEvalDelta), ExcelError> {
15287 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
15288 engine.observe_function_semantic_epoch()?;
15289 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15290 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
15291 Ok((result, collector.finish_target()))
15292 })
15293 }
15294
15295 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
15296 self.evaluate_all_with_delta_policy(EvalDeltaCompatibilityPolicy::Unlimited)
15297 }
15298
15299 pub fn evaluate_all_with_delta_policy(
15300 &mut self,
15301 policy: EvalDeltaCompatibilityPolicy,
15302 ) -> Result<(EvalResult, EvalDelta), ExcelError> {
15303 self.observe_evaluation_resource_request(EvaluationRequestKind::FullWithDelta, |engine| {
15304 engine.observe_function_semantic_epoch()?;
15305 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15306 let result = engine.evaluate_all_with_delta_collector(&mut collector)?;
15307 Ok((result, collector.finish_with_policy(policy)?))
15308 })
15309 }
15310
15311 fn evaluate_all_with_delta_collector(
15312 &mut self,
15313 delta: &mut DeltaCollector,
15314 ) -> Result<EvalResult, ExcelError> {
15315 let _source_cache = self.source_cache_session();
15316 if self.config.defer_graph_building {
15317 self.build_graph_all()?;
15318 }
15319 self.require_unified_authority()?;
15320 self.begin_evaluation_request();
15321 self.reset_virtual_dep_telemetry_if_disabled();
15322 let _span_eval = crate::engine::trace::fz_span!(
15323 tracing::Level::INFO,
15324 "evaluate",
15325 "evaluate.legacy_delta"
15326 );
15327 let start = crate::instant::FzInstant::now();
15328 let mut computed_vertices = 0;
15329 let mut cycle_errors = 0;
15330
15331 let mut replan_iterations = 0;
15332 const MAX_REPLAN: usize = 5;
15333 let mut telemetry = self
15334 .config
15335 .enable_virtual_dep_telemetry
15336 .then(|| self.start_virtual_dep_telemetry());
15337
15338 loop {
15339 let to_evaluate = self.graph.get_evaluation_vertices();
15340 if to_evaluate.is_empty() {
15341 if let Some(t) = telemetry.as_mut()
15342 && t.bailout_reason.is_none()
15343 {
15344 t.bailout_reason = Some("no_work");
15345 }
15346 break;
15347 }
15348
15349 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
15350 if let Some(t) = telemetry.as_mut() {
15351 Self::accumulate_schedule_meta(t, &meta);
15352 }
15353
15354 self.begin_pass(&schedule);
15355 for (unit_index, &unit) in schedule.units.iter().enumerate() {
15356 match unit {
15357 ScheduleUnit::Cycle(i) => {
15358 if self.handle_cycle_unit(
15359 schedule.unit_cycle(i),
15360 Some(delta),
15361 None,
15362 None,
15363 )? > 0
15364 {
15365 cycle_errors += 1;
15366 }
15367 }
15368 ScheduleUnit::Layer(i) => {
15369 let layer = schedule.unit_layer(i);
15370 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
15371 computed_vertices +=
15372 self.evaluate_layer_parallel_with_delta(layer, delta)?;
15373 } else {
15374 computed_vertices +=
15375 self.evaluate_layer_sequential_with_delta(layer, delta)?;
15376 }
15377 }
15378 }
15379 if self.stop_after_unit(&schedule, unit_index) {
15380 break;
15381 }
15382 }
15383
15384 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
15385 if let Some(t) = telemetry.as_mut() {
15386 t.changed_vdeps_total += changed_vertices.len();
15387 }
15388 self.resource_checkpoint(0)?;
15389 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
15390 if let Some(t) = telemetry.as_mut() {
15391 t.bailout_reason = Some("converged");
15392 }
15393 break;
15394 }
15395 if replan_iterations >= MAX_REPLAN {
15396 if let Some(mut t) = telemetry.take() {
15397 t.bailout_reason = Some("max_replan");
15398 t.replan_iterations = replan_iterations;
15399 self.last_virtual_dep_telemetry = t;
15400 }
15401 return Err(
15402 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
15403 );
15404 }
15405 replan_iterations += 1;
15406 }
15407
15408 if let Some(mut t) = telemetry {
15409 t.replan_iterations = replan_iterations;
15410 self.last_virtual_dep_telemetry = t;
15411 }
15412
15413 self.redirty_for_next_recalc();
15414 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
15415
15416 Ok(EvalResult {
15417 computed_vertices,
15418 cycle_errors,
15419 elapsed: start.elapsed(),
15420 })
15421 }
15422
15423 pub fn evaluate_cell(
15433 &mut self,
15434 sheet: &str,
15435 row: u32,
15436 col: u32,
15437 ) -> Result<Option<LiteralValue>, ExcelError> {
15438 self.observe_evaluation_resource_request(EvaluationRequestKind::Cell, |engine| {
15439 engine.evaluate_cell_unobserved(sheet, row, col)
15440 })
15441 }
15442
15443 fn evaluate_cell_unobserved(
15444 &mut self,
15445 sheet: &str,
15446 row: u32,
15447 col: u32,
15448 ) -> Result<Option<LiteralValue>, ExcelError> {
15449 if row == 0 || col == 0 {
15450 return Err(ExcelError::new(ExcelErrorKind::Ref)
15451 .with_message("Row and column must be >= 1".to_string()));
15452 }
15453
15454 let result = self.evaluate_cells(&[(sheet, row, col)])?;
15455
15456 match result.len() {
15457 0 => Ok(None),
15458 1 => {
15459 let v = result.into_iter().next().unwrap();
15460 Ok(v)
15461 }
15462 _ => unreachable!("evaluate_cells returned unexpected length"),
15463 }
15464 }
15465
15466 pub fn evaluate_cells(
15473 &mut self,
15474 targets: &[(&str, u32, u32)],
15475 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
15476 self.observe_evaluation_resource_request(EvaluationRequestKind::Cells, |engine| {
15477 engine.evaluate_cells_unobserved(targets)
15478 })
15479 }
15480
15481 fn evaluate_cells_unobserved(
15482 &mut self,
15483 targets: &[(&str, u32, u32)],
15484 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
15485 self.observe_function_semantic_epoch()?;
15486 debug_assert!(
15487 !self.graph.deferred_dirty_active(),
15488 "deferred-dirty scope leaked into evaluate_cells: a begin_deferred_dirty \
15489 was not balanced by end_deferred_dirty"
15490 );
15491 self.validate_deterministic_mode()?;
15492 if targets.is_empty() {
15493 return Ok(Vec::new());
15494 }
15495 let typed_targets = self.legacy_coordinate_targets(targets);
15496 self.evaluate_mixed_targets(&typed_targets, None)?;
15497 Ok(targets
15498 .iter()
15499 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
15500 .collect())
15501 }
15502
15503 pub fn evaluate_cells_cancellable(
15504 &mut self,
15505 targets: &[(&str, u32, u32)],
15506 cancel: crate::engine::CancelToken,
15507 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
15508 self.observe_evaluation_resource_request(
15509 EvaluationRequestKind::CellsCancellable,
15510 |engine| {
15511 engine.observe_function_semantic_epoch()?;
15512 engine.active_cancel_flag = Some(cancel.clone());
15513 let res = engine.evaluate_cells_cancellable_impl(targets, cancel.as_flag());
15514 engine.active_cancel_flag = None;
15515 res
15516 },
15517 )
15518 }
15519
15520 fn evaluate_cells_cancellable_impl(
15521 &mut self,
15522 targets: &[(&str, u32, u32)],
15523 cancel_flag: &AtomicBool,
15524 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
15525 self.validate_deterministic_mode()?;
15526 if targets.is_empty() {
15527 return Ok(Vec::new());
15528 }
15529 if cancel_flag.load(Ordering::Relaxed) {
15530 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
15531 .with_message("Evaluation cancelled before target preparation"));
15532 }
15533 let typed_targets = self.legacy_coordinate_targets(targets);
15534 self.evaluate_mixed_targets(&typed_targets, None)?;
15535 Ok(targets
15536 .iter()
15537 .map(|(sheet, row, col)| self.get_cell_value(sheet, *row, *col))
15538 .collect())
15539 }
15540
15541 pub fn evaluate_cells_with_target_delta(
15542 &mut self,
15543 targets: &[(&str, u32, u32)],
15544 ) -> Result<(Vec<Option<LiteralValue>>, crate::engine::TargetEvalDelta), ExcelError> {
15545 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
15546 engine.observe_function_semantic_epoch()?;
15547 engine.validate_deterministic_mode()?;
15548 if targets.is_empty() {
15549 return Ok((Vec::new(), crate::engine::TargetEvalDelta::default()));
15550 }
15551 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15552 engine.evaluate_until_with_delta_collector(targets, &mut collector)?;
15553 let values = targets
15554 .iter()
15555 .map(|(sheet, row, col)| engine.get_cell_value(sheet, *row, *col))
15556 .collect();
15557 Ok((values, collector.finish_target()))
15558 })
15559 }
15560
15561 pub fn evaluate_cells_with_delta(
15562 &mut self,
15563 targets: &[(&str, u32, u32)],
15564 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15565 self.evaluate_cells_with_delta_policy(targets, EvalDeltaCompatibilityPolicy::Unlimited)
15566 }
15567
15568 pub fn evaluate_cells_with_delta_policy(
15569 &mut self,
15570 targets: &[(&str, u32, u32)],
15571 policy: EvalDeltaCompatibilityPolicy,
15572 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15573 self.observe_evaluation_resource_request(EvaluationRequestKind::CellsWithDelta, |engine| {
15574 engine.evaluate_cells_with_delta_unobserved(targets, policy)
15575 })
15576 }
15577
15578 fn evaluate_cells_with_delta_unobserved(
15579 &mut self,
15580 targets: &[(&str, u32, u32)],
15581 policy: EvalDeltaCompatibilityPolicy,
15582 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
15583 self.observe_function_semantic_epoch()?;
15584 self.validate_deterministic_mode()?;
15585 if targets.is_empty() {
15586 return Ok((Vec::new(), EvalDelta::default()));
15587 }
15588 let mut collector = DeltaCollector::new(DeltaMode::Cells);
15589 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
15590 let values = targets
15591 .iter()
15592 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
15593 .collect();
15594 Ok((values, collector.finish_with_policy(policy)?))
15595 }
15596
15597 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
15599 if targets.is_empty() {
15600 return Ok(EvalPlan {
15601 total_vertices_to_evaluate: 0,
15602 layers: Vec::new(),
15603 cycles_detected: 0,
15604 dirty_count: 0,
15605 volatile_count: 0,
15606 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15607 estimated_parallel_layers: 0,
15608 target_cells: Vec::new(),
15609 });
15610 }
15611 if self.config.defer_graph_building && self.has_staged_formulas() {
15612 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
15613 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
15614 ));
15615 }
15616
15617 let addresses: Vec<String> = targets
15619 .iter()
15620 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
15621 .collect();
15622
15623 let mut target_addrs = Vec::new();
15625 for (sheet, row, col) in targets {
15626 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
15627 let coord = Coord::from_excel(*row, *col, true, true);
15628 target_addrs.push(CellRef::new(sheet_id, coord));
15629 }
15630 }
15631
15632 let mut target_vertex_ids = Vec::new();
15634 for addr in &target_addrs {
15635 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
15636 target_vertex_ids.push(vertex_id);
15637 }
15638 }
15639
15640 if target_vertex_ids.is_empty() {
15641 return Ok(EvalPlan {
15642 total_vertices_to_evaluate: 0,
15643 layers: Vec::new(),
15644 cycles_detected: 0,
15645 dirty_count: 0,
15646 volatile_count: 0,
15647 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15648 estimated_parallel_layers: 0,
15649 target_cells: addresses,
15650 });
15651 }
15652
15653 let (precedents_to_eval, vdeps) = self.demand_subgraph(&target_vertex_ids)?;
15655
15656 if precedents_to_eval.is_empty() {
15657 return Ok(EvalPlan {
15658 total_vertices_to_evaluate: 0,
15659 layers: Vec::new(),
15660 cycles_detected: 0,
15661 dirty_count: 0,
15662 volatile_count: 0,
15663 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
15664 estimated_parallel_layers: 0,
15665 target_cells: addresses,
15666 });
15667 }
15668
15669 let mut dirty_count = 0;
15671 let mut volatile_count = 0;
15672 for &vertex_id in &precedents_to_eval {
15673 if self.graph.is_dirty(vertex_id) {
15674 dirty_count += 1;
15675 }
15676 if self.graph.is_volatile(vertex_id) {
15677 volatile_count += 1;
15678 }
15679 }
15680
15681 let schedule = self.create_authority_schedule(&precedents_to_eval, &vdeps, None)?;
15683
15684 let mut layers = Vec::new();
15686 let mut estimated_parallel_layers = 0;
15687 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
15688
15689 for layer in &schedule.layers {
15690 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
15691 if parallel_eligible {
15692 estimated_parallel_layers += 1;
15693 }
15694
15695 let sample_cells: Vec<String> = layer
15697 .vertices
15698 .iter()
15699 .take(5)
15700 .filter_map(|&vertex_id| {
15701 self.graph
15702 .get_cell_ref_for_vertex(vertex_id)
15703 .map(|cell_ref| {
15704 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
15705 format!(
15706 "{}!{}{}",
15707 sheet_name,
15708 Self::col_to_letters(cell_ref.coord.col().saturating_add(1)),
15709 cell_ref.coord.row() + 1
15710 )
15711 })
15712 })
15713 .collect();
15714
15715 layers.push(LayerInfo {
15716 vertex_count: layer.vertices.len(),
15717 parallel_eligible,
15718 sample_cells,
15719 });
15720 }
15721
15722 Ok(EvalPlan {
15723 total_vertices_to_evaluate: precedents_to_eval.len(),
15724 layers,
15725 cycles_detected: schedule.cycles.len(),
15726 dirty_count,
15727 volatile_count,
15728 parallel_enabled,
15729 estimated_parallel_layers,
15730 target_cells: addresses,
15731 })
15732 }
15733 fn create_evaluation_schedule(
15735 &mut self,
15736 to_evaluate: &[VertexId],
15737 ) -> Result<EvaluationScheduleBuildOutput, ExcelError> {
15738 #[cfg(any(test, feature = "benchmark_internal"))]
15739 {
15740 self.recalc_reuse_probe.get_mut().unwrap().schedule_requests += 1;
15741 }
15742 #[cfg(any(test, feature = "legacy_oracle"))]
15745 self.graph.flush_pending_edge_deltas();
15746 self.graph.authority_sync();
15748 if self.can_use_static_schedule_cache(to_evaluate) {
15749 let revision = self.schedule_cache_authority_revision();
15751 let current = |e: &CachedScheduleEntry| {
15752 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15753 };
15754 if !self
15755 .cached_static_schedule
15756 .as_ref()
15757 .is_some_and(|c| current(c) && c.candidate_vertices.equals(to_evaluate))
15758 && let Some(i) = self.recent_schedules.iter().position(|e| {
15759 current(e)
15760 && e.candidate_vertices.len() == to_evaluate.len()
15761 && e.candidate_vertices.equals(to_evaluate)
15762 })
15763 {
15764 let hit = self.recent_schedules.remove(i);
15765 if let Some(previous) = self.cached_static_schedule.replace(hit) {
15766 self.retain_recent_schedule(previous);
15767 }
15768 }
15769 if let Some(cached) = self.cached_static_schedule.as_ref()
15770 && cached.topology_epoch == self.topology_epoch
15771 && cached.authority_revision == self.schedule_cache_authority_revision()
15772 && cached.candidate_vertices.equals(to_evaluate)
15773 {
15774 let meta = ScheduleBuildMeta {
15775 candidate_vertices: to_evaluate.len(),
15776 vdeps_vertices: 0,
15777 vdeps_edges: 0,
15778 builder_elapsed_ms: 0,
15779 used_virtual_schedule: false,
15780 schedule_cache_hit: true,
15781 schedule_cache_eligible: true,
15782 };
15783 #[cfg(any(test, feature = "benchmark_internal"))]
15784 {
15785 let mut probe = self.recalc_reuse_probe.lock().unwrap();
15786 probe.schedule_cache_hits += 1;
15787 probe.schedule_shared_handles += 1;
15788 }
15789 return Ok((
15790 EvaluationSchedule::Shared(Arc::clone(&cached.schedule)),
15791 FxHashMap::default(),
15792 meta,
15793 ));
15794 }
15795
15796 let (schedule, vdeps, mut meta) = match self.schedule_from_base(to_evaluate)? {
15797 Some(schedule) => (
15798 schedule,
15799 FxHashMap::default(),
15800 ScheduleBuildMeta {
15801 candidate_vertices: to_evaluate.len(),
15802 vdeps_vertices: 0,
15803 vdeps_edges: 0,
15804 builder_elapsed_ms: 0,
15805 used_virtual_schedule: false,
15806 schedule_cache_hit: false,
15807 schedule_cache_eligible: true,
15808 },
15809 ),
15810 None => self.create_evaluation_schedule_active(to_evaluate)?,
15811 };
15812 meta.schedule_cache_hit = false;
15813 meta.schedule_cache_eligible = true;
15814 #[cfg(any(test, feature = "benchmark_internal"))]
15815 {
15816 self.recalc_reuse_probe
15817 .get_mut()
15818 .unwrap()
15819 .schedule_cache_misses += 1;
15820 }
15821 let schedule = if vdeps.is_empty() {
15822 let mut schedule = schedule;
15825 schedule.units.shrink_to_fit();
15826 for layer in &mut schedule.layers {
15827 layer.vertices.shrink_to_fit();
15828 }
15829 schedule.layers.shrink_to_fit();
15830 for cycle in &mut schedule.cycles {
15831 cycle.shrink_to_fit();
15832 }
15833 schedule.cycles.shrink_to_fit();
15834 let schedule = Arc::new(schedule);
15835 #[cfg(any(test, feature = "benchmark_internal"))]
15836 {
15837 self.recalc_reuse_probe
15838 .get_mut()
15839 .unwrap()
15840 .schedule_shared_handles += 1;
15841 }
15842 let entry = CachedScheduleEntry {
15843 topology_epoch: self.topology_epoch,
15844 authority_revision: self.schedule_cache_authority_revision(),
15845 candidate_vertices: VertexIdRuns::from_slice(to_evaluate),
15846 schedule: Arc::clone(&schedule),
15847 };
15848 if let Some(previous) = self.cached_static_schedule.replace(entry) {
15849 self.retain_recent_schedule(previous);
15850 }
15851 EvaluationSchedule::Shared(schedule)
15852 } else {
15853 EvaluationSchedule::Owned(schedule)
15854 };
15855 return Ok((schedule, vdeps, meta));
15856 }
15857
15858 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_active(to_evaluate)?;
15859 meta.schedule_cache_hit = false;
15860 meta.schedule_cache_eligible = false;
15861 #[cfg(any(test, feature = "benchmark_internal"))]
15862 {
15863 self.recalc_reuse_probe
15864 .get_mut()
15865 .unwrap()
15866 .schedule_cache_ineligible += 1;
15867 }
15868 Ok((EvaluationSchedule::Owned(schedule), vdeps, meta))
15869 }
15870
15871 fn schedule_from_base(
15877 &mut self,
15878 to_evaluate: &[VertexId],
15879 ) -> Result<Option<crate::engine::scheduler::Schedule>, ExcelError> {
15880 let revision = self.schedule_cache_authority_revision();
15881 let current = |e: &&CachedScheduleEntry| {
15882 e.topology_epoch == self.topology_epoch && e.authority_revision == revision
15883 };
15884 let Some(base) = [
15887 self.base_schedule.as_ref(),
15888 self.cached_static_schedule.as_ref(),
15889 ]
15890 .into_iter()
15891 .flatten()
15892 .filter(current)
15893 .max_by_key(|e| e.candidate_vertices.len()) else {
15894 return Ok(None);
15895 };
15896 let base_len = base.candidate_vertices.len();
15897 if base_len <= BASE_SCHEDULE_MIN_VERTICES
15898 || to_evaluate.len() < BASE_SCHEDULE_MIN_REQUEST
15899 || to_evaluate.len() > base_len
15900 || base_len > to_evaluate.len().saturating_mul(BASE_SCHEDULE_RATIO)
15901 {
15902 return Ok(None);
15903 }
15904 let mut keep = crate::engine::idset::DenseIdSet::default();
15905 keep.extend(to_evaluate.iter().copied());
15906 let Some((schedule, kept)) = base.schedule.restrict(&keep) else {
15907 return Ok(None);
15908 };
15909 if kept != keep.len() {
15911 return Ok(None);
15912 }
15913 if let Some(ledger) = self.active_resource_ledger.as_mut() {
15914 let layers: usize = schedule
15915 .layers
15916 .iter()
15917 .map(|l| {
15918 l.vertices.capacity() * std::mem::size_of::<VertexId>()
15919 + l.runs.capacity()
15920 * std::mem::size_of::<crate::engine::scheduler::LayerRun>()
15921 })
15922 .sum();
15923 let bytes = (keep.heap_bytes()
15924 + layers
15925 + schedule.layers.capacity()
15926 * std::mem::size_of::<crate::engine::scheduler::Layer>()
15927 + schedule.units.capacity()
15928 * std::mem::size_of::<crate::engine::scheduler::ScheduleUnit>()
15929 + schedule
15930 .cycles
15931 .iter()
15932 .map(|c| c.capacity() * 4)
15933 .sum::<usize>()) as u64;
15934 ledger
15935 .reserve_schedule_discovery(bytes)
15936 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15937 ledger
15938 .release_scratch(bytes)
15939 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
15940 }
15941 #[cfg(debug_assertions)]
15942 self.debug_check_restricted_schedule(to_evaluate, &schedule);
15943 #[cfg(any(test, feature = "benchmark_internal"))]
15944 {
15945 self.recalc_reuse_probe
15946 .get_mut()
15947 .unwrap()
15948 .schedule_base_restrictions += 1;
15949 }
15950 Ok(Some(schedule))
15951 }
15952
15953 #[cfg(debug_assertions)]
15957 fn debug_check_restricted_schedule(
15958 &self,
15959 to_evaluate: &[VertexId],
15960 schedule: &crate::engine::scheduler::Schedule,
15961 ) {
15962 let mut position: FxHashMap<VertexId, usize> = FxHashMap::default();
15963 for (u, unit) in schedule.units.iter().enumerate() {
15964 let vertices: &[VertexId] = match *unit {
15965 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
15966 &schedule.unit_layer(i).vertices
15967 }
15968 crate::engine::scheduler::ScheduleUnit::Cycle(i) => schedule.unit_cycle(i),
15969 };
15970 for &v in vertices {
15971 assert!(
15972 position.insert(v, u).is_none(),
15973 "vertex twice in a restricted schedule"
15974 );
15975 }
15976 }
15977 assert_eq!(position.len(), to_evaluate.len());
15978 if to_evaluate.len() > 512 {
15979 return;
15980 }
15981 let Ok(store) = self.graph.authority_plan_store() else {
15984 return;
15985 };
15986 let cells: Vec<(VertexId, (u16, u32, u32))> = to_evaluate
15987 .iter()
15988 .filter_map(|&v| self.graph.authority_cell_of_vertex(v).map(|c| (v, c)))
15989 .collect();
15990 for &(v, (sheet, row, col)) in &cells {
15991 let Some(owner) = store.owner_at((sheet, row, col)) else {
15992 continue;
15993 };
15994 let Ok(refined) = store.refine_owner_column(owner, col, row, row, None) else {
15995 continue;
15996 };
15997 for piece in &refined.pieces {
15998 for edge in &refined.edges[piece.edge_start..piece.edge_end] {
15999 let Some(image) = edge.proj.forward(&piece.domain) else {
16000 continue;
16001 };
16002 for &(d, (s2, r2, c2)) in &cells {
16003 if d == v
16004 || edge.proj.sheet != s2
16005 || !(image.r0 <= r2
16006 && r2 <= image.r1
16007 && image.c0 <= c2
16008 && c2 <= image.c1)
16009 {
16010 continue;
16011 }
16012 let (pd, pv) = (position[&d], position[&v]);
16013 let same_ok = pd == pv
16014 && match schedule.units[pv] {
16015 crate::engine::scheduler::ScheduleUnit::Layer(i) => {
16016 schedule.unit_layer(i).sequential
16017 }
16018 crate::engine::scheduler::ScheduleUnit::Cycle(_) => true,
16019 };
16020 assert!(
16021 pd < pv || same_ok,
16022 "restricted schedule orders {v:?} before its precedent {d:?}"
16023 );
16024 }
16025 }
16026 }
16027 }
16028 }
16029
16030 fn maybe_compress_formulas(&mut self) {
16034 if !self.config.formula_compression {
16035 return;
16036 }
16037 let builds = self.graph.authority_host().builds;
16038 if self.compressed_at_build == Some(builds) {
16039 return;
16040 }
16041 self.compressed_at_build = Some(builds);
16042 if self.has_staged_formulas() {
16043 self.graph.virtualize_family_members();
16046 return;
16047 }
16048 let pool = self.thread_pool.clone();
16049 let (_, garbage) = self.graph.compress_family_formulas(pool.as_deref());
16050 self.graph.virtualize_family_members();
16051 match pool {
16055 Some(pool) if garbage.len() >= 1024 => pool.spawn(move || drop(garbage)),
16056 _ => drop(garbage),
16057 }
16058 }
16059
16060 fn create_evaluation_schedule_active(
16061 &mut self,
16062 to_evaluate: &[VertexId],
16063 ) -> Result<ScheduleBuildOutput, ExcelError> {
16064 self.graph.authority_sync();
16065 self.maybe_compress_formulas();
16066 let mut ledger = self.active_resource_ledger.take();
16067 let result = self.create_evaluation_schedule_uncached(to_evaluate, ledger.as_mut());
16068 self.active_resource_ledger = ledger;
16069 result
16070 }
16071
16072 fn create_evaluation_schedule_uncached(
16073 &self,
16074 to_evaluate: &[VertexId],
16075 #[allow(unused_variables)] ledger: Option<&mut ResourceLedger>,
16076 ) -> Result<ScheduleBuildOutput, ExcelError> {
16077 #[cfg(any(test, feature = "benchmark_internal"))]
16078 {
16079 self.recalc_reuse_probe.lock().unwrap().schedule_builds += 1;
16080 }
16081 let builder = VirtualDepBuilder::new(self);
16082 #[allow(unused_mut)]
16083 let (mut vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
16084 if self.config.enable_virtual_dep_telemetry {
16085 let build_started = crate::instant::FzInstant::now();
16086 let (vdeps, augmented) = builder.build(to_evaluate);
16087 let builder_elapsed_ms = build_started.elapsed().as_millis();
16088 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
16089 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
16090 } else {
16091 let (vdeps, augmented) = builder.build(to_evaluate);
16092 (vdeps, augmented, 0, 0)
16093 };
16094
16095 {
16097 self.freshness_merge_hints(to_evaluate, &mut vdeps);
16098 self.freshness_extent_hints(to_evaluate, &mut vdeps);
16099 }
16100 let mut final_evaluate = to_evaluate.to_vec();
16101 if !augmented.is_empty() {
16102 final_evaluate.extend(augmented);
16103 final_evaluate.sort_unstable();
16104 final_evaluate.dedup();
16105 }
16106
16107 let use_virtual = !vdeps.is_empty();
16108
16109 let schedule = self.create_authority_schedule(&final_evaluate, &vdeps, ledger)?;
16110
16111 let meta = ScheduleBuildMeta {
16112 candidate_vertices: to_evaluate.len(),
16113 vdeps_vertices: vdeps.len(),
16114 vdeps_edges,
16115 builder_elapsed_ms,
16116 used_virtual_schedule: use_virtual,
16117 schedule_cache_hit: false,
16118 schedule_cache_eligible: false,
16119 };
16120
16121 Ok((schedule, vdeps, meta))
16122 }
16123
16124 fn create_authority_schedule(
16125 &self,
16126 candidates: &[VertexId],
16127 vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
16128 mut ledger: Option<&mut ResourceLedger>,
16129 ) -> Result<crate::engine::scheduler::Schedule, ExcelError> {
16130 use crate::engine::authority::{
16131 geom::{Cover, Rect},
16132 plan_schedule, planner,
16133 proj::{AxisMap, RefProj},
16134 store::{EdgeKey, Tag},
16135 };
16136 let failure = |message: String| {
16137 ExcelError::new(ExcelErrorKind::Error)
16138 .with_message(format!("unified_authority planner: {message}"))
16139 };
16140 self.cancellation_checkpoint("Evaluation cancelled before authority planning")?;
16141 let store = self
16142 .graph
16143 .authority_plan_store()
16144 .map_err(Self::authority_excel_error)?;
16145 if let [only] = candidates
16148 && vdeps.is_empty()
16149 && self.graph.authority_host().observed(*only).is_none()
16150 && let Some(cell) = self.graph.authority_cell_of_vertex(*only)
16151 && cell.0 != crate::engine::authority::geom::SYMBOL_SHEET
16152 && let Some(single) = planner::plan_single(store, cell)
16153 {
16154 #[cfg(debug_assertions)]
16155 {
16156 let mut cover = Cover::new();
16157 cover.insert_rect(cell.0, &Rect::new(cell.1, cell.2, cell.1, cell.2));
16158 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
16159 .expect("general plan of one cell");
16160 assert_eq!(
16161 general.cells.as_slice(),
16162 &[single],
16163 "single-cell plan differs from the planner at {cell:?}"
16164 );
16165 }
16166 let adapted = plan_schedule::schedule(
16167 &[single],
16168 0,
16169 None,
16170 |cell| {
16171 self.graph
16172 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
16173 .ok_or_else(|| failure("missing executor identity".to_owned()))
16174 },
16175 |_work| Ok(()),
16176 )
16177 .map_err(|error| match error {
16178 plan_schedule::ScheduleError::Runtime(error) => error,
16179 other => failure(format!("{other:?}")),
16180 })?;
16181 if let Some(ledger) = ledger {
16182 ledger
16183 .reserve_schedule_discovery(adapted.peak_heap_bytes)
16184 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16185 ledger
16186 .release_scratch(adapted.peak_heap_bytes)
16187 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16188 }
16189 return Ok(adapted.schedule);
16190 }
16191 if (2..=planner::SMALL_PLAN_MAX).contains(&candidates.len())
16194 && vdeps.is_empty()
16195 && candidates
16196 .iter()
16197 .all(|&v| self.graph.authority_host().observed(v).is_none())
16198 {
16199 let cells: Option<Vec<(u16, u32, u32)>> = candidates
16200 .iter()
16201 .map(|&v| self.graph.authority_cell_of_vertex(v))
16202 .collect();
16203 if let Some(small) = cells.as_deref().and_then(|c| planner::plan_small(store, c)) {
16204 #[cfg(debug_assertions)]
16205 {
16206 let mut cover = Cover::new();
16207 for c in cells.as_deref().unwrap_or_default() {
16208 cover.insert_rect(c.0, &Rect::new(c.1, c.2, c.1, c.2));
16209 }
16210 let general = planner::plan_with_hints(store, &cover, &[], None, None, None)
16211 .expect("general plan of a small request");
16212 let key = |c: &planner::OrderedCell| (c.sheet, c.row, c.col, c.id, c.owner);
16213 let mut a: Vec<_> = general.cells.iter().map(key).collect();
16214 let mut b: Vec<_> = small.iter().map(key).collect();
16215 a.sort_unstable();
16216 b.sort_unstable();
16217 assert_eq!(a, b, "small plan cells differ from the planner");
16218 assert!(
16219 general.cells.iter().all(|c| c.cycle.is_none()),
16220 "small plan of a cyclic request"
16221 );
16222 }
16223 let adapted = plan_schedule::schedule(
16224 &small,
16225 0,
16226 None,
16227 |cell| {
16228 self.graph
16229 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
16230 .ok_or_else(|| failure("missing executor identity".to_owned()))
16231 },
16232 |_work| Ok(()),
16233 )
16234 .map_err(|error| match error {
16235 plan_schedule::ScheduleError::Runtime(error) => error,
16236 other => failure(format!("{other:?}")),
16237 })?;
16238 if let Some(ledger) = ledger {
16239 ledger
16240 .reserve_schedule_discovery(adapted.peak_heap_bytes)
16241 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16242 ledger
16243 .release_scratch(adapted.peak_heap_bytes)
16244 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16245 }
16246 return Ok(adapted.schedule);
16247 }
16248 }
16249 let mut cover = Cover::new();
16254 {
16255 let cell_of = |&id: &VertexId| {
16256 self.graph
16257 .authority_cell_of_vertex(id)
16258 .map(|(sheet, row, col)| (sheet, col, row))
16259 };
16260 let cells: Vec<(u16, u32, u32)> = match self.thread_pool.as_deref() {
16263 Some(pool) if candidates.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
16264 use rayon::prelude::*;
16265 pool.install(|| {
16266 let mut cells: Vec<_> = candidates.par_iter().filter_map(cell_of).collect();
16267 cells.par_sort_unstable();
16268 cells
16269 })
16270 }
16271 _ => {
16272 let mut cells: Vec<_> = candidates.iter().filter_map(cell_of).collect();
16273 cells.sort_unstable();
16274 cells
16275 }
16276 };
16277 let mut i = 0;
16278 while i < cells.len() {
16279 let (sheet, col, r0) = cells[i];
16280 let mut r1 = r0;
16281 let mut j = i + 1;
16282 while j < cells.len() && cells[j].0 == sheet && cells[j].1 == col {
16283 if cells[j].2 > r1 + 1 {
16284 break;
16285 }
16286 r1 = r1.max(cells[j].2);
16287 j += 1;
16288 }
16289 cover.insert_rect(sheet, &Rect::new(r0, col, r1, col));
16290 i = j;
16291 }
16292 }
16293 let mut hints = Vec::new();
16294 for (&reader, deps) in vdeps {
16295 let Some(reader) = self.graph.authority_cell_of_vertex(reader) else {
16296 continue;
16297 };
16298 for &dependency in deps {
16299 let Some(dep) = self.graph.authority_cell_of_vertex(dependency) else {
16300 continue;
16301 };
16302 hints.push(planner::PlanHint {
16303 reader: (reader.0, reader.2, reader.1),
16304 edge: EdgeKey {
16305 dep_sheet: reader.0,
16306 tag: Tag::X,
16307 lk: u32::MAX,
16308 proj: RefProj {
16309 sheet: dep.0,
16310 rows: AxisMap::fixed(dep.1, dep.1),
16311 cols: AxisMap::fixed(dep.2, dep.2),
16312 },
16313 },
16314 });
16315 }
16316 }
16317 let host = self.graph.authority_host();
16319 for &id in candidates.iter().filter(|_| host.has_observed()) {
16320 let Some(reads) = host.observed(id) else {
16321 continue;
16322 };
16323 let Some(reader) = self.graph.authority_cell_of_vertex(id) else {
16324 continue;
16325 };
16326 for &(sheet, r0, c0, r1, c1) in reads {
16327 hints.push(planner::PlanHint {
16328 reader: (reader.0, reader.2, reader.1),
16329 edge: EdgeKey {
16330 dep_sheet: reader.0,
16331 tag: Tag::X,
16332 lk: u32::MAX,
16333 proj: RefProj {
16334 sheet,
16335 rows: AxisMap::fixed(r0, r1),
16336 cols: AxisMap::fixed(c0, c1),
16337 },
16338 },
16339 });
16340 }
16341 }
16342 hints.sort_unstable_by_key(|hint| hint.reader);
16343 let checkpoint = ledger
16344 .as_ref()
16345 .map_or(0, |ledger| ledger.scratch_checkpoint());
16346 let scratch_limit = ledger
16347 .as_ref()
16348 .and_then(|ledger| ledger.schedule_discovery_limit())
16349 .map(|limit| limit.saturating_sub(checkpoint));
16350 let ordered = planner::plan_with_hints(store, &cover, &hints, scratch_limit, None, None)
16351 .map_err(|error| failure(format!("{error:?}")))?;
16352 let adapted = plan_schedule::schedule(
16356 &ordered.cells,
16357 ordered.heap_bytes(),
16358 scratch_limit,
16359 |cell| {
16360 self.graph
16361 .authority_vertex_of_formula(cell.id, (cell.sheet, cell.row, cell.col))
16362 .ok_or_else(|| failure("missing executor identity".to_owned()))
16363 },
16364 |_work| {
16365 self.cancellation_checkpoint("Evaluation cancelled during authority planning")?;
16366 if let Some(ledger) = ledger.as_deref_mut() {
16367 ledger
16368 .checkpoint_deadline()
16369 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16370 }
16371 Ok(())
16372 },
16373 )
16374 .map_err(|error| match error {
16375 plan_schedule::ScheduleError::Runtime(error) => error,
16376 other => failure(format!("{other:?}")),
16377 })?;
16378 if let Some(ledger) = ledger {
16379 let peak = ordered.peak_heap_bytes.max(adapted.peak_heap_bytes);
16380 ledger
16381 .reserve_schedule_discovery(peak)
16382 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16383 ledger
16384 .release_scratch(peak)
16385 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
16386 }
16387 match self.thread_pool.as_deref() {
16389 Some(pool) if ordered.cells.len() >= PARALLEL_SCHEDULE_MIN_CANDIDATES => {
16390 pool.spawn(move || drop(ordered))
16391 }
16392 _ => drop(ordered),
16393 }
16394 Ok(adapted.schedule)
16395 }
16396
16397 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
16403 {
16404 let host = self.graph.authority_host();
16408 !to_evaluate.is_empty()
16409 && !self.freshness_has_hints()
16410 && to_evaluate
16411 .iter()
16412 .all(|&v| !self.graph.is_dynamic(v) || host.observed(v).is_some())
16413 }
16414 }
16415
16416 fn schedule_cache_authority_revision(&self) -> (u64, u64) {
16417 {
16418 let host = self.graph.authority_host();
16421 (host.revision(), host.rev_dyn())
16422 }
16423 }
16424
16425 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
16426 VirtualDepTelemetry {
16427 fallback_mode_activations: self.virtual_dep_fallback_activations,
16428 ..VirtualDepTelemetry::default()
16429 }
16430 }
16431
16432 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
16433 telemetry.candidate_vertices_total += meta.candidate_vertices;
16434 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
16435 telemetry.vdeps_edges_total += meta.vdeps_edges;
16436 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
16437 if meta.schedule_cache_eligible {
16438 if meta.schedule_cache_hit {
16439 telemetry.schedule_cache_hits += 1;
16440 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
16441 } else {
16442 telemetry.schedule_cache_misses += 1;
16443 }
16444 }
16445 if meta.used_virtual_schedule {
16446 telemetry.schedule_virtual_passes += 1;
16447 } else {
16448 telemetry.schedule_static_passes += 1;
16449 }
16450 }
16451
16452 fn finish_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
16457 self.finish_pass_dirty_scoped(to_evaluate, changed, true)
16458 }
16459
16460 fn finish_target_pass_dirty(&mut self, to_evaluate: &[VertexId], changed: &[VertexId]) -> bool {
16463 self.finish_pass_dirty_scoped(to_evaluate, changed, false)
16464 }
16465
16466 fn finish_pass_dirty_scoped(
16467 &mut self,
16468 to_evaluate: &[VertexId],
16469 changed: &[VertexId],
16470 #[allow(unused_variables)] whole_workbook: bool,
16471 ) -> bool {
16472 self.freshness_finish_pass(to_evaluate, changed, whole_workbook)
16473 }
16474
16475 fn begin_pass(
16477 &mut self,
16478 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
16479 ) {
16480 self.freshness_begin_pass(schedule);
16481 }
16482
16483 fn stop_after_unit(
16485 &mut self,
16486 #[allow(unused_variables)] schedule: &crate::engine::scheduler::Schedule,
16487 #[allow(unused_variables)] index: usize,
16488 ) -> bool {
16489 self.freshness_stop_after_unit(schedule, index)
16490 }
16491
16492 fn changed_virtual_dep_vertices(
16493 &mut self,
16494 to_evaluate: &[VertexId],
16495 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
16496 ) -> Vec<VertexId> {
16497 #[cfg(test)]
16498 if self.force_virtual_dep_changes_remaining_for_test > 0
16499 && let Some(vertex) = to_evaluate.first().copied()
16500 {
16501 self.force_virtual_dep_changes_remaining_for_test -= 1;
16502 return vec![vertex];
16503 }
16504 if self.freshness_armed() {
16507 return Vec::new();
16508 }
16509 if !to_evaluate
16510 .iter()
16511 .copied()
16512 .any(|v| self.graph.is_dynamic(v))
16513 {
16514 return Vec::new();
16515 }
16516
16517 let builder = VirtualDepBuilder::new(self);
16518 let (new_vdeps, _) = builder.build(to_evaluate);
16519
16520 let mut candidates = FxHashSet::default();
16521 candidates.extend(old_vdeps.keys().copied());
16522 candidates.extend(new_vdeps.keys().copied());
16523
16524 let mut changed = Vec::new();
16525 for v in candidates {
16526 if old_vdeps.get(&v) != new_vdeps.get(&v) {
16527 changed.push(v);
16528 }
16529 }
16530 changed
16531 }
16532
16533 #[allow(clippy::type_complexity)]
16539 fn demand_subgraph(
16540 &self,
16541 targets: &[VertexId],
16542 ) -> Result<
16543 (
16544 Vec<VertexId>,
16545 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
16546 ),
16547 ExcelError,
16548 > {
16549 self.authority_demand_subgraph(targets)
16550 }
16551
16552 #[allow(clippy::type_complexity)]
16558 fn authority_demand_subgraph(
16559 &self,
16560 targets: &[VertexId],
16561 ) -> Result<
16562 (
16563 Vec<VertexId>,
16564 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
16565 ),
16566 ExcelError,
16567 > {
16568 use crate::engine::authority::geom::{Cell, Rect, SYMBOL_SHEET};
16569 use crate::engine::authority::store::TagFilter;
16570 use rustc_hash::{FxHashMap, FxHashSet};
16571 let store = self
16572 .graph
16573 .authority_plan_store()
16574 .map_err(Self::authority_excel_error)?;
16575 let ids = store.ids();
16576 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
16577 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default();
16578 let mut visited: FxHashSet<Cell> = FxHashSet::default();
16579 let mut stack: Vec<(Cell, u32)> = Vec::new();
16582 let push_formulas = |stack: &mut Vec<(Cell, u32)>,
16584 visited: &FxHashSet<Cell>,
16585 sheet: u16,
16586 rect: Rect| {
16587 for col in rect.c0..=rect.c1 {
16588 ids.visit_runs_in(sheet, col, rect.r0, rect.r1, &mut |h| {
16589 let run = ids.run(h);
16590 let r0 = run.row_start.max(rect.r0);
16591 let r1 = (run.row_start + run.len - 1).min(rect.r1);
16592 for row in r0..=r1 {
16593 if !visited.contains(&(sheet, row, col)) {
16594 stack.push(((sheet, row, col), run.first_id + (row - run.row_start)));
16595 }
16596 }
16597 });
16598 }
16599 };
16600 for &v in targets {
16601 if let Some(table) = self.graph.table_by_vertex(v) {
16602 let (s, e) = (table.range.start, table.range.end);
16606 push_formulas(
16607 &mut stack,
16608 &visited,
16609 s.sheet_id,
16610 Rect::new(s.coord.row(), s.coord.col(), e.coord.row(), e.coord.col()),
16611 );
16612 } else if let Some(cell) = self.graph.authority_cell_of_vertex(v) {
16613 stack.push((cell, crate::engine::authority::identity::NO_VID));
16614 }
16615 }
16616 let mut hits = Vec::new();
16617 #[cfg(any(test, feature = "benchmark_internal"))]
16618 let (mut probe_vertices, mut probe_clean_formulas, mut probe_edges, mut probe_dynamic) =
16619 (0, 0, 0, 0);
16620 while let Some((cell, id)) = stack.pop() {
16621 if !visited.insert(cell) {
16622 continue;
16623 }
16624 let Some(v) = self.graph.authority_vertex_of_formula(id, cell) else {
16625 continue;
16626 };
16627 if !self.graph.vertex_exists(v) {
16628 continue;
16629 }
16630 #[cfg(any(test, feature = "benchmark_internal"))]
16631 {
16632 probe_vertices += 1;
16633 }
16634 match self.graph.get_vertex_kind(v) {
16635 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
16636 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
16637 to_evaluate.insert(v);
16638 } else {
16639 #[cfg(any(test, feature = "benchmark_internal"))]
16640 {
16641 probe_clean_formulas += 1;
16642 }
16643 }
16644 }
16645 VertexKind::NamedScalar | VertexKind::NamedArray => {
16646 to_evaluate.insert(v);
16647 }
16648 _ => {}
16649 }
16650 hits.clear();
16651 store.direct_precedents(cell, TagFilter::All, &mut hits);
16652 #[cfg(any(test, feature = "benchmark_internal"))]
16653 {
16654 probe_edges += hits.len();
16655 }
16656 for &(_, sheet, rect) in &hits {
16657 if sheet != SYMBOL_SHEET {
16660 for anchor in self
16661 .graph
16662 .spill_anchors_in_region(sheet, rect.r0, rect.c0, rect.r1, rect.c1)
16663 {
16664 if anchor != v && self.graph.is_dirty(anchor) {
16665 vdeps.entry(v).or_default().push(anchor);
16666 if let Some(c) = self.graph.authority_cell_of_vertex(anchor) {
16667 stack.push((c, crate::engine::authority::identity::NO_VID));
16668 }
16669 }
16670 }
16671 }
16672 if sheet == SYMBOL_SHEET {
16673 stack.extend((rect.r0..=rect.r1).map(|slot| {
16674 (
16675 (SYMBOL_SHEET, slot, 0),
16676 crate::engine::authority::identity::NO_VID,
16677 )
16678 }));
16679 continue;
16680 }
16681 push_formulas(&mut stack, &visited, sheet, rect);
16682 }
16683 if self.graph.is_dynamic(v) {
16684 #[cfg(any(test, feature = "benchmark_internal"))]
16685 {
16686 probe_dynamic += 1;
16687 }
16688 if let Some(reads) = self.graph.authority_host().observed(v) {
16690 for &(sheet, r0, c0, r1, c1) in reads {
16691 push_formulas(&mut stack, &visited, sheet, Rect::new(r0, c0, r1, c1));
16692 }
16693 }
16694 let (vdeps_map, _) = VirtualDepBuilder::new(self).build(&[v]);
16695 let hinted = self.freshness_hints(v).unwrap_or(&[]);
16698 if let Some(deps) = vdeps_map
16699 .get(&v)
16700 .map(|deps| deps.iter().chain(hinted))
16701 .or(Some([].iter().chain(hinted)))
16702 {
16703 for &u in deps {
16704 vdeps.entry(v).or_default().push(u);
16705 if let Some(c) = self.graph.authority_cell_of_vertex(u) {
16706 stack.push((c, crate::engine::authority::identity::NO_VID));
16707 }
16708 }
16709 }
16710 }
16711 }
16712 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
16713 result.sort_unstable();
16714 for deps in vdeps.values_mut() {
16715 deps.sort_unstable();
16716 deps.dedup();
16717 }
16718 #[cfg(any(test, feature = "benchmark_internal"))]
16719 {
16720 let mut probe = self.recalc_reuse_probe.lock().unwrap();
16721 probe.demand_builds += 1;
16722 probe.demand_vertices += probe_vertices;
16723 probe.demand_clean_formulas += probe_clean_formulas;
16724 probe.demand_explicit_edges += probe_edges;
16725 probe.demand_virtual_builder_calls += probe_dynamic;
16726 }
16727 Ok((result, vdeps))
16728 }
16729
16730 fn col_to_letters(col: u32) -> String {
16732 col_letters_from_1based(col).expect("column index must be >= 1")
16733 }
16734
16735 pub fn evaluate_all_cancellable(
16737 &mut self,
16738 cancel: crate::engine::CancelToken,
16739 ) -> Result<EvalResult, ExcelError> {
16740 self.observe_evaluation_resource_request(EvaluationRequestKind::FullCancellable, |engine| {
16741 engine.observe_function_semantic_epoch()?;
16742 engine.active_cancel_flag = Some(cancel.clone());
16743 let res = engine.evaluate_all_cancellable_impl(cancel.as_flag());
16744 engine.active_cancel_flag = None;
16745 res
16746 })
16747 }
16748
16749 fn evaluate_all_cancellable_impl(
16750 &mut self,
16751 cancel_flag: &AtomicBool,
16752 ) -> Result<EvalResult, ExcelError> {
16753 let _source_cache = self.source_cache_session();
16754 self.validate_deterministic_mode()?;
16755 if self.config.defer_graph_building {
16756 self.build_graph_all()?;
16757 }
16758 if cancel_flag.load(Ordering::Relaxed) {
16759 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16760 .with_message("Evaluation cancelled before scheduling".to_string()));
16761 }
16762 self.require_unified_authority()?;
16763 self.begin_evaluation_request();
16764 self.reset_virtual_dep_telemetry_if_disabled();
16765 let start = crate::instant::FzInstant::now();
16766 let mut computed_vertices = 0;
16767 let mut cycle_errors = 0;
16768
16769 let mut replan_iterations = 0;
16770 const MAX_REPLAN: usize = 5;
16771 let mut telemetry = self
16772 .config
16773 .enable_virtual_dep_telemetry
16774 .then(|| self.start_virtual_dep_telemetry());
16775
16776 loop {
16777 if cancel_flag.load(Ordering::Relaxed) {
16778 if let Some(mut t) = telemetry {
16779 t.bailout_reason = Some("cancelled");
16780 t.replan_iterations = replan_iterations;
16781 self.last_virtual_dep_telemetry = t;
16782 }
16783 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16784 .with_message("Evaluation cancelled before scheduling".to_string()));
16785 }
16786
16787 let to_evaluate = self.graph.get_evaluation_vertices();
16788 if to_evaluate.is_empty() {
16789 if let Some(t) = telemetry.as_mut()
16790 && t.bailout_reason.is_none()
16791 {
16792 t.bailout_reason = Some("no_work");
16793 }
16794 break;
16795 }
16796
16797 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
16798 if let Some(t) = telemetry.as_mut() {
16799 Self::accumulate_schedule_meta(t, &meta);
16800 }
16801
16802 self.begin_pass(&schedule);
16805 for (unit_index, &unit) in schedule.units.iter().enumerate() {
16806 match unit {
16807 ScheduleUnit::Cycle(i) => {
16808 if cancel_flag.load(Ordering::Relaxed) {
16810 if let Some(mut t) = telemetry {
16811 t.bailout_reason = Some("cancelled");
16812 t.replan_iterations = replan_iterations;
16813 self.last_virtual_dep_telemetry = t;
16814 }
16815 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
16816 "Evaluation cancelled during cycle handling".to_string(),
16817 ));
16818 }
16819
16820 if self.handle_cycle_unit(
16821 schedule.unit_cycle(i),
16822 None,
16823 None,
16824 Some(cancel_flag),
16825 )? > 0
16826 {
16827 cycle_errors += 1;
16828 }
16829 }
16830 ScheduleUnit::Layer(i) => {
16831 let layer = schedule.unit_layer(i);
16832 if cancel_flag.load(Ordering::Relaxed) {
16834 if let Some(mut t) = telemetry {
16835 t.bailout_reason = Some("cancelled");
16836 t.replan_iterations = replan_iterations;
16837 self.last_virtual_dep_telemetry = t;
16838 }
16839 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16840 .with_message("Evaluation cancelled between layers".to_string()));
16841 }
16842
16843 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
16845 computed_vertices +=
16846 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
16847 } else {
16848 computed_vertices +=
16849 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
16850 }
16851 }
16852 }
16853 if self.stop_after_unit(&schedule, unit_index) {
16854 break;
16855 }
16856 }
16857
16858 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
16859 if let Some(t) = telemetry.as_mut() {
16860 t.changed_vdeps_total += changed_vertices.len();
16861 }
16862 self.resource_checkpoint(0)?;
16863 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
16864 if let Some(t) = telemetry.as_mut() {
16865 t.bailout_reason = Some("converged");
16866 }
16867 break;
16868 }
16869 if replan_iterations >= MAX_REPLAN {
16870 if let Some(mut t) = telemetry.take() {
16871 t.bailout_reason = Some("max_replan");
16872 t.replan_iterations = replan_iterations;
16873 self.last_virtual_dep_telemetry = t;
16874 }
16875 return Err(
16876 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
16877 );
16878 }
16879 replan_iterations += 1;
16880 }
16881
16882 if let Some(mut t) = telemetry {
16883 t.replan_iterations = replan_iterations;
16884 self.last_virtual_dep_telemetry = t;
16885 }
16886
16887 self.redirty_for_next_recalc();
16889 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
16890
16891 Ok(EvalResult {
16892 computed_vertices,
16893 cycle_errors,
16894 elapsed: start.elapsed(),
16895 })
16896 }
16897
16898 pub fn evaluate_until_cancellable(
16900 &mut self,
16901 targets: &[&str],
16902 cancel: crate::engine::CancelToken,
16903 ) -> Result<EvalResult, ExcelError> {
16904 self.observe_evaluation_resource_request(
16905 EvaluationRequestKind::TargetedCancellable,
16906 |engine| {
16907 engine.observe_function_semantic_epoch()?;
16908 engine.active_cancel_flag = Some(cancel.clone());
16909 let res = engine.evaluate_until_cancellable_impl(targets, cancel.as_flag());
16910 engine.active_cancel_flag = None;
16911 res
16912 },
16913 )
16914 }
16915
16916 fn evaluate_until_cancellable_impl(
16917 &mut self,
16918 targets: &[&str],
16919 cancel_flag: &AtomicBool,
16920 ) -> Result<EvalResult, ExcelError> {
16921 if cancel_flag.load(Ordering::Relaxed) {
16922 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
16923 .with_message("Evaluation cancelled before target preparation"));
16924 }
16925 let mut typed_targets = Vec::with_capacity(targets.len());
16926 for target in targets {
16927 let (sheet, row, col) = self.parse_a1_notation(target)?;
16928 self.graph.sheet_id_mut(&sheet);
16929 typed_targets.push(crate::engine::EvaluationTarget::Cell { sheet, row, col });
16930 }
16931 self.evaluate_mixed_targets(&typed_targets, None)
16932 }
16933
16934 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
16935 let mut quoted = false;
16936 let mut separator = None;
16937 let bytes = address.as_bytes();
16938 let mut index = 0usize;
16939 while index < bytes.len() {
16940 match bytes[index] {
16941 b'\'' => {
16942 if quoted && bytes.get(index + 1) == Some(&b'\'') {
16943 index = index.saturating_add(1);
16944 } else {
16945 quoted = !quoted;
16946 }
16947 }
16948 b'!' if !quoted => separator = Some(index),
16949 _ => {}
16950 }
16951 index = index.saturating_add(1);
16952 }
16953 if quoted {
16954 return Err(ExcelError::new(ExcelErrorKind::Ref)
16955 .with_message(format!("Invalid quoted sheet reference `{address}`")));
16956 }
16957 let (sheet, cell_part) = match separator {
16958 Some(separator) => {
16959 let raw_sheet = &address[..separator];
16960 let sheet = if raw_sheet.starts_with('\'') && raw_sheet.ends_with('\'') {
16961 raw_sheet[1..raw_sheet.len().saturating_sub(1)].replace("''", "'")
16962 } else {
16963 raw_sheet.to_string()
16964 };
16965 (sheet, &address[separator + 1..])
16966 }
16967 None => (self.default_sheet_name().to_string(), address),
16968 };
16969
16970 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
16971 ExcelError::new(ExcelErrorKind::Ref)
16972 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
16973 })?;
16974
16975 Ok((sheet, row, col))
16976 }
16977
16978 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
16980 use formualizer_parse::parser::ASTNodeType;
16981 match &ast.node_type {
16982 ASTNodeType::Function { name, args, .. } => {
16983 if let Some(func) = self
16984 .get_function("", name)
16985 .or_else(|| crate::function_registry::get("", name))
16986 && func.caps().contains(crate::function::FnCaps::VOLATILE)
16987 {
16988 return true;
16989 }
16990 args.iter()
16991 .any(|arg| self.is_ast_volatile_with_provider(arg))
16992 }
16993 ASTNodeType::BinaryOp { left, right, .. } => {
16994 self.is_ast_volatile_with_provider(left)
16995 || self.is_ast_volatile_with_provider(right)
16996 }
16997 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
16998 ASTNodeType::Array(rows) => rows.iter().any(|row| {
16999 row.iter()
17000 .any(|cell| self.is_ast_volatile_with_provider(cell))
17001 }),
17002 _ => false,
17003 }
17004 }
17005
17006 fn evaluate_layer_sequential(
17008 &mut self,
17009 layer: &super::scheduler::Layer,
17010 ) -> Result<usize, ExcelError> {
17011 self.resource_checkpoint(layer.vertices.len() as u64)?;
17012 self.evaluate_layer_sequential_effects(layer)
17013 }
17014
17015 fn update_vertex_value_with_delta(
17016 &mut self,
17017 vertex_id: VertexId,
17018 new_value: LiteralValue,
17019 delta: &mut DeltaCollector,
17020 ) {
17021 if delta.mode != DeltaMode::Off
17022 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
17023 {
17024 let sheet_name = self.graph.sheet_name(cell.sheet_id);
17025 let old = self
17026 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
17027 .unwrap_or(LiteralValue::Empty);
17028 if old != new_value {
17029 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
17030 }
17031 }
17032 self.graph.update_vertex_value_ref(vertex_id, &new_value);
17033 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
17034 }
17035
17036 fn evaluate_layer_sequential_with_delta(
17037 &mut self,
17038 layer: &super::scheduler::Layer,
17039 delta: &mut DeltaCollector,
17040 ) -> Result<usize, ExcelError> {
17041 self.resource_checkpoint(layer.vertices.len() as u64)?;
17042 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
17043 }
17044
17045 fn evaluate_layer_sequential_cancellable(
17047 &mut self,
17048 layer: &super::scheduler::Layer,
17049 cancel_flag: &AtomicBool,
17050 ) -> Result<usize, ExcelError> {
17051 self.resource_checkpoint(layer.vertices.len() as u64)?;
17052 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
17053 }
17054
17055 fn evaluate_layer_sequential_cancellable_demand_driven(
17057 &mut self,
17058 layer: &super::scheduler::Layer,
17059 cancel_flag: &AtomicBool,
17060 ) -> Result<usize, ExcelError> {
17061 self.resource_checkpoint(layer.vertices.len() as u64)?;
17062 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
17063 }
17064
17065 fn evaluate_layer_parallel(
17077 &mut self,
17078 layer: &super::scheduler::Layer,
17079 ) -> Result<usize, ExcelError> {
17080 if layer.sequential {
17081 return self.evaluate_layer_sequential(layer);
17082 }
17083 self.resource_checkpoint(layer.vertices.len() as u64)?;
17084 let len = layer.vertices.len();
17085 let buffered = buffer_layer_writes(layer);
17086 let (probe, worth) = (PARALLEL_LAYER_PROBE, PARALLEL_LAYER_WORTH);
17087 let start = crate::instant::FzInstant::now();
17088 let mut pos = 0usize;
17089 let mut step = 1usize;
17090 while pos < len {
17091 if pos > 0 {
17092 let elapsed = start.elapsed();
17093 let per_vertex = elapsed.as_nanos() / pos as u128 + 1;
17095 let rest_estimate = per_vertex * (len - pos) as u128;
17096 if len - pos >= 2 && (elapsed >= probe || rest_estimate >= worth.as_nanos()) {
17097 let rest = layer.sub_layer(pos, len);
17098 let min_chunk = if per_vertex >= EXPENSIVE_VERTEX_NS {
17101 1
17102 } else {
17103 8
17104 };
17105 return Ok(pos + self.evaluate_layer_parallel_effects(&rest, min_chunk)?);
17106 }
17107 let fit = (probe.saturating_sub(elapsed).as_nanos() / per_vertex) as usize + 1;
17110 step = step.saturating_mul(2).min(fit);
17111 }
17112 let end = (pos + step).min(len);
17113 let slice = layer.sub_layer(pos, end);
17114 pos += self.evaluate_layer_units_until(
17117 &slice,
17118 None,
17119 None,
17120 None,
17121 buffered,
17122 Some(start + probe),
17123 )?;
17124 }
17125 Ok(len)
17126 }
17127
17128 fn evaluate_layer_parallel_with_delta(
17129 &mut self,
17130 layer: &super::scheduler::Layer,
17131 delta: &mut DeltaCollector,
17132 ) -> Result<usize, ExcelError> {
17133 if layer.sequential {
17134 return self.evaluate_layer_sequential_with_delta(layer, delta);
17135 }
17136 self.resource_checkpoint(layer.vertices.len() as u64)?;
17137 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
17138 }
17139
17140 fn evaluate_layer_parallel_cancellable(
17142 &mut self,
17143 layer: &super::scheduler::Layer,
17144 cancel_flag: &AtomicBool,
17145 ) -> Result<usize, ExcelError> {
17146 if layer.sequential {
17147 return self.evaluate_layer_sequential_cancellable(layer, cancel_flag);
17148 }
17149 self.resource_checkpoint(layer.vertices.len() as u64)?;
17150 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
17151 }
17152
17153 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
17155 if !self.graph.vertex_exists(vertex_id) {
17157 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
17158 .with_message(format!("Vertex not found: {vertex_id:?}")));
17159 }
17160
17161 let kind = self.graph.get_vertex_kind(vertex_id);
17163 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
17164
17165 let view = match kind {
17166 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
17167 if let Some(view) = self.graph.formula_view(vertex_id) {
17168 view
17169 } else {
17170 return Ok(LiteralValue::Number(0.0));
17171 }
17172 }
17173 VertexKind::Empty | VertexKind::Cell => {
17174 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
17175 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
17176 let row = cell_ref.coord.row() + 1;
17177 let col = cell_ref.coord.col() + 1;
17178 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
17179 return Ok(v);
17180 }
17181 }
17182 return Ok(LiteralValue::Number(0.0));
17183 }
17184 VertexKind::NamedScalar => {
17185 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
17186 ExcelError::new(ExcelErrorKind::Name)
17187 .with_message("Named range metadata missing".to_string())
17188 })?;
17189
17190 return match &named_range.definition {
17191 NamedDefinition::Cell(cell_ref) => {
17192 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
17193 Ok(self
17194 .get_cell_value(
17195 sheet_name,
17196 cell_ref.coord.row() + 1,
17197 cell_ref.coord.col() + 1,
17198 )
17199 .unwrap_or(LiteralValue::Empty))
17200 }
17201 NamedDefinition::Literal(v) => Ok(v.clone()),
17202 NamedDefinition::Formula { ast, .. } => {
17203 let context_sheet = match named_range.scope {
17204 NameScope::Sheet(id) => id,
17205 NameScope::Workbook => sheet_id,
17206 };
17207 let sheet_name = self.graph.sheet_name(context_sheet);
17208 let cell_ref = self
17209 .graph
17210 .get_cell_ref(vertex_id)
17211 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
17212 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
17213 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
17214 }
17215 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
17216 .with_message("Range-valued name evaluated as scalar".to_string())),
17217 };
17218 }
17219 VertexKind::NamedArray => {
17220 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
17221 ExcelError::new(ExcelErrorKind::Name)
17222 .with_message("Named range metadata missing".to_string())
17223 })?;
17224
17225 return match &named_range.definition {
17226 NamedDefinition::Range(range_ref) => {
17227 if range_ref.start.sheet_id != range_ref.end.sheet_id {
17228 return Err(ExcelError::new(ExcelErrorKind::Ref)
17229 .with_message("Named range cannot span sheets".to_string()));
17230 }
17231 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
17232 let sr0 = range_ref.start.coord.row();
17233 let sc0 = range_ref.start.coord.col();
17234 let er0 = range_ref.end.coord.row();
17235 let ec0 = range_ref.end.coord.col();
17236 if sr0 > er0 || sc0 > ec0 {
17237 return Err(ExcelError::new(ExcelErrorKind::Ref)
17238 .with_message("Invalid named range bounds".to_string()));
17239 }
17240
17241 let h = (er0 - sr0 + 1) as usize;
17242 let w = (ec0 - sc0 + 1) as usize;
17243 let cell_count = (h as u64).saturating_mul(w as u64);
17244 if cell_count > self.config.spill.max_spill_cells as u64 {
17245 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
17246 "Named range too large to materialize as an array".to_string(),
17247 ));
17248 }
17249
17250 let sheet_id = range_ref.start.sheet_id;
17253 let asheet = self.arrow_sheets.sheet(sheet_name);
17254 let mut rows = Vec::with_capacity(h);
17255 for r0 in sr0..=er0 {
17256 let mut row = Vec::with_capacity(w);
17257 for c0 in sc0..=ec0 {
17258 row.push(
17259 self.read_cell_formatted_in(sheet_id, asheet, r0 + 1, c0 + 1)
17260 .0,
17261 );
17262 }
17263 rows.push(row);
17264 }
17265 Ok(LiteralValue::Array(rows))
17266 }
17267 NamedDefinition::Cell(cell_ref) => {
17268 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
17269 let row = cell_ref.coord.row() + 1;
17270 let col = cell_ref.coord.col() + 1;
17271 let v = self
17272 .get_cell_value(sheet_name, row, col)
17273 .unwrap_or(LiteralValue::Empty);
17274 Ok(LiteralValue::Array(vec![vec![v]]))
17275 }
17276 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
17277 NamedDefinition::Formula { ast, .. } => {
17278 let context_sheet = match named_range.scope {
17279 NameScope::Sheet(id) => id,
17280 NameScope::Workbook => sheet_id,
17281 };
17282 let sheet_name = self.graph.sheet_name(context_sheet);
17283 let cell_ref = self
17284 .graph
17285 .get_cell_ref(vertex_id)
17286 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
17287 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
17288 match interpreter.evaluate_ast(ast) {
17289 Ok(cv) => {
17290 let v = cv.into_literal();
17291 match v {
17292 LiteralValue::Array(_) => Ok(v),
17293 other => Ok(LiteralValue::Array(vec![vec![other]])),
17294 }
17295 }
17296 Err(err) => Ok(LiteralValue::Error(err)),
17297 }
17298 }
17299 };
17300 }
17301 VertexKind::InfiniteRange
17302 | VertexKind::Range
17303 | VertexKind::External
17304 | VertexKind::Table => {
17305 return Ok(LiteralValue::Number(0.0));
17307 }
17308 };
17309
17310 let sheet_name = self.graph.sheet_name(sheet_id);
17312 let cell_ref = self
17313 .graph
17314 .get_cell_ref(vertex_id)
17315 .expect("cell ref for vertex");
17316 if let Some(result) =
17317 self.freshness_evaluate_recorded(vertex_id, sheet_name, cell_ref, view)
17318 {
17319 return result;
17320 }
17321 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
17322
17323 interpreter
17324 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
17325 .map(|cv| {
17326 let format = cv.format_id();
17327 self.record_derived_format(vertex_id, format);
17328 self.materialize_formula_result(vertex_id, cv)
17329 })
17330 .or_else(|error| self.fit_formula_error(vertex_id, error))
17331 }
17332
17333 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
17335 self.thread_pool.as_ref()
17336 }
17337}
17338
17339#[derive(Default)]
17340struct RowBoundsCache {
17341 snapshot: u64,
17342 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
17344}
17345
17346impl RowBoundsCache {
17347 fn new(snapshot: u64) -> Self {
17348 Self {
17349 snapshot,
17350 map: Default::default(),
17351 }
17352 }
17353 fn get_row_bounds(
17354 &self,
17355 sheet_id: SheetId,
17356 col_idx: usize,
17357 snapshot: u64,
17358 ) -> Option<(Option<u32>, Option<u32>)> {
17359 if self.snapshot != snapshot {
17360 return None;
17361 }
17362 self.map.get(&(sheet_id as u32, col_idx)).copied()
17363 }
17364 fn put_row_bounds(
17365 &mut self,
17366 sheet_id: SheetId,
17367 col_idx: usize,
17368 snapshot: u64,
17369 bounds: (Option<u32>, Option<u32>),
17370 ) {
17371 if self.snapshot != snapshot {
17372 self.snapshot = snapshot;
17373 self.map.clear();
17374 }
17375 self.map.insert((sheet_id as u32, col_idx), bounds);
17376 }
17377}
17378
17379struct UsedAxisBoundsCache {
17380 snapshot: u64,
17381 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
17382 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
17383 #[cfg(test)]
17384 row_hits: std::sync::atomic::AtomicUsize,
17385 #[cfg(test)]
17386 row_misses: std::sync::atomic::AtomicUsize,
17387 #[cfg(test)]
17388 col_hits: std::sync::atomic::AtomicUsize,
17389 #[cfg(test)]
17390 col_misses: std::sync::atomic::AtomicUsize,
17391}
17392
17393impl UsedAxisBoundsCache {
17394 fn new(snapshot: u64) -> Self {
17395 Self {
17396 snapshot,
17397 row_bounds_by_col_span: Default::default(),
17398 col_bounds_by_row_span: Default::default(),
17399 #[cfg(test)]
17400 row_hits: std::sync::atomic::AtomicUsize::new(0),
17401 #[cfg(test)]
17402 row_misses: std::sync::atomic::AtomicUsize::new(0),
17403 #[cfg(test)]
17404 col_hits: std::sync::atomic::AtomicUsize::new(0),
17405 #[cfg(test)]
17406 col_misses: std::sync::atomic::AtomicUsize::new(0),
17407 }
17408 }
17409
17410 fn reset_for_snapshot(&mut self, snapshot: u64) {
17411 if self.snapshot != snapshot {
17412 self.snapshot = snapshot;
17413 self.row_bounds_by_col_span.clear();
17414 self.col_bounds_by_row_span.clear();
17415 }
17416 }
17417
17418 fn get_row_bounds(
17419 &self,
17420 sheet_id: SheetId,
17421 start_col: u32,
17422 end_col: u32,
17423 snapshot: u64,
17424 ) -> Option<Option<(u32, u32)>> {
17425 if self.snapshot != snapshot {
17426 return None;
17427 }
17428 let cached = self
17429 .row_bounds_by_col_span
17430 .get(&(sheet_id, start_col, end_col))
17431 .copied();
17432 #[cfg(test)]
17433 if cached.is_some() {
17434 self.row_hits.fetch_add(1, Ordering::Relaxed);
17435 }
17436 cached
17437 }
17438
17439 fn put_row_bounds(
17440 &mut self,
17441 sheet_id: SheetId,
17442 start_col: u32,
17443 end_col: u32,
17444 snapshot: u64,
17445 bounds: Option<(u32, u32)>,
17446 ) {
17447 self.reset_for_snapshot(snapshot);
17448 self.row_bounds_by_col_span
17449 .insert((sheet_id, start_col, end_col), bounds);
17450 #[cfg(test)]
17451 self.row_misses.fetch_add(1, Ordering::Relaxed);
17452 }
17453
17454 fn get_col_bounds(
17455 &self,
17456 sheet_id: SheetId,
17457 start_row: u32,
17458 end_row: u32,
17459 snapshot: u64,
17460 ) -> Option<Option<(u32, u32)>> {
17461 if self.snapshot != snapshot {
17462 return None;
17463 }
17464 let cached = self
17465 .col_bounds_by_row_span
17466 .get(&(sheet_id, start_row, end_row))
17467 .copied();
17468 #[cfg(test)]
17469 if cached.is_some() {
17470 self.col_hits.fetch_add(1, Ordering::Relaxed);
17471 }
17472 cached
17473 }
17474
17475 fn put_col_bounds(
17476 &mut self,
17477 sheet_id: SheetId,
17478 start_row: u32,
17479 end_row: u32,
17480 snapshot: u64,
17481 bounds: Option<(u32, u32)>,
17482 ) {
17483 self.reset_for_snapshot(snapshot);
17484 self.col_bounds_by_row_span
17485 .insert((sheet_id, start_row, end_row), bounds);
17486 #[cfg(test)]
17487 self.col_misses.fetch_add(1, Ordering::Relaxed);
17488 }
17489}
17490
17491#[derive(Default)]
17493pub struct ShimSpillManager {
17494 region_locks: RegionLockManager,
17495 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
17496}
17497
17498impl ShimSpillManager {
17499 pub(crate) fn reserve(
17500 &mut self,
17501 owner: VertexId,
17502 anchor_cell: CellRef,
17503 shape: SpillShape,
17504 _meta: SpillMeta,
17505 ) -> Result<(), ExcelError> {
17506 let region = crate::engine::spill::Region {
17508 sheet_id: anchor_cell.sheet_id as u32,
17509 row_start: anchor_cell.coord.row(),
17510 row_end: anchor_cell
17511 .coord
17512 .row()
17513 .saturating_add(shape.rows)
17514 .saturating_sub(1),
17515 col_start: anchor_cell.coord.col(),
17516 col_end: anchor_cell
17517 .coord
17518 .col()
17519 .saturating_add(shape.cols)
17520 .saturating_sub(1),
17521 };
17522 match self.region_locks.reserve(region, owner) {
17523 Ok(id) => {
17524 if id != 0 {
17525 self.active_locks.insert(owner, id);
17526 }
17527 Ok(())
17528 }
17529 Err(e) => Err(e),
17530 }
17531 }
17532
17533 pub(crate) fn release_owner(&mut self, owner: VertexId) {
17539 if let Some(id) = self.active_locks.remove(&owner) {
17540 self.region_locks.release(id);
17541 }
17542 }
17543
17544 pub(crate) fn commit_array_with_value_probe<F>(
17545 &mut self,
17546 graph: &mut DependencyGraph,
17547 anchor_vertex: VertexId,
17548 targets: &[CellRef],
17549 rows: Vec<Vec<LiteralValue>>,
17550 mut value_probe: F,
17551 ) -> Result<(), ExcelError>
17552 where
17553 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
17554 {
17555 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
17556
17557 let plan_res = graph.plan_spill_region(anchor_vertex, targets);
17561 if let Err(e) = plan_res {
17562 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17563 self.region_locks.release(id);
17564 }
17565 return Err(e);
17566 }
17567
17568 if !graph.value_cache_enabled() {
17569 let (expected_rows, expected_cols) = if targets.is_empty() {
17571 (0u32, 0u32)
17572 } else {
17573 let mut min_r = u32::MAX;
17574 let mut max_r = 0u32;
17575 let mut min_c = u32::MAX;
17576 let mut max_c = 0u32;
17577 for cell in targets {
17578 let r = cell.coord.row();
17579 let c = cell.coord.col();
17580 min_r = min_r.min(r);
17581 max_r = max_r.max(r);
17582 min_c = min_c.min(c);
17583 max_c = max_c.max(c);
17584 }
17585 (
17586 max_r.saturating_sub(min_r).saturating_add(1),
17587 max_c.saturating_sub(min_c).saturating_add(1),
17588 )
17589 };
17590
17591 let anchor_cell = graph
17592 .get_cell_ref(anchor_vertex)
17593 .expect("anchor cell ref for spill commit");
17594
17595 for cell in targets {
17596 if *cell == anchor_cell {
17598 continue;
17599 }
17600 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
17602 continue;
17603 }
17604 if let Some(vid) = graph.get_vertex_id_for_address(cell)
17606 && vid != anchor_vertex
17607 {
17608 match graph.get_vertex_kind(vid) {
17609 crate::engine::vertex::VertexKind::FormulaScalar
17610 | crate::engine::vertex::VertexKind::FormulaArray => {
17611 continue;
17613 }
17614 _ => {}
17615 }
17616 }
17617
17618 if let Some(v) = value_probe(graph, cell)
17619 && !matches!(v, LiteralValue::Empty)
17620 {
17621 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17622 self.region_locks.release(id);
17623 }
17624 return Err(ExcelError::new(ExcelErrorKind::Spill)
17625 .with_message("BlockedByValue")
17626 .with_extra(ExcelErrorExtra::Spill {
17627 expected_rows,
17628 expected_cols,
17629 }));
17630 }
17631 }
17632 }
17633
17634 let commit_res = graph.commit_spill_region_atomic_with_fault(
17635 anchor_vertex,
17636 targets.to_vec(),
17637 rows,
17638 None,
17639 );
17640 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17641 self.region_locks.release(id);
17642 }
17643 commit_res.map(|_| ())
17644 }
17645
17646 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
17648 &mut self,
17649 engine: &mut Engine<R>,
17650 anchor_vertex: VertexId,
17651 targets: &[CellRef],
17652 rows: Vec<Vec<LiteralValue>>,
17653 ) -> Result<(), ExcelError> {
17654 if let Err(error) = engine.guard_pending_spill_commit(anchor_vertex, targets) {
17655 self.release_owner(anchor_vertex);
17656 return Err(error);
17657 }
17658 let plan_res = engine.graph.plan_spill_region(anchor_vertex, targets);
17660 if let Err(e) = plan_res {
17661 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17662 self.region_locks.release(id);
17663 }
17664 return Err(e);
17665 }
17666
17667 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
17668 anchor_vertex,
17669 targets.to_vec(),
17670 rows.clone(),
17671 None,
17672 );
17673 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
17674 self.region_locks.release(id);
17675 }
17676 commit_res.map(|_| ())?;
17677 engine
17678 .blocked_pending_spills
17679 .retain(|entry| entry.0 != anchor_vertex);
17680 engine.spill_blocker_waiters.forget(anchor_vertex);
17681
17682 if engine.config.arrow_storage_enabled
17684 && engine.config.delta_overlay_enabled
17685 && engine.config.write_formula_overlay_enabled
17686 {
17687 for (idx, cell) in targets.iter().enumerate() {
17689 let (r_off, c_off) = {
17690 if rows.is_empty() || rows[0].is_empty() {
17691 (0usize, 0usize)
17692 } else {
17693 let width = rows[0].len();
17694 (idx / width, idx % width)
17695 }
17696 };
17697 let v = rows
17698 .get(r_off)
17699 .and_then(|r| r.get(c_off))
17700 .cloned()
17701 .unwrap_or(LiteralValue::Empty);
17702 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
17703 engine.mirror_value_to_computed_overlay(
17704 &sheet_name,
17705 cell.coord.row() + 1,
17706 cell.coord.col() + 1,
17707 &v,
17708 );
17709 }
17710 }
17711 Ok(())
17712 }
17713}
17714
17715impl<R> Engine<R>
17716where
17717 R: EvaluationContext,
17718{
17719 fn resolve_shared_ref(
17720 &self,
17721 reference: &ReferenceType,
17722 current_sheet: &str,
17723 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
17724 use formualizer_common::{
17725 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
17726 SheetRef as SharedRef,
17727 };
17728
17729 let sr = match reference {
17731 ReferenceType::Cell {
17732 sheet,
17733 row,
17734 col,
17735 row_abs,
17736 col_abs,
17737 } => {
17738 let row0 = row
17739 .checked_sub(1)
17740 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17741 let col0 = col
17742 .checked_sub(1)
17743 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17744 let sheet_loc = match sheet.as_deref() {
17745 Some(name) => SheetLocator::from_name(name),
17746 None => SheetLocator::Current,
17747 };
17748 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
17749 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
17750 }
17751 ReferenceType::Range {
17752 sheet,
17753 start_row,
17754 start_col,
17755 end_row,
17756 end_col,
17757 start_row_abs,
17758 start_col_abs,
17759 end_row_abs,
17760 end_col_abs,
17761 } => {
17762 let sheet_loc = match sheet.as_deref() {
17763 Some(name) => SheetLocator::from_name(name),
17764 None => SheetLocator::Current,
17765 };
17766 let sr = start_row
17767 .map(|r| {
17768 r.checked_sub(1)
17769 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17770 })
17771 .transpose()?;
17772 let sc = start_col
17773 .map(|c| {
17774 c.checked_sub(1)
17775 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17776 })
17777 .transpose()?;
17778 let er = end_row
17779 .map(|r| {
17780 r.checked_sub(1)
17781 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17782 })
17783 .transpose()?;
17784 let ec = end_col
17785 .map(|c| {
17786 c.checked_sub(1)
17787 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17788 })
17789 .transpose()?;
17790 let range = SharedRangeRef::from_parts(
17791 sheet_loc,
17792 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
17793 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
17794 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
17795 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
17796 )
17797 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
17798 SharedRef::Range(range)
17799 }
17800 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
17801 };
17802
17803 let current_id = self
17804 .graph
17805 .sheet_id(current_sheet)
17806 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
17807
17808 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
17809 match loc {
17810 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
17811 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
17812 SheetLocator::Name(name) => {
17813 let n = name.as_ref();
17814 self.graph
17815 .sheet_id(n)
17816 .map(SheetLocator::Id)
17817 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
17818 }
17819 }
17820 };
17821
17822 match sr {
17823 SharedRef::Cell(cell) => {
17824 let owned = cell.into_owned();
17825 let sheet = resolve_loc(owned.sheet)?;
17826 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
17827 }
17828 SharedRef::Range(range) => {
17829 let owned = range.into_owned();
17830 let sheet = resolve_loc(owned.sheet)?;
17831 Ok(SharedRef::Range(SharedRangeRef {
17832 sheet,
17833 start_row: owned.start_row,
17834 start_col: owned.start_col,
17835 end_row: owned.end_row,
17836 end_col: owned.end_col,
17837 }))
17838 }
17839 }
17840 }
17841}
17842
17843impl<R> crate::traits::ReferenceResolver for Engine<R>
17846where
17847 R: EvaluationContext,
17848{
17849 fn resolve_cell_reference(
17850 &self,
17851 sheet: Option<&str>,
17852 row: u32,
17853 col: u32,
17854 ) -> Result<LiteralValue, ExcelError> {
17855 let Some(sheet_name) = sheet else {
17866 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
17867 "Unqualified cell reference resolved without sheet context".to_string(),
17868 ));
17869 };
17870 if let Some(v) = self.get_cell_value(sheet_name, row, col) {
17873 Ok(v)
17874 } else {
17875 Ok(LiteralValue::Number(0.0))
17877 }
17878 }
17879}
17880
17881impl<R> crate::traits::RangeResolver for Engine<R>
17882where
17883 R: EvaluationContext,
17884{
17885 fn resolve_range_reference(
17886 &self,
17887 sheet: Option<&str>,
17888 sr: Option<u32>,
17889 sc: Option<u32>,
17890 er: Option<u32>,
17891 ec: Option<u32>,
17892 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
17893 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
17896 }
17897}
17898
17899impl<R> crate::traits::NamedRangeResolver for Engine<R>
17900where
17901 R: EvaluationContext,
17902{
17903 fn resolve_named_range_reference(
17904 &self,
17905 name: &str,
17906 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
17907 self.resolver.resolve_named_range_reference(name)
17908 }
17909}
17910
17911impl<R> crate::traits::TableResolver for Engine<R>
17912where
17913 R: EvaluationContext,
17914{
17915 fn resolve_table_reference(
17916 &self,
17917 tref: &formualizer_parse::parser::TableReference,
17918 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17919 self.resolver.resolve_table_reference(tref)
17920 }
17921}
17922
17923impl<R> crate::traits::SourceResolver for Engine<R>
17924where
17925 R: EvaluationContext,
17926{
17927 fn source_scalar_version(&self, name: &str) -> Option<u64> {
17928 self.resolver.source_scalar_version(name)
17929 }
17930
17931 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
17932 self.resolver.resolve_source_scalar(name)
17933 }
17934
17935 fn source_table_version(&self, name: &str) -> Option<u64> {
17936 self.resolver.source_table_version(name)
17937 }
17938
17939 fn resolve_source_table(
17940 &self,
17941 name: &str,
17942 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
17943 self.resolver.resolve_source_table(name)
17944 }
17945}
17946
17947impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
17949
17950impl<R> crate::traits::FunctionProvider for Engine<R>
17952where
17953 R: EvaluationContext,
17954{
17955 fn planning_semantic_revision(&self) -> Option<u64> {
17956 self.resolver.planning_semantic_revision()
17957 }
17958
17959 fn get_function(
17960 &self,
17961 prefix: &str,
17962 name: &str,
17963 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17964 self.resolver.get_function(prefix, name)
17965 }
17966
17967 fn get_function_for_planning(
17968 &self,
17969 prefix: &str,
17970 name: &str,
17971 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
17972 self.resolver.get_function_for_planning(prefix, name)
17973 }
17974}
17975
17976impl<R> Engine<R>
17977where
17978 R: EvaluationContext,
17979{
17980 pub(crate) fn semantic_used_rows_for_columns(
17988 &self,
17989 sheet: &str,
17990 start_col: u32,
17991 end_col: u32,
17992 ) -> Option<(u32, u32)> {
17993 let arrow_bounds = self
17994 .sheet_store()
17995 .sheet(sheet)
17996 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
17997 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
17998 Self::union_used_bounds(arrow_bounds, formula_bounds)
17999 }
18000
18001 pub(crate) fn semantic_used_cols_for_rows(
18002 &self,
18003 sheet: &str,
18004 start_row: u32,
18005 end_row: u32,
18006 ) -> Option<(u32, u32)> {
18007 let arrow_bounds = self
18008 .sheet_store()
18009 .sheet(sheet)
18010 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
18011 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
18012 Self::union_used_bounds(arrow_bounds, formula_bounds)
18013 }
18014}
18015
18016impl<R> crate::traits::EvaluationContext for Engine<R>
18018where
18019 R: EvaluationContext,
18020{
18021 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
18022 &self.clock
18023 }
18024
18025 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
18026 self.thread_pool.as_ref()
18027 }
18028
18029 fn cancellation_token(&self) -> Option<crate::engine::CancelToken> {
18030 self.active_cancel_flag.clone()
18031 }
18032
18033 fn chunk_hint(&self) -> Option<usize> {
18034 let hint =
18036 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
18037 Some(hint.clamp(1024, 1 << 20)) }
18039
18040 fn volatile_level(&self) -> crate::traits::VolatileLevel {
18041 self.config.volatile_level
18042 }
18043
18044 fn workbook_seed(&self) -> u64 {
18045 self.config.workbook_seed
18046 }
18047
18048 fn recalc_epoch(&self) -> u64 {
18049 self.recalc_epoch
18050 }
18051
18052 fn workbook_sheet_count(&self) -> Option<usize> {
18053 Some(self.graph.sheet_reg().active_len())
18054 }
18055
18056 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
18057 self.graph.sheet_reg().active_position(sheet)
18058 }
18059
18060 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
18061 self.sheet_index_by_name(current_sheet)
18062 }
18063
18064 fn inspect_reference(
18065 &self,
18066 reference: &ReferenceType,
18067 current_sheet: &str,
18068 ) -> Result<Option<ReferenceInfo>, ExcelError> {
18069 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
18070 let sheet_id = self
18071 .graph
18072 .sheet_id(sheet_name)
18073 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18074 let sheet_index = self
18075 .graph
18076 .sheet_reg()
18077 .active_position_by_id(sheet_id)
18078 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18079 Ok((sheet_id, sheet_index))
18080 };
18081
18082 let cell_info =
18083 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
18084 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
18085 let row0 = row
18086 .checked_sub(1)
18087 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18088 let col0 = col
18089 .checked_sub(1)
18090 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18091 Ok(ReferenceInfo {
18092 first_sheet_index: Some(sheet_index),
18093 sheet_count: Some(1),
18094 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
18095 })
18096 };
18097
18098 let range_info = |sheet_name: &str,
18099 start_row: Option<u32>,
18100 start_col: Option<u32>|
18101 -> Result<ReferenceInfo, ExcelError> {
18102 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
18103 let row = start_row.unwrap_or(1);
18104 let col = start_col.unwrap_or(1);
18105 let row0 = row
18106 .checked_sub(1)
18107 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18108 let col0 = col
18109 .checked_sub(1)
18110 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18111 Ok(ReferenceInfo {
18112 first_sheet_index: Some(sheet_index),
18113 sheet_count: Some(1),
18114 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
18115 })
18116 };
18117
18118 let info = match reference {
18119 ReferenceType::Cell {
18120 sheet, row, col, ..
18121 } => {
18122 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
18123 cell_info(sheet_name, *row, *col)?
18124 }
18125 ReferenceType::Range {
18126 sheet,
18127 start_row,
18128 start_col,
18129 ..
18130 } => {
18131 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
18132 range_info(sheet_name, *start_row, *start_col)?
18133 }
18134 ReferenceType::Cell3D {
18135 sheet_first,
18136 sheet_last,
18137 row,
18138 col,
18139 ..
18140 } => {
18141 let first = cell_info(sheet_first, *row, *col)?;
18142 ReferenceInfo {
18143 first_sheet_index: first.first_sheet_index,
18144 sheet_count: self
18145 .graph
18146 .sheet_reg()
18147 .active_span_len(sheet_first, sheet_last),
18148 first_cell: first.first_cell,
18149 }
18150 }
18151 ReferenceType::Range3D {
18152 sheet_first,
18153 sheet_last,
18154 start_row,
18155 start_col,
18156 ..
18157 } => {
18158 let first = range_info(sheet_first, *start_row, *start_col)?;
18159 ReferenceInfo {
18160 first_sheet_index: first.first_sheet_index,
18161 sheet_count: self
18162 .graph
18163 .sheet_reg()
18164 .active_span_len(sheet_first, sheet_last),
18165 first_cell: first.first_cell,
18166 }
18167 }
18168 ReferenceType::NamedRange(name) => {
18169 let current_id = self
18170 .graph
18171 .sheet_id(current_sheet)
18172 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18173 let named = self
18174 .graph
18175 .resolve_name_entry(name, current_id)
18176 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18177 match &named.definition {
18178 NamedDefinition::Cell(cell) => ReferenceInfo {
18179 first_sheet_index: self
18180 .graph
18181 .sheet_reg()
18182 .active_position_by_id(cell.sheet_id),
18183 sheet_count: Some(1),
18184 first_cell: Some(*cell),
18185 },
18186 NamedDefinition::Range(range) => ReferenceInfo {
18187 first_sheet_index: self
18188 .graph
18189 .sheet_reg()
18190 .active_position_by_id(range.start.sheet_id),
18191 sheet_count: Some(1),
18192 first_cell: Some(range.start),
18193 },
18194 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
18195 ReferenceInfo {
18196 first_sheet_index: None,
18197 sheet_count: None,
18198 first_cell: None,
18199 }
18200 }
18201 }
18202 }
18203 ReferenceType::Table(tref) => {
18204 let table = self
18205 .graph
18206 .resolve_table_entry(&tref.name)
18207 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
18208 ReferenceInfo {
18209 first_sheet_index: self
18210 .graph
18211 .sheet_reg()
18212 .active_position_by_id(table.range.start.sheet_id),
18213 sheet_count: Some(1),
18214 first_cell: Some(table.range.start),
18215 }
18216 }
18217 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
18218 };
18219
18220 Ok(Some(info))
18221 }
18222
18223 fn resolve_spill_reference(
18224 &self,
18225 anchor: &ReferenceType,
18226 current_sheet: &str,
18227 ) -> Result<ReferenceType, ExcelError> {
18228 let no_spill = || {
18229 ExcelError::new(ExcelErrorKind::Ref)
18230 .with_message("Spill reference anchor has no current spill")
18231 };
18232 let mut qualify = true;
18235 let anchor_cell = match anchor {
18236 ReferenceType::Cell {
18237 sheet, row, col, ..
18238 } => {
18239 qualify = sheet.is_some();
18240 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
18241 let sheet_id = self.graph.sheet_id(sheet_name).ok_or_else(no_spill)?;
18242 if *row == 0 || *col == 0 {
18243 return Err(no_spill());
18244 }
18245 CellRef::new(sheet_id, Coord::from_excel(*row, *col, true, true))
18246 }
18247 ReferenceType::NamedRange(name) => {
18248 let current_id = self.graph.sheet_id(current_sheet).ok_or_else(no_spill)?;
18249 let named = self
18250 .graph
18251 .resolve_name_entry(name, current_id)
18252 .ok_or_else(no_spill)?;
18253 match &named.definition {
18254 NamedDefinition::Cell(cell) => *cell,
18255 NamedDefinition::Range(range)
18256 if range.start.sheet_id == range.end.sheet_id
18257 && range.start.coord.row() == range.end.coord.row()
18258 && range.start.coord.col() == range.end.coord.col() =>
18259 {
18260 range.start
18261 }
18262 _ => {
18263 return Err(ExcelError::new(ExcelErrorKind::Ref)
18264 .with_message("Spill reference name must refer to a single cell"));
18265 }
18266 }
18267 }
18268 _ => {
18269 return Err(ExcelError::new(ExcelErrorKind::Ref)
18270 .with_message("Spill reference operand must be a single cell"));
18271 }
18272 };
18273 let vertex = self
18274 .graph
18275 .get_vertex_id_for_address(&anchor_cell)
18276 .ok_or_else(no_spill)?;
18277 if self.graph.fixed_single_arrays.contains(&vertex)
18280 || self.graph.fixed_array_shapes.contains_key(&vertex)
18281 {
18282 return Err(ExcelError::new(ExcelErrorKind::Ref)
18283 .with_message("Spill reference to a fixed array formula"));
18284 }
18285 let (first, last) = match self.graph.spill_extent_for_anchor(vertex) {
18286 Some(extent) => extent,
18287 None if !self.graph.spill_registry_has_anchor(vertex)
18291 && self.graph.is_current_declared_dynamic_anchor(vertex)
18292 && self.declared_anchor_holds_scalar(anchor_cell) =>
18293 {
18294 (anchor_cell, anchor_cell)
18295 }
18296 None => return Err(no_spill()),
18297 };
18298 let sheet_name = qualify.then(|| self.graph.sheet_name(first.sheet_id).to_string());
18299 let (sr, sc) = (first.coord.row() + 1, first.coord.col() + 1);
18300 let (er, ec) = (last.coord.row() + 1, last.coord.col() + 1);
18301 Ok(if sr == er && sc == ec {
18303 ReferenceType::cell_with_abs(sheet_name, sr, sc, true, true)
18304 } else {
18305 ReferenceType::Range {
18306 sheet: sheet_name,
18307 start_row: Some(sr),
18308 start_col: Some(sc),
18309 end_row: Some(er),
18310 end_col: Some(ec),
18311 start_row_abs: true,
18312 start_col_abs: true,
18313 end_row_abs: true,
18314 end_col_abs: true,
18315 }
18316 })
18317 }
18318
18319 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
18320 let sheet_name = self.graph.sheet_name(cell.sheet_id);
18321 if sheet_name.is_empty() {
18322 return Err(ExcelError::new(ExcelErrorKind::Ref));
18323 }
18324 let row = cell.coord.row() + 1;
18325 let col = cell.coord.col() + 1;
18326
18327 if let Some(entries) = self.staged_formulas.get(sheet_name)
18328 && let Some(text) = entries.get(row, col)
18329 {
18330 return Ok(Some(if text.starts_with('=') {
18331 text.to_owned()
18332 } else {
18333 format!("={text}")
18334 }));
18335 }
18336
18337 let Some((Some(ast), _)) = self.get_cell(sheet_name, row, col) else {
18338 return Ok(None);
18339 };
18340 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
18341 }
18342
18343 fn used_rows_for_columns(
18344 &self,
18345 sheet: &str,
18346 start_col: u32,
18347 end_col: u32,
18348 ) -> Option<(u32, u32)> {
18349 let sheet_id = self.graph.sheet_id(sheet)?;
18351 let snap = self.data_snapshot_id();
18352 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
18353 guard
18354 .as_ref()
18355 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
18356 }) {
18357 return cached;
18358 }
18359
18360 let arrow_bounds = self
18361 .sheet_store()
18362 .sheet(sheet)
18363 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
18364 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
18365 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
18366 Some(bounds)
18367 } else {
18368 let sc0 = start_col.saturating_sub(1);
18369 let ec0 = end_col.saturating_sub(1);
18370 self.graph
18371 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
18372 .map(|(a0, b0)| (a0 + 1, b0 + 1))
18373 };
18374
18375 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
18376 guard
18377 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
18378 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
18379 }
18380
18381 computed
18382 }
18383
18384 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
18385 let sheet_id = self.graph.sheet_id(sheet)?;
18387 let snap = self.data_snapshot_id();
18388 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
18389 guard
18390 .as_ref()
18391 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
18392 }) {
18393 return cached;
18394 }
18395
18396 let arrow_bounds = self
18397 .sheet_store()
18398 .sheet(sheet)
18399 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
18400 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
18401 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
18402 Some(bounds)
18403 } else {
18404 let sr0 = start_row.saturating_sub(1);
18405 let er0 = end_row.saturating_sub(1);
18406 self.graph
18407 .used_col_bounds_for_rows(sheet_id, sr0, er0)
18408 .map(|(a0, b0)| (a0 + 1, b0 + 1))
18409 };
18410
18411 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
18412 guard
18413 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
18414 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
18415 }
18416
18417 computed
18418 }
18419
18420 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
18421 let _ = self.graph.sheet_id(sheet)?;
18422 Some((1_048_576, 16_384)) }
18426
18427 fn data_snapshot_id(&self) -> u64 {
18428 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
18429 }
18430
18431 fn backend_caps(&self) -> crate::traits::BackendCaps {
18432 crate::traits::BackendCaps {
18433 streaming: true,
18434 used_region: true,
18435 write: false,
18436 tables: false,
18437 async_stream: false,
18438 }
18439 }
18440
18441 fn build_lookup_index(
18442 &self,
18443 view: &RangeView<'_>,
18444 axis: LookupAxis,
18445 ) -> Option<Arc<LookupIndex>> {
18446 self.build_lookup_index_impl(view, axis)
18447 }
18448
18449 fn date_system(&self) -> crate::engine::DateSystem {
18452 self.config.date_system
18453 }
18454 fn resolve_range_view<'c>(
18456 &'c self,
18457 reference: &ReferenceType,
18458 current_sheet: &str,
18459 ) -> Result<RangeView<'c>, ExcelError> {
18460 match reference {
18461 ReferenceType::External(ext) => {
18462 let name = ext.raw.as_str();
18463 match ext.kind {
18464 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
18465 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
18466 return Err(ExcelError::new(ExcelErrorKind::Name)
18467 .with_message(format!("Undefined name: {name}")));
18468 };
18469 let version = source
18470 .version
18471 .or_else(|| self.resolver.source_scalar_version(name));
18472 let v = self.resolve_source_scalar_cached(name, version)?;
18473 Ok(RangeView::from_owned_rows(
18474 vec![vec![v]],
18475 self.config.date_system,
18476 ))
18477 }
18478 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
18479 let Some(source) = self.graph.resolve_source_table_entry(name) else {
18480 let kind =
18484 if crate::engine::refs::unbound_external_range_defers(&ext.kind) {
18485 ExcelErrorKind::Ref
18486 } else {
18487 ExcelErrorKind::Name
18488 };
18489 return Err(ExcelError::new(kind)
18490 .with_message(format!("Undefined table: {name}")));
18491 };
18492 let version = source
18493 .version
18494 .or_else(|| self.resolver.source_table_version(name));
18495 let table = self.resolve_source_table_cached(name, version)?;
18496 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
18497 self.source_table_to_range_view(table.as_ref(), &spec)
18498 }
18499 }
18500 }
18501 ReferenceType::Range { .. } => {
18502 let shared = self.resolve_shared_ref(reference, current_sheet)?;
18503 let formualizer_common::SheetRef::Range(range) = shared else {
18504 return Err(ExcelError::new(ExcelErrorKind::Ref));
18505 };
18506 let sheet_id = match range.sheet {
18509 formualizer_common::SheetLocator::Id(id) => id,
18510 formualizer_common::SheetLocator::Current
18511 | formualizer_common::SheetLocator::Name(_) => {
18512 return Err(ExcelError::new(ExcelErrorKind::Ref));
18513 }
18514 };
18515 let sheet_name = self.graph.sheet_name(sheet_id);
18516
18517 let bounded_range = if range.start_row.is_some()
18518 && range.start_col.is_some()
18519 && range.end_row.is_some()
18520 && range.end_col.is_some()
18521 {
18522 Some(RangeRef::try_from_shared(range.as_ref())?)
18523 } else {
18524 None
18525 };
18526
18527 let sr = bounded_range
18528 .as_ref()
18529 .map(|r| r.start.coord.row() + 1)
18530 .or_else(|| range.start_row.map(|b| b.index + 1));
18531 let sc = bounded_range
18532 .as_ref()
18533 .map(|r| r.start.coord.col() + 1)
18534 .or_else(|| range.start_col.map(|b| b.index + 1));
18535 let er = bounded_range
18536 .as_ref()
18537 .map(|r| r.end.coord.row() + 1)
18538 .or_else(|| range.end_row.map(|b| b.index + 1));
18539 let ec = bounded_range
18540 .as_ref()
18541 .map(|r| r.end.coord.col() + 1)
18542 .or_else(|| range.end_col.map(|b| b.index + 1));
18543
18544 let extent = resolve_used_extent_with_fallback(
18545 OpenRangeBounds {
18546 start_row: sr,
18547 start_column: sc,
18548 end_row: er,
18549 end_column: ec,
18550 },
18551 ExtentPolicy::EvaluationCompat {
18552 fallback_row: None,
18553 fallback_column: None,
18554 },
18555 || {
18556 self.sheet_bounds(sheet_name)
18557 .map(|_| self.config.max_open_ended_rows)
18558 },
18559 || {
18560 self.sheet_bounds(sheet_name)
18561 .map(|_| self.config.max_open_ended_cols)
18562 },
18563 |first, last| self.used_rows_for_columns(sheet_name, first, last),
18564 |first, last| self.used_cols_for_rows(sheet_name, first, last),
18565 );
18566 let (sr, sc, er, ec) = extent
18567 .map(|extent| {
18568 (
18569 extent.start_row,
18570 extent.start_column,
18571 extent.end_row,
18572 extent.end_column,
18573 )
18574 })
18575 .unwrap_or((1, 1, 0, 0));
18576
18577 if self.force_materialize_range_views {
18578 if er < sr || ec < sc {
18579 return Ok(RangeView::from_owned_rows(
18580 Vec::new(),
18581 self.config.date_system,
18582 ));
18583 }
18584 let h = (er - sr + 1) as u64;
18585 let w = (ec - sc + 1) as u64;
18586 let cell_count = h.saturating_mul(w);
18587 if cell_count <= self.config.spill.max_spill_cells as u64 {
18588 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
18589 for r in sr..=er {
18590 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
18591 for c in sc..=ec {
18592 rowv.push(
18593 self.get_cell_value(sheet_name, r, c)
18594 .unwrap_or(LiteralValue::Empty),
18595 );
18596 }
18597 rows.push(rowv);
18598 }
18599 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
18600 }
18601 }
18602
18603 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
18604 return Ok(RangeView::from_owned_rows(
18605 Vec::new(),
18606 self.config.date_system,
18607 ));
18608 };
18609
18610 let rv = if er < sr || ec < sc {
18611 asheet.range_view(1, 1, 0, 0)
18612 } else {
18613 let sr0 = sr.saturating_sub(1) as usize;
18614 let sc0 = sc.saturating_sub(1) as usize;
18615 let er0 = er.saturating_sub(1) as usize;
18616 let ec0 = ec.saturating_sub(1) as usize;
18617 asheet.range_view(sr0, sc0, er0, ec0)
18618 };
18619
18620 Ok(rv)
18621 }
18622 ReferenceType::Cell { .. } => {
18623 let shared = self.resolve_shared_ref(reference, current_sheet)?;
18624 let formualizer_common::SheetRef::Cell(cell) = shared else {
18625 return Err(ExcelError::new(ExcelErrorKind::Ref));
18626 };
18627 let addr = CellRef::try_from_shared(cell)?;
18628 let sheet_id = addr.sheet_id;
18629 let sheet_name = self.graph.sheet_name(sheet_id);
18630 let row = addr.coord.row() + 1;
18631 let col = addr.coord.col() + 1;
18632
18633 if self.force_materialize_range_views {
18634 let v = self
18635 .get_cell_value(sheet_name, row, col)
18636 .unwrap_or(LiteralValue::Empty);
18637 return Ok(RangeView::from_owned_rows(
18638 vec![vec![v]],
18639 self.config.date_system,
18640 ));
18641 }
18642
18643 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
18644 let r0 = row.saturating_sub(1) as usize;
18645 let c0 = col.saturating_sub(1) as usize;
18646 let rv = asheet.range_view(r0, c0, r0, c0);
18647 Ok(rv)
18648 } else {
18649 let v = self
18650 .get_cell_value(sheet_name, row, col)
18651 .unwrap_or(LiteralValue::Empty);
18652 Ok(RangeView::from_owned_rows(
18653 vec![vec![v]],
18654 self.config.date_system,
18655 ))
18656 }
18657 }
18658 ReferenceType::NamedRange(name) => {
18659 if let Some(current_id) = self.graph.sheet_id(current_sheet)
18660 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
18661 {
18662 match &named.definition {
18663 NamedDefinition::Cell(cell_ref) => {
18664 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
18665 if self.force_materialize_range_views {
18666 let v = self
18667 .get_cell_value(
18668 sheet_name,
18669 cell_ref.coord.row() + 1,
18670 cell_ref.coord.col() + 1,
18671 )
18672 .unwrap_or(LiteralValue::Empty);
18673 return Ok(RangeView::from_owned_rows(
18674 vec![vec![v]],
18675 self.config.date_system,
18676 ));
18677 } else {
18678 let asheet = self
18679 .sheet_store()
18680 .sheet(sheet_name)
18681 .expect("Arrow sheet missing for named cell");
18682 let r0 = cell_ref.coord.row() as usize;
18683 let c0 = cell_ref.coord.col() as usize;
18684 let rv = asheet.range_view(r0, c0, r0, c0);
18685 return Ok(rv);
18686 }
18687 }
18688 NamedDefinition::Range(range_ref) => {
18689 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
18690 let sr = range_ref.start.coord.row() + 1;
18691 let sc = range_ref.start.coord.col() + 1;
18692 let er = range_ref.end.coord.row() + 1;
18693 let ec = range_ref.end.coord.col() + 1;
18694 if self.force_materialize_range_views {
18695 let h = (er.saturating_sub(sr) + 1) as u64;
18696 let w = (ec.saturating_sub(sc) + 1) as u64;
18697 let cell_count = h.saturating_mul(w);
18698 if cell_count <= self.config.spill.max_spill_cells as u64 {
18699 let mut rows: Vec<Vec<LiteralValue>> =
18700 Vec::with_capacity(h as usize);
18701 for r in sr..=er {
18702 let mut rowv: Vec<LiteralValue> =
18703 Vec::with_capacity(w as usize);
18704 for c in sc..=ec {
18705 rowv.push(
18706 self.get_cell_value(sheet_name, r, c)
18707 .unwrap_or(LiteralValue::Empty),
18708 );
18709 }
18710 rows.push(rowv);
18711 }
18712 return Ok(RangeView::from_owned_rows(
18713 rows,
18714 self.config.date_system,
18715 ));
18716 }
18717 }
18718 let asheet = self
18719 .sheet_store()
18720 .sheet(sheet_name)
18721 .expect("Arrow sheet missing for named range");
18722 let sr0 = range_ref.start.coord.row() as usize;
18723 let sc0 = range_ref.start.coord.col() as usize;
18724 let er0 = range_ref.end.coord.row() as usize;
18725 let ec0 = range_ref.end.coord.col() as usize;
18726 let rv = asheet.range_view(sr0, sc0, er0, ec0);
18727 return Ok(rv);
18728 }
18729 NamedDefinition::Literal(v) => {
18730 return Ok(RangeView::from_owned_rows(
18731 vec![vec![v.clone()]],
18732 self.config.date_system,
18733 ));
18734 }
18735 NamedDefinition::Formula { .. } => {
18736 if let Some(value) = self.graph.get_value(named.vertex) {
18737 return Ok(RangeView::from_owned_rows(
18738 vec![vec![value]],
18739 self.config.date_system,
18740 ));
18741 }
18742 }
18743 }
18744 }
18745
18746 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
18747 let version = source
18748 .version
18749 .or_else(|| self.resolver.source_scalar_version(name));
18750 let v = self.resolve_source_scalar_cached(name, version)?;
18751 return Ok(RangeView::from_owned_rows(
18752 vec![vec![v]],
18753 self.config.date_system,
18754 ));
18755 }
18756
18757 let data = self.resolver.resolve_named_range_reference(name)?;
18758 Ok(RangeView::from_owned_rows(data, self.config.date_system))
18759 }
18760 ReferenceType::Table(tref) => {
18761 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
18762 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
18763 let asheet = self
18764 .sheet_store()
18765 .sheet(sheet_name)
18766 .expect("Arrow sheet missing for table reference");
18767
18768 let sr0 = table.range.start.coord.row() as usize;
18769 let sc0 = table.range.start.coord.col() as usize;
18770 let er0 = table.range.end.coord.row() as usize;
18771 let ec0 = table.range.end.coord.col() as usize;
18772
18773 let has_totals = table.totals_row;
18774 let has_headers = table.header_row;
18775 let data_sr = if has_headers {
18776 sr0.saturating_add(1)
18777 } else {
18778 sr0
18779 };
18780 let data_er = if has_totals {
18781 er0.saturating_sub(1)
18782 } else {
18783 er0
18784 };
18785
18786 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
18787 if sr > er || sc > ec {
18788 asheet.range_view(1, 1, 0, 0)
18789 } else {
18790 asheet.range_view(sr, sc, er, ec)
18791 }
18792 };
18793
18794 let av = match &tref.specifier {
18795 None => {
18796 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18797 "Table reference without specifier is unsupported".to_string(),
18798 ));
18799 }
18800 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
18801 let Some(idx) = table.col_index(col) else {
18802 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18803 "Column refers to unknown table column".to_string(),
18804 ));
18805 };
18806 let c0 = sc0 + idx;
18807 select(data_sr, c0, data_er, c0)
18808 }
18809 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
18810 start,
18811 end,
18812 )) => {
18813 let Some(si) = table.col_index(start) else {
18814 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18815 "Column range refers to unknown column(s)".to_string(),
18816 ));
18817 };
18818 let Some(ei) = table.col_index(end) else {
18819 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
18820 "Column range refers to unknown column(s)".to_string(),
18821 ));
18822 };
18823 let (mut a, mut b) = (si, ei);
18824 if a > b {
18825 std::mem::swap(&mut a, &mut b);
18826 }
18827 let c_start = sc0 + a;
18828 let c_end = sc0 + b;
18829 select(data_sr, c_start, data_er, c_end)
18830 }
18831 Some(formualizer_parse::parser::TableSpecifier::All)
18832 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18833 formualizer_parse::parser::SpecialItem::All,
18834 )) => select(sr0, sc0, er0, ec0),
18835 Some(formualizer_parse::parser::TableSpecifier::Data)
18836 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18837 formualizer_parse::parser::SpecialItem::Data,
18838 )) => select(data_sr, sc0, data_er, ec0),
18839 Some(formualizer_parse::parser::TableSpecifier::Headers)
18840 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18841 formualizer_parse::parser::SpecialItem::Headers,
18842 )) => {
18843 if !has_headers {
18844 asheet.range_view(1, 1, 0, 0)
18845 } else {
18846 select(sr0, sc0, sr0, ec0)
18847 }
18848 }
18849 Some(formualizer_parse::parser::TableSpecifier::Totals)
18850 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18851 formualizer_parse::parser::SpecialItem::Totals,
18852 )) => {
18853 if !has_totals {
18854 asheet.range_view(1, 1, 0, 0)
18855 } else {
18856 select(er0, sc0, er0, ec0)
18857 }
18858 }
18859 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
18860 formualizer_parse::parser::SpecialItem::ThisRow,
18861 )) => {
18862 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18863 "@ (This Row) requires table-aware context; not yet supported"
18864 .to_string(),
18865 ));
18866 }
18867 Some(formualizer_parse::parser::TableSpecifier::Row(_))
18868 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
18869 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
18870 "Complex structured references not yet supported".to_string(),
18871 ));
18872 }
18873 };
18874
18875 return Ok(av);
18876 }
18877
18878 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
18879 let version = source
18880 .version
18881 .or_else(|| self.resolver.source_table_version(&tref.name));
18882 let table = self.resolve_source_table_cached(&tref.name, version)?;
18883 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
18884 }
18885
18886 let boxed = self
18891 .resolve_range_like(&ReferenceType::Table(tref.clone()))
18892 .map_err(|e| {
18893 if e.kind == ExcelErrorKind::NImpl {
18894 ExcelError::new(ExcelErrorKind::Name)
18895 .with_message(format!("Unknown table: {}", tref.name))
18896 } else {
18897 e
18898 }
18899 })?;
18900 let owned = boxed.materialise().into_owned();
18901 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
18902 }
18903 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
18904 Err(ExcelError::new(ExcelErrorKind::NImpl)
18905 .with_message("3D references are not yet supported".to_string()))
18906 }
18907 }
18908 }
18909
18910 fn resolve_cell_format(
18911 &self,
18912 sheet: Option<&str>,
18913 row: u32,
18914 col: u32,
18915 current_sheet: &str,
18916 ) -> Option<crate::format::FormatId> {
18917 self.effective_format_id(sheet.unwrap_or(current_sheet), row, col)
18918 }
18919
18920 fn format_class(
18921 &self,
18922 format: crate::format::FormatId,
18923 ) -> Option<formualizer_common::numfmt::FormatClass> {
18924 self.format_registry.class(format).cloned()
18925 }
18926
18927 fn record_cell_derived_format(
18928 &self,
18929 sheet: &str,
18930 row: u32,
18931 col: u32,
18932 format: Option<crate::format::FormatId>,
18933 ) {
18934 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
18935 let cell = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
18936 self.record_derived_format_at(cell, format);
18937 }
18938 }
18939
18940 fn resolve_cell_reference_value(
18941 &self,
18942 sheet: Option<&str>,
18943 row: u32,
18944 col: u32,
18945 current_sheet: &str,
18946 ) -> Result<LiteralValue, ExcelError> {
18947 let sheet_name = sheet.unwrap_or(current_sheet);
18948 if self.graph.sheet_id(sheet_name).is_none() {
18949 return Err(ExcelError::new(ExcelErrorKind::Ref));
18950 }
18951 Ok(self
18952 .get_cell_value(sheet_name, row, col)
18953 .unwrap_or(LiteralValue::Empty))
18954 }
18955
18956 fn resolve_cell_reference_value_formatted(
18957 &self,
18958 sheet: Option<&str>,
18959 row: u32,
18960 col: u32,
18961 current_sheet: &str,
18962 ) -> Result<(LiteralValue, Option<crate::format::FormatId>), ExcelError> {
18963 let sheet_name = sheet.unwrap_or(current_sheet);
18966 let Some(sheet_id) = self.graph.sheet_id(sheet_name) else {
18967 return Err(ExcelError::new(ExcelErrorKind::Ref));
18968 };
18969 let asheet = self.arrow_sheets.sheet(sheet_name);
18970 Ok(self.read_cell_formatted_in(sheet_id, asheet, row, col))
18971 }
18972
18973 fn build_criteria_mask(
18974 &self,
18975 view: &RangeView<'_>,
18976 col_in_view: usize,
18977 pred: &crate::args::CriteriaPredicate,
18978 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18979 #[cfg(any(test, feature = "test-support"))]
18980 criteria_mask_test_hooks::note_mask(view.dims().0);
18981 if view.dims().1 == 0 {
18982 return None;
18983 }
18984 let sheet_rows = view.sheet().nrows as usize;
18987 if sheet_rows == 0 || view.start_row() >= sheet_rows {
18988 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
18989 }
18990 compute_criteria_mask(view, col_in_view, pred)
18991 }
18992
18993 fn build_row_visibility_mask(
18994 &self,
18995 view: &RangeView<'_>,
18996 mode: VisibilityMaskMode,
18997 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
18998 self.build_row_visibility_mask_for_view(view, mode)
18999 }
19000
19001 fn nested_subtotal_cells(
19002 &self,
19003 view: &RangeView<'_>,
19004 include_aggregate: bool,
19005 ) -> Option<Vec<(usize, usize, usize)>> {
19006 self.nested_subtotal_cells_for_view(view, include_aggregate)
19007 }
19008}
19009
19010impl<R> Engine<R>
19011where
19012 R: EvaluationContext,
19013{
19014 fn clear_spill_projection_and_mirror(
19015 &mut self,
19016 anchor_vertex: VertexId,
19017 delta: Option<&mut DeltaCollector>,
19018 ) {
19019 let spill_cells = self.graph.spill_cells_to_clear(anchor_vertex);
19020 if spill_cells.is_empty() {
19021 return;
19022 }
19023
19024 if let Some(delta) = delta
19025 && delta.mode != DeltaMode::Off
19026 {
19027 let empty = LiteralValue::Empty;
19028 for cell in spill_cells.iter() {
19029 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19030 let old = self
19031 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19032 .unwrap_or(LiteralValue::Empty);
19033 if old != empty {
19034 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19035 }
19036 }
19037 }
19038
19039 self.graph.clear_spill_region(anchor_vertex);
19040 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
19041 self.record_structural_change(scope);
19042 }
19043
19044 if self.config.arrow_storage_enabled
19045 && self.config.delta_overlay_enabled
19046 && self.config.write_formula_overlay_enabled
19047 {
19048 let empty = LiteralValue::Empty;
19049 for cell in spill_cells.iter() {
19050 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19051 self.mirror_value_to_computed_overlay(
19052 &sheet_name,
19053 cell.coord.row() + 1,
19054 cell.coord.col() + 1,
19055 &empty,
19056 );
19057 }
19058 }
19059 }
19060
19061 fn apply_cycle_outcome(
19075 &mut self,
19076 cycle: &[VertexId],
19077 mut delta: Option<&mut DeltaCollector>,
19078 dirty_filter: Option<&FxHashSet<VertexId>>,
19079 ) -> usize {
19080 let circ_error = LiteralValue::Error(
19081 ExcelError::new(ExcelErrorKind::Circ)
19082 .with_message("Circular dependency detected".to_string()),
19083 );
19084 let mut stamped = 0usize;
19085 for &vertex_id in cycle {
19086 if let Some(filter) = dirty_filter
19087 && !filter.contains(&vertex_id)
19088 {
19089 continue;
19090 }
19091 self.stamp_cycle_error(vertex_id, &circ_error, delta.as_deref_mut());
19092 stamped += 1;
19093 }
19094 stamped
19095 }
19096
19097 fn stamp_cycle_error(
19112 &mut self,
19113 vertex_id: VertexId,
19114 circ_error: &LiteralValue,
19115 mut delta: Option<&mut DeltaCollector>,
19116 ) {
19117 if self.graph.spill_registry_has_anchor(vertex_id) {
19119 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
19120 }
19121 self.spill_mgr.release_owner(vertex_id);
19124
19125 if let Some(d) = delta
19127 && d.mode != DeltaMode::Off
19128 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
19129 {
19130 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19131 let old = self
19132 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19133 .unwrap_or(LiteralValue::Empty);
19134 if old != *circ_error {
19135 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19136 }
19137 }
19138
19139 self.graph.update_vertex_value_ref(vertex_id, circ_error);
19140 self.mirror_vertex_value_to_overlay(vertex_id, circ_error);
19141 }
19142
19143 fn handle_cycle_unit(
19156 &mut self,
19157 cycle: &[VertexId],
19158 mut delta: Option<&mut DeltaCollector>,
19159 dirty_filter: Option<&FxHashSet<VertexId>>,
19160 cancel_flag: Option<&AtomicBool>,
19161 ) -> Result<usize, ExcelError> {
19162 self.resource_checkpoint(cycle.len() as u64)?;
19163 match self.config.cycle.detection {
19164 CycleDetection::Static => {
19165 Ok(self.apply_cycle_outcome(cycle, delta.as_deref_mut(), dirty_filter))
19166 }
19167 CycleDetection::Runtime => {
19168 if let Some(filter) = dirty_filter
19169 && !cycle.iter().any(|v| filter.contains(v))
19170 {
19171 return Ok(0);
19172 }
19173 self.evaluate_scc_unit(cycle, delta, cancel_flag)
19177 }
19178 }
19179 }
19180
19181 pub(crate) fn evaluate_scc_unit(
19201 &mut self,
19202 cycle: &[VertexId],
19203 mut delta: Option<&mut DeltaCollector>,
19204 cancel_flag: Option<&AtomicBool>,
19205 ) -> Result<usize, ExcelError> {
19206 struct SccMember {
19207 vertex: VertexId,
19208 cell: Option<CellRef>,
19209 }
19210
19211 let task_start = crate::instant::FzInstant::now();
19212
19213 let mut cell_members: Vec<(VertexId, CellRef)> = Vec::new();
19218 let mut name_members: Vec<(VertexId, String)> = Vec::new();
19219 let mut other_members: Vec<VertexId> = Vec::new();
19220 for &v in cycle {
19221 match self.graph.get_vertex_kind(v) {
19222 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19223 match self.graph.get_cell_ref(v) {
19224 Some(cell) => cell_members.push((v, cell)),
19225 None => other_members.push(v),
19226 }
19227 }
19228 VertexKind::NamedScalar | VertexKind::NamedArray => {
19229 match self.graph.name_key_for_vertex(v) {
19230 Some(key) => name_members.push((v, key)),
19231 None => other_members.push(v),
19232 }
19233 }
19234 _ => other_members.push(v),
19235 }
19236 }
19237 cell_members.sort_unstable_by_key(|(_, c)| (c.sheet_id, c.coord.row(), c.coord.col()));
19238 name_members.sort_unstable_by(|(av, ak), (bv, bk)| ak.cmp(bk).then(av.cmp(bv)));
19239 other_members.sort_unstable();
19240
19241 let cell_refs: Vec<CellRef> = cell_members.iter().map(|(_, c)| *c).collect();
19242 let name_keys: Vec<String> = name_members.iter().map(|(_, k)| k.clone()).collect();
19243 let mut members: Vec<SccMember> = Vec::with_capacity(cycle.len());
19244 for (v, c) in &cell_members {
19245 members.push(SccMember {
19246 vertex: *v,
19247 cell: Some(*c),
19248 });
19249 }
19250 for (v, _) in &name_members {
19251 members.push(SccMember {
19252 vertex: *v,
19253 cell: None,
19254 });
19255 }
19256 for v in &other_members {
19257 members.push(SccMember {
19258 vertex: *v,
19259 cell: None,
19260 });
19261 }
19262 let n = members.len();
19263 let recordable = cell_refs.len() + name_keys.len();
19266
19267 let circ_error = LiteralValue::Error(
19268 ExcelError::new(ExcelErrorKind::Circ)
19269 .with_message("Circular dependency detected".to_string()),
19270 );
19271
19272 if !self.iterative_state_values.is_empty() {
19283 let restore: Vec<(VertexId, LiteralValue)> = members
19284 .iter()
19285 .filter_map(|m| {
19286 let cell = m.cell?;
19287 let persisted = self.iterative_state_values.get(&m.vertex)?;
19288 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19289 let overlay = self
19290 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19291 .unwrap_or(LiteralValue::Empty);
19292 if matches!(overlay, LiteralValue::Empty) {
19293 Some((m.vertex, persisted.clone()))
19294 } else {
19295 None
19296 }
19297 })
19298 .collect();
19299 for (vertex, value) in restore {
19300 self.mirror_vertex_value_to_overlay(vertex, &value);
19301 }
19302 }
19303
19304 let snapshot: Vec<LiteralValue> = members
19307 .iter()
19308 .map(|m| match m.cell {
19309 Some(cell) => {
19310 let sheet_name = self.graph.sheet_name(cell.sheet_id);
19311 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
19312 .unwrap_or(LiteralValue::Empty)
19313 }
19314 None => self
19315 .graph
19316 .get_value(m.vertex)
19317 .unwrap_or(LiteralValue::Empty),
19318 })
19319 .collect();
19320
19321 let mut excluded = vec![false; n];
19326 let mut last_value = snapshot.clone();
19327 let mut stamped = 0usize;
19328 for (i, m) in members.iter().enumerate() {
19329 match self.graph.get_vertex_kind(m.vertex) {
19330 VertexKind::FormulaArray => {
19331 self.stamp_cycle_error(m.vertex, &circ_error, delta.as_deref_mut());
19335 excluded[i] = true;
19336 last_value[i] = circ_error.clone();
19337 stamped += 1;
19338 }
19339 VertexKind::FormulaScalar | VertexKind::NamedScalar | VertexKind::NamedArray => {}
19340 _ => excluded[i] = true,
19341 }
19342 }
19343
19344 let collector = LiveEdgeCollector::new_with_names(&cell_refs, &name_keys);
19345
19346 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
19348 let mut pos: Vec<i64> = vec![-1; n];
19350 let mut changed = vec![false; n];
19352
19353 macro_rules! run_member {
19356 ($i:expr) => {{
19357 let i: usize = $i;
19358 let m = &members[i];
19359 if i < recordable {
19360 collector.set_current(i as u32);
19361 }
19362 let value = {
19363 let ctx = RecordingContext::new(&*self, &collector);
19364 match self.evaluate_vertex_recorded(m.vertex, &ctx, &collector) {
19365 Ok(v) => v,
19366 Err(e) => LiteralValue::Error(e),
19367 }
19368 };
19369 let is_cell_formula = m.cell.is_some();
19370 if is_cell_formula && matches!(value, LiteralValue::Array(_)) {
19371 self.stamp_cycle_error(m.vertex, &circ_error, None);
19376 excluded[i] = true;
19377 stamped += 1;
19378 changed[i] = last_value[i] != circ_error;
19379 last_value[i] = circ_error.clone();
19380 } else {
19381 self.graph.update_vertex_value_ref(m.vertex, &value);
19382 self.mirror_vertex_value_to_overlay(m.vertex, &value);
19383 #[cfg(debug_assertions)]
19387 if let Some(cell) = m.cell {
19388 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
19389 debug_assert!(
19390 self.read_delta_overlay_cell(
19391 &sheet_name,
19392 cell.coord.row() + 1,
19393 cell.coord.col() + 1
19394 )
19395 .is_none(),
19396 "user overlay must never shadow a formula SCC member ({sheet_name}!r{}c{})",
19397 cell.coord.row() + 1,
19398 cell.coord.col() + 1
19399 );
19400 }
19401 changed[i] = last_value[i] != value;
19402 last_value[i] = value;
19403 }
19404 }};
19405 }
19406
19407 let check_cancel = |flag: Option<&AtomicBool>| -> Result<(), ExcelError> {
19408 if let Some(flag) = flag
19409 && flag.load(Ordering::Relaxed)
19410 {
19411 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
19412 .with_message("Evaluation cancelled during SCC evaluation".to_string()));
19413 }
19414 Ok(())
19415 };
19416
19417 check_cancel(cancel_flag)?;
19419 let mut passes = 1usize;
19420 {
19421 let mut p = 0i64;
19422 for i in 0..n {
19423 if excluded[i] {
19424 continue;
19425 }
19426 run_member!(i);
19427 pos[i] = p;
19428 p += 1;
19429 }
19430 }
19431
19432 let policy = self.config.cycle.policy;
19448 let cap = n + 2;
19449 let mut witnessed_cycles = 0usize;
19450 let mut capped = false;
19451 let mut iterating = false;
19453 let mut converged = false;
19454 let mut exact_fixed_point = false;
19455 let mut prev_pass: Option<Vec<LiteralValue>> = None;
19459 let mut iter_max_delta = 0f64;
19462 let mut iter_nan_converged = 0usize;
19463 let mut settle_passes = 0usize;
19467 loop {
19468 let drained = collector.take_edges();
19471 for i in 0..n {
19472 if pos[i] >= 0 {
19473 out_edges[i].clear();
19474 }
19475 }
19476 for (from, to) in drained {
19477 debug_assert!(
19478 pos[from as usize] >= 0,
19479 "edge from a member that did not run"
19480 );
19481 out_edges[from as usize].push(to);
19482 }
19483 let mut edges: Vec<(u32, u32)> = Vec::new();
19484 for (i, outs) in out_edges.iter().enumerate() {
19485 if excluded[i] {
19486 continue;
19487 }
19488 for &t in outs {
19489 edges.push((i as u32, t));
19490 }
19491 }
19492 edges.sort_unstable();
19493 edges.dedup();
19494
19495 let analysis = analyze_live_graph(n, &edges);
19496
19497 if analysis.cycle_count > 0 {
19498 witnessed_cycles = witnessed_cycles.max(analysis.cycle_count);
19502 match policy {
19503 CyclePolicy::Error => {
19504 for i in 0..n {
19510 if analysis.in_cycle[i] && !excluded[i] {
19511 self.stamp_cycle_error(members[i].vertex, &circ_error, None);
19512 excluded[i] = true;
19513 last_value[i] = circ_error.clone();
19514 stamped += 1;
19515 }
19516 }
19517 check_cancel(cancel_flag)?;
19518 let order: Vec<usize> = analysis
19519 .topo
19520 .iter()
19521 .map(|&i| i as usize)
19522 .filter(|&i| !excluded[i])
19523 .collect();
19524 if !order.is_empty() {
19525 passes += 1;
19526 for i in order {
19527 run_member!(i);
19528 }
19529 }
19530 break;
19531 }
19532 CyclePolicy::Iterate {
19533 max_iterations,
19534 max_change,
19535 } => {
19536 iterating = true;
19538
19539 if let Some(prev) = &prev_pass {
19545 let mut round_max_delta = 0f64;
19546 let mut round_nan = 0usize;
19547 let mut all_converged = true;
19548 let mut round_exact = true;
19549 for i in 0..n {
19550 if excluded[i] {
19551 continue;
19555 }
19556 let out = crate::engine::convergence::values_converged(
19557 &prev[i],
19558 &last_value[i],
19559 max_change,
19560 self.config.date_system,
19561 );
19562 if out.nan_converged {
19563 round_nan += 1;
19564 }
19565 if let Some(d) = out.abs_delta {
19566 round_max_delta = round_max_delta.max(d);
19567 if d != 0.0 {
19568 round_exact = false;
19569 }
19570 }
19571 if !out.converged {
19572 all_converged = false;
19573 }
19574 }
19575 iter_max_delta = round_max_delta;
19578 iter_nan_converged = round_nan;
19579 if all_converged {
19580 exact_fixed_point = round_exact;
19581 converged = true;
19582 break;
19583 }
19584 }
19585
19586 if passes >= max_iterations as usize {
19598 capped = true;
19599 break;
19600 }
19601
19602 check_cancel(cancel_flag)?;
19603 prev_pass = Some(last_value.clone());
19611 for x in pos.iter_mut() {
19612 *x = -1;
19613 }
19614 changed.fill(false);
19615 passes += 1;
19616 let mut p = 0i64;
19617 for i in 0..n {
19618 if excluded[i] {
19619 continue;
19620 }
19621 run_member!(i);
19622 pos[i] = p;
19623 p += 1;
19624 }
19625 continue;
19626 }
19627 }
19628 }
19629
19630 let mut stale: Vec<usize> = Vec::new();
19633 for i in 0..n {
19634 if excluded[i] {
19635 continue;
19636 }
19637 let is_stale = out_edges[i].iter().any(|&t| {
19638 let t = t as usize;
19639 changed[t] && (pos[i] < 0 || (pos[t] >= 0 && pos[i] < pos[t]))
19640 });
19641 if is_stale {
19642 stale.push(i);
19643 }
19644 }
19645 if stale.is_empty() {
19646 break; }
19648 if 1 + settle_passes >= cap {
19649 capped = true;
19651 for (i, m) in members.iter().enumerate() {
19652 if !excluded[i] {
19653 self.stamp_cycle_error(m.vertex, &circ_error, None);
19654 excluded[i] = true;
19655 last_value[i] = circ_error.clone();
19656 stamped += 1;
19657 }
19658 }
19659 break;
19660 }
19661
19662 check_cancel(cancel_flag)?;
19663 prev_pass = None;
19670 let topo_pos = analysis.topo_positions();
19671 stale.sort_unstable_by_key(|&i| topo_pos[i]);
19672 for x in pos.iter_mut() {
19673 *x = -1;
19674 }
19675 changed.fill(false);
19676 passes += 1;
19677 settle_passes += 1;
19678 for (p, i) in stale.into_iter().enumerate() {
19679 run_member!(i);
19680 pos[i] = p as i64;
19681 }
19682 }
19683
19684 self.live_cancellation_after_work(SCC_CANCELLED)?;
19690
19691 if iterating && !converged && !capped {
19696 converged = true;
19697 }
19698
19699 collector.clear_current();
19702 if let Some(d) = delta
19703 && d.mode != DeltaMode::Off
19704 {
19705 for (i, m) in members.iter().enumerate() {
19706 if let Some(cell) = m.cell
19707 && last_value[i] != snapshot[i]
19708 {
19709 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
19710 }
19711 }
19712 }
19713
19714 if !self.retained_scc_members.is_empty() {
19732 for m in members.iter() {
19733 self.retained_scc_members.remove(&m.vertex);
19734 }
19735 }
19736 if iterating {
19737 let retain = converged
19738 && !capped
19739 && exact_fixed_point
19740 && iter_nan_converged == 0
19741 && members
19742 .iter()
19743 .all(|m| !self.graph.is_volatile(m.vertex) && !self.graph.is_dynamic(m.vertex));
19744 if retain {
19745 if self.retained_scc_members.is_empty() {
19746 self.retained_scc_config_fingerprint = self.retained_scc_config_fingerprint();
19747 self.retained_scc_function_epoch_seen =
19748 crate::function_registry::semantic_epoch();
19749 self.retained_scc_provider_revision_seen =
19750 self.resolver.planning_semantic_revision();
19751 }
19752 let scc_id = self.next_retained_scc_id;
19753 self.next_retained_scc_id = self.next_retained_scc_id.wrapping_add(1);
19754 for (i, m) in members.iter().enumerate() {
19755 self.retained_scc_members.insert(m.vertex, scc_id);
19756 if matches!(last_value[i], LiteralValue::Empty) {
19759 self.iterative_state_values.remove(&m.vertex);
19760 } else {
19761 self.iterative_state_values
19762 .insert(m.vertex, last_value[i].clone());
19763 }
19764 }
19765 } else {
19766 self.pending_iterative_redirty
19767 .extend(members.iter().map(|m| m.vertex));
19768 }
19769 } else if !self.iterative_state_values.is_empty() {
19770 for m in members.iter() {
19774 self.iterative_state_values.remove(&m.vertex);
19775 }
19776 }
19777
19778 {
19779 let t = &mut self.last_cycle_telemetry;
19780 t.static_sccs += 1;
19781 if witnessed_cycles == 0 && stamped == 0 && !capped {
19782 t.phantom_sccs += 1;
19783 }
19784 t.live_cycles_witnessed += witnessed_cycles;
19785 t.circ_cells_stamped += stamped;
19786 t.settle_passes_total += passes;
19787 t.max_passes_single_scc = t.max_passes_single_scc.max(passes);
19788 if iterating {
19789 t.iterated_sccs += 1;
19790 if converged {
19791 t.converged_sccs += 1;
19792 }
19793 t.max_abs_delta_at_stop = t.max_abs_delta_at_stop.max(iter_max_delta);
19794 t.nan_converged += iter_nan_converged;
19795 }
19796 if capped {
19797 t.capped_sccs += 1;
19798 }
19799 t.elapsed_ms += task_start.elapsed().as_millis();
19800 }
19801
19802 Ok(stamped)
19803 }
19804
19805 fn evaluate_vertex_recorded(
19813 &self,
19814 vertex_id: VertexId,
19815 ctx: &RecordingContext<'_, R>,
19816 collector: &LiveEdgeCollector,
19817 ) -> Result<LiteralValue, ExcelError> {
19818 if !self.graph.vertex_exists(vertex_id) {
19819 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
19820 .with_message(format!("Vertex not found: {vertex_id:?}")));
19821 }
19822
19823 let kind = self.graph.get_vertex_kind(vertex_id);
19824 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
19825
19826 match kind {
19827 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
19828 let Some(view) = self.graph.formula_view(vertex_id) else {
19829 return Ok(LiteralValue::Number(0.0)); };
19831 let sheet_name = self.graph.sheet_name(sheet_id);
19832 let cell_ref = self
19833 .graph
19834 .get_cell_ref(vertex_id)
19835 .expect("cell ref for vertex");
19836 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19837 interpreter
19838 .evaluate_formula_view(view, self.graph.data_store(), self.graph.sheet_reg())
19839 .map(|cv| {
19840 let format = cv.format_id();
19841 self.record_derived_format(vertex_id, format);
19842 self.materialize_formula_result(vertex_id, cv)
19843 })
19844 .or_else(|error| self.fit_formula_error(vertex_id, error))
19845 }
19846 VertexKind::NamedScalar | VertexKind::NamedArray => {
19847 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
19848 ExcelError::new(ExcelErrorKind::Name)
19849 .with_message("Named range metadata missing".to_string())
19850 })?;
19851
19852 match &named_range.definition {
19853 NamedDefinition::Formula { ast, .. } => {
19854 let context_sheet = match named_range.scope {
19855 NameScope::Sheet(id) => id,
19856 NameScope::Workbook => sheet_id,
19857 };
19858 let sheet_name = self.graph.sheet_name(context_sheet);
19859 let cell_ref = self
19860 .graph
19861 .get_cell_ref(vertex_id)
19862 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
19863 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
19864 if kind == VertexKind::NamedScalar {
19865 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
19866 } else {
19867 match interpreter.evaluate_ast(ast) {
19868 Ok(cv) => match cv.into_literal() {
19869 v @ LiteralValue::Array(_) => Ok(v),
19870 other => Ok(LiteralValue::Array(vec![vec![other]])),
19871 },
19872 Err(err) => Ok(LiteralValue::Error(err)),
19873 }
19874 }
19875 }
19876 NamedDefinition::Cell(cell_ref) => {
19877 collector.record_scalar(
19881 cell_ref.sheet_id,
19882 cell_ref.coord.row(),
19883 cell_ref.coord.col(),
19884 );
19885 self.evaluate_vertex_immutable(vertex_id)
19886 }
19887 NamedDefinition::Range(range_ref) => {
19888 if range_ref.start.sheet_id == range_ref.end.sheet_id {
19889 collector.record_rect(
19890 range_ref.start.sheet_id,
19891 range_ref.start.coord.row(),
19892 range_ref.start.coord.col(),
19893 range_ref.end.coord.row(),
19894 range_ref.end.coord.col(),
19895 );
19896 }
19897 self.evaluate_vertex_immutable(vertex_id)
19898 }
19899 NamedDefinition::Literal(_) => self.evaluate_vertex_immutable(vertex_id),
19900 }
19901 }
19902 _ => self.evaluate_vertex_immutable(vertex_id),
19903 }
19904 }
19905
19906 fn pending_spill_occupied(&self, anchor: CellRef, end_row: u32, end_col: u32) -> bool {
19908 let sheet = self.graph.sheet_name(anchor.sheet_id);
19909 let package = self
19910 .staged_formulas
19911 .get(sheet)
19912 .and_then(|staged| staged.deferred_package.as_ref());
19913 self.staged_formula_index.occupies_spill(
19914 sheet,
19915 (anchor.coord.row() + 1, anchor.coord.col() + 1),
19916 (end_row + 1, end_col + 1),
19917 |point| package.is_some_and(|package| package.suppressed.contains(&point)),
19918 )
19919 }
19920
19921 fn remember_pending_spill(
19922 &mut self,
19923 vertex: VertexId,
19924 anchor: CellRef,
19925 region: Region,
19926 ) -> Result<(), ExcelError> {
19927 self.cancellation_checkpoint("pending spill occupancy")?;
19928 self.resource_checkpoint(1)?;
19929 if let Some(entry) = self
19930 .blocked_pending_spills
19931 .iter_mut()
19932 .find(|entry| entry.0 == vertex)
19933 {
19934 *entry = (vertex, anchor, region);
19935 return Ok(());
19936 }
19937 if self.blocked_pending_spills.len() == self.blocked_pending_spills.capacity() {
19938 let additional = self.blocked_pending_spills.capacity().max(1);
19941 let bytes =
19942 (additional as u64)
19943 .saturating_mul(std::mem::size_of::<(VertexId, CellRef, Region)>() as u64);
19944 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19945 ledger
19946 .reserve_retained(bytes)
19947 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19948 self.source_cache_accounted = self.source_cache_accounted.saturating_add(bytes);
19949 }
19950 if self
19951 .blocked_pending_spills
19952 .try_reserve_exact(additional)
19953 .is_err()
19954 {
19955 if let Some(ledger) = self.active_resource_ledger.as_mut() {
19956 ledger
19957 .release_retained(bytes)
19958 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
19959 self.source_cache_accounted -= bytes;
19960 }
19961 return Err(crate::engine::ResourceLedgerError::Exhausted(
19962 formualizer_common::ResourceExhaustionDetail {
19963 reason: formualizer_common::ResourceExhaustionReason::RetainedMemory,
19964 limit: u64::MAX,
19965 observed: bytes,
19966 request_id: None,
19967 },
19968 )
19969 .into_excel_error());
19970 }
19971 }
19972 self.blocked_pending_spills.push((vertex, anchor, region));
19973 Ok(())
19974 }
19975
19976 fn invalidate_pending_spills(&mut self, scope: StructuralScope) {
19980 if let StructuralScope::RemovedSheet(sheet) = scope {
19981 self.blocked_pending_spills
19982 .retain(|entry| entry.1.sheet_id != sheet);
19983 return;
19984 }
19985 for &(vertex, anchor, region) in &self.blocked_pending_spills {
19986 let affected = match scope {
19987 StructuralScope::Cell { sheet, row, col } => {
19988 region.intersects(&Region::point(sheet, row, col))
19989 }
19990 StructuralScope::Region(changed) => region.intersects(&changed),
19991 StructuralScope::Sheet(sheet) | StructuralScope::RemovedSheet(sheet) => {
19992 region.sheet_id() == sheet
19993 }
19994 StructuralScope::OpaqueGlobal | StructuralScope::AllSheets => true,
19995 };
19996 if affected
19997 && self.graph.vertex_exists(vertex)
19998 && self.graph.get_cell_ref(vertex) == Some(anchor)
19999 && matches!(
20000 self.graph.get_vertex_kind(vertex),
20001 VertexKind::FormulaScalar | VertexKind::FormulaArray
20002 )
20003 {
20004 self.graph.mark_vertex_dirty(vertex);
20005 }
20006 }
20007 }
20008
20009 fn guard_pending_spill_commit(
20010 &mut self,
20011 anchor_vertex: VertexId,
20012 targets: &[CellRef],
20013 ) -> Result<(), ExcelError> {
20014 let Some(anchor) = self.graph.get_cell_ref(anchor_vertex) else {
20015 return Ok(());
20016 };
20017 let Some(last) = targets.last() else {
20018 return Ok(());
20019 };
20020 let occupied = self.pending_spill_occupied(anchor, last.coord.row(), last.coord.col())
20021 || self
20022 .graph
20023 .table_intersects_spill(anchor, last.coord.row(), last.coord.col());
20024 if (occupied
20025 || self
20026 .blocked_pending_spills
20027 .iter()
20028 .any(|entry| entry.0 == anchor_vertex))
20029 && let Err(error) = self.remember_pending_spill(
20030 anchor_vertex,
20031 anchor,
20032 Region::rect(
20033 anchor.sheet_id,
20034 anchor.coord.row(),
20035 last.coord.row(),
20036 anchor.coord.col(),
20037 last.coord.col(),
20038 ),
20039 )
20040 {
20041 self.spill_mgr.release_owner(anchor_vertex);
20042 return Err(error);
20043 }
20044 if occupied {
20045 self.spill_mgr.release_owner(anchor_vertex);
20046 return Err(ExcelError::new(ExcelErrorKind::Spill)
20047 .with_message("Spill blocked")
20048 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
20049 expected_rows: last.coord.row() - anchor.coord.row() + 1,
20050 expected_cols: last.coord.col() - anchor.coord.col() + 1,
20051 }));
20052 }
20053 Ok(())
20054 }
20055
20056 fn commit_spill_and_mirror(
20058 &mut self,
20059 anchor_vertex: VertexId,
20060 targets: &[CellRef],
20061 rows: Vec<Vec<LiteralValue>>,
20062 delta: Option<&mut DeltaCollector>,
20063 ) -> Result<(), ExcelError> {
20064 self.guard_pending_spill_commit(anchor_vertex, targets)?;
20065 let prev_spill_cells = self.graph.spill_cells_to_clear(anchor_vertex);
20066
20067 if let Some(delta) = delta
20068 && delta.mode != DeltaMode::Off
20069 {
20070 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
20071 targets.iter().copied().collect();
20072 let empty = LiteralValue::Empty;
20073
20074 for cell in prev_spill_cells.iter() {
20076 if target_set.contains(cell) {
20077 continue;
20078 }
20079 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20080 let old = self
20081 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20082 .unwrap_or(LiteralValue::Empty);
20083 if old != empty {
20084 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20085 }
20086 }
20087
20088 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
20090 let width = rows[0].len();
20091 for (idx, cell) in targets.iter().enumerate() {
20092 let r_off = idx / width;
20093 let c_off = idx % width;
20094 let new = rows
20095 .get(r_off)
20096 .and_then(|r| r.get(c_off))
20097 .cloned()
20098 .unwrap_or(LiteralValue::Empty);
20099 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20100 let old = self
20101 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20102 .unwrap_or(LiteralValue::Empty);
20103 if old != new {
20104 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20105 }
20106 }
20107 } else {
20108 for cell in targets.iter() {
20110 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20111 let old = self
20112 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20113 .unwrap_or(LiteralValue::Empty);
20114 if !matches!(old, LiteralValue::Empty) {
20115 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20116 }
20117 }
20118 }
20119 }
20120
20121 let arrow_sheets = &self.arrow_sheets;
20124 self.spill_mgr.commit_array_with_value_probe(
20125 &mut self.graph,
20126 anchor_vertex,
20127 targets,
20128 rows.clone(),
20129 |g, cell| {
20130 let sheet_name = g.sheet_name(cell.sheet_id);
20131 let asheet = arrow_sheets.sheet(sheet_name)?;
20132 let r0 = cell.coord.row() as usize;
20133 let c0 = cell.coord.col() as usize;
20134 let v = asheet.get_cell_value(r0, c0);
20135 if matches!(v, LiteralValue::Empty) {
20136 None
20137 } else {
20138 Some(v)
20139 }
20140 },
20141 )?;
20142
20143 self.blocked_pending_spills
20144 .retain(|entry| entry.0 != anchor_vertex);
20145 self.spill_blocker_waiters.forget(anchor_vertex);
20146 if let Some(scope) = Self::structural_scope_from_cells(&prev_spill_cells) {
20147 self.record_structural_change(scope);
20148 }
20149 if let Some(scope) = Self::structural_scope_from_cells(targets) {
20150 self.record_structural_change(scope);
20151 }
20152
20153 if self.config.arrow_storage_enabled
20154 && self.config.delta_overlay_enabled
20155 && self.config.write_formula_overlay_enabled
20156 {
20157 if !prev_spill_cells.is_empty() {
20158 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
20159 targets.iter().copied().collect();
20160 let empty = LiteralValue::Empty;
20161 for cell in prev_spill_cells.iter() {
20162 if !target_set.contains(cell) {
20163 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
20164 self.mirror_value_to_computed_overlay(
20165 &sheet_name,
20166 cell.coord.row() + 1,
20167 cell.coord.col() + 1,
20168 &empty,
20169 );
20170 }
20171 }
20172 }
20173
20174 for (idx, cell) in targets.iter().enumerate() {
20175 if rows.is_empty() || rows[0].is_empty() {
20176 break;
20177 }
20178 let width = rows[0].len();
20179 let r_off = idx / width;
20180 let c_off = idx % width;
20181 let v = rows[r_off][c_off].clone();
20182 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
20183 self.mirror_value_to_computed_overlay(
20184 &sheet_name,
20185 cell.coord.row() + 1,
20186 cell.coord.col() + 1,
20187 &v,
20188 );
20189 }
20190 }
20191 Ok(())
20192 }
20193}
20194
20195#[cfg(test)]
20196#[path = "tests/authority_schedule_execution.rs"]
20197mod authority_schedule_execution;
20198
20199#[cfg(test)]
20200#[path = "tests/pending_spill.rs"]
20201mod pending_spill_tests;
20202
20203#[cfg(test)]
20204#[path = "tests/spill_batch_abort_192.rs"]
20205mod spill_batch_abort_192;
20206
20207use crate::engine::effects::Effect;
20212use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
20213
20214impl<R> Engine<R>
20215where
20216 R: EvaluationContext,
20217{
20218 pub(crate) fn plan_vertex_effects(
20225 &mut self,
20226 vertex_id: VertexId,
20227 computed_value: LiteralValue,
20228 ) -> Result<Vec<Effect>, ExcelError> {
20229 if self.freshness_armed() {
20232 if self.freshness_drop_stale(vertex_id) {
20233 return Ok(Vec::new());
20234 }
20235 let effects = self.plan_vertex_effects_unrecorded(vertex_id, computed_value)?;
20236 self.freshness_mark_committed(vertex_id);
20237 return Ok(effects);
20238 }
20239 self.plan_vertex_effects_unrecorded(vertex_id, computed_value)
20240 }
20241
20242 fn plan_vertex_effects_unrecorded(
20243 &mut self,
20244 vertex_id: VertexId,
20245 computed_value: LiteralValue,
20246 ) -> Result<Vec<Effect>, ExcelError> {
20247 let kind = self.graph.get_vertex_kind(vertex_id);
20248 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
20249
20250 if !is_formula {
20254 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
20255 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
20256 && owner != vertex_id
20257 {
20258 return Ok(Vec::new());
20259 }
20260 return Ok(vec![Effect::WriteCell {
20262 vertex_id,
20263 value: computed_value,
20264 }]);
20265 }
20266
20267 match computed_value {
20268 LiteralValue::Array(rows) => self.plan_array_effects(vertex_id, rows),
20269 other => self.plan_scalar_effects(vertex_id, other),
20270 }
20271 }
20272
20273 fn plan_scalar_effects(
20275 &mut self,
20276 vertex_id: VertexId,
20277 value: LiteralValue,
20278 ) -> Result<Vec<Effect>, ExcelError> {
20279 if let LiteralValue::Error(error) = &value
20282 && error.kind == ExcelErrorKind::Spill
20283 && error.message.as_deref() == Some("SpillTooLarge")
20284 && let formualizer_common::ExcelErrorExtra::Spill {
20285 expected_rows,
20286 expected_cols,
20287 } = error.extra
20288 && u64::from(expected_rows).saturating_mul(u64::from(expected_cols))
20289 > u64::from(self.config.spill.max_spill_cells)
20290 {
20291 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
20292 return self.plan_spill_error_effects(
20293 vertex_id,
20294 "SpillTooLarge",
20295 expected_rows,
20296 expected_cols,
20297 );
20298 }
20299 if !matches!(&value, LiteralValue::Error(e) if e.kind == ExcelErrorKind::Spill) {
20300 self.blocked_pending_spills
20301 .retain(|entry| entry.0 != vertex_id);
20302 self.spill_blocker_waiters.forget(vertex_id);
20303 }
20304 let has_spill = self
20305 .graph
20306 .spill_cells_for_anchor(vertex_id)
20307 .is_some_and(|c| !c.is_empty());
20308
20309 let mut effects = Vec::new();
20310 if has_spill {
20311 effects.push(Effect::SpillClear {
20312 anchor_vertex: vertex_id,
20313 });
20314 }
20315 effects.push(Effect::WriteCell { vertex_id, value });
20316 Ok(effects)
20317 }
20318
20319 fn plan_array_effects(
20321 &mut self,
20322 vertex_id: VertexId,
20323 rows: Vec<Vec<LiteralValue>>,
20324 ) -> Result<Vec<Effect>, ExcelError> {
20325 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
20327
20328 let anchor = self
20329 .graph
20330 .get_cell_ref(vertex_id)
20331 .expect("cell ref for vertex");
20332 let sheet_id = anchor.sheet_id;
20333 let h = rows.len() as u32;
20334 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
20335
20336 let spill_cells = (h as u64).saturating_mul(w as u64);
20338 if spill_cells > self.config.spill.max_spill_cells as u64 {
20339 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
20340 }
20341
20342 const PACKED_MAX_ROW: u32 = 1_048_575;
20344 const PACKED_MAX_COL: u32 = 16_383;
20345 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
20346 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
20347 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
20348 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
20349 }
20350
20351 if h != 0 && w != 0 && self.graph.table_intersects_spill(anchor, end_row, end_col) {
20352 return self.plan_spill_error_effects(vertex_id, "Spill blocked by table", h, w);
20353 }
20354 let mut targets = Vec::new();
20355 for r in 0..h {
20356 for c in 0..w {
20357 targets.push(self.graph.make_cell_ref_internal(
20358 sheet_id,
20359 anchor.coord.row() + r,
20360 anchor.coord.col() + c,
20361 ));
20362 }
20363 }
20364
20365 if h != 0 && w != 0 {
20366 let occupied = self.pending_spill_occupied(anchor, end_row, end_col);
20367 if occupied
20368 || self
20369 .blocked_pending_spills
20370 .iter()
20371 .any(|entry| entry.0 == vertex_id)
20372 {
20373 self.spill_mgr.release_owner(vertex_id);
20374 self.remember_pending_spill(
20375 vertex_id,
20376 anchor,
20377 Region::rect(
20378 sheet_id,
20379 anchor.coord.row(),
20380 end_row,
20381 anchor.coord.col(),
20382 end_col,
20383 ),
20384 )?;
20385 }
20386 if occupied {
20387 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
20388 }
20389 }
20390
20391 match self.spill_mgr.reserve(
20393 vertex_id,
20394 anchor,
20395 SpillShape { rows: h, cols: w },
20396 SpillMeta {
20397 epoch: self.recalc_epoch,
20398 config: self.config.spill,
20399 },
20400 ) {
20401 Ok(()) => {
20402 let mut yield_effects = Vec::new();
20404 if let Err((e, blocker)) = self
20405 .graph
20406 .plan_spill_region_with_blocker(vertex_id, &targets)
20407 {
20408 let yielding = if e.message.as_deref() == Some("BlockedBySpill") {
20409 self.graph.spills_yielding_to(vertex_id, &targets)
20410 } else {
20411 None
20412 };
20413 if let Some(yielding) = yielding {
20414 for &other in &yielding {
20418 let (rows, cols) = self
20419 .graph
20420 .spill_extent_for_anchor(other)
20421 .map(|(first, last)| {
20422 (
20423 last.coord.row() - first.coord.row() + 1,
20424 last.coord.col() - first.coord.col() + 1,
20425 )
20426 })
20427 .unwrap_or((0, 0));
20428 yield_effects.push(Effect::SpillClear {
20429 anchor_vertex: other,
20430 });
20431 yield_effects.push(Effect::WriteCell {
20432 vertex_id: other,
20433 value: Self::spill_error_value("Spill blocked", rows, cols),
20434 });
20435 }
20436 self.graph.mark_dirty_many(&yielding);
20437 } else {
20438 if matches!(
20442 e.message.as_deref(),
20443 Some("BlockedByFormula" | "BlockedBySpill")
20444 ) {
20445 self.spill_blocker_waiters.wait(vertex_id, blocker);
20446 }
20447 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
20448 }
20449 }
20450
20451 if !self.graph.value_cache_enabled() {
20455 let sheet_name = self.graph.sheet_name(sheet_id);
20456 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
20457 for cell in targets.iter() {
20458 if *cell == anchor {
20460 continue;
20461 }
20462 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
20464 continue;
20465 }
20466 if let Some(vid) = self.graph.get_vertex_id_for_address(cell)
20468 && vid != vertex_id
20469 {
20470 match self.graph.get_vertex_kind(vid) {
20471 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
20472 continue;
20473 }
20474 _ => {}
20475 }
20476 }
20477
20478 let v = asheet.get_cell_value(
20479 cell.coord.row() as usize,
20480 cell.coord.col() as usize,
20481 );
20482 if !matches!(v, LiteralValue::Empty) {
20483 return self.plan_spill_error_effects(
20484 vertex_id,
20485 "BlockedByValue",
20486 h,
20487 w,
20488 );
20489 }
20490 }
20491 }
20492 }
20493
20494 let top_left = rows
20495 .first()
20496 .and_then(|r| r.first())
20497 .cloned()
20498 .unwrap_or(LiteralValue::Empty);
20499
20500 let mut effects = yield_effects;
20501 let has_prev = self
20503 .graph
20504 .spill_cells_for_anchor(vertex_id)
20505 .is_some_and(|c| !c.is_empty());
20506 if has_prev {
20507 effects.push(Effect::SpillClear {
20508 anchor_vertex: vertex_id,
20509 });
20510 }
20511 effects.push(Effect::SpillCommit {
20512 anchor_vertex: vertex_id,
20513 anchor_cell: anchor,
20514 target_cells: targets,
20515 values: rows,
20516 });
20517 effects.push(Effect::WriteCell {
20518 vertex_id,
20519 value: top_left,
20520 });
20521 Ok(effects)
20522 }
20523 Err(e) => {
20524 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
20525 self.plan_spill_error_effects(vertex_id, &msg, h, w)
20526 }
20527 }
20528 }
20529
20530 fn spill_error_value(message: &str, expected_rows: u32, expected_cols: u32) -> LiteralValue {
20532 LiteralValue::Error(
20533 ExcelError::new(ExcelErrorKind::Spill)
20534 .with_message(message)
20535 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
20536 expected_rows,
20537 expected_cols,
20538 }),
20539 )
20540 }
20541
20542 fn plan_spill_error_effects(
20544 &mut self,
20545 vertex_id: VertexId,
20546 message: &str,
20547 expected_rows: u32,
20548 expected_cols: u32,
20549 ) -> Result<Vec<Effect>, ExcelError> {
20550 self.spill_mgr.release_owner(vertex_id);
20551 let spill_val = Self::spill_error_value(message, expected_rows, expected_cols);
20552
20553 let effects = vec![
20554 Effect::SpillClear {
20555 anchor_vertex: vertex_id,
20556 },
20557 Effect::WriteCell {
20558 vertex_id,
20559 value: spill_val,
20560 },
20561 ];
20562 Ok(effects)
20563 }
20564
20565 pub(crate) fn apply_effect(
20567 &mut self,
20568 effect: &Effect,
20569 delta: Option<&mut DeltaCollector>,
20570 log: Option<&mut ChangeLog>,
20571 ) -> Result<(), ExcelError> {
20572 self.apply_effect_with_computed_writes(effect, delta, log, None)
20573 }
20574
20575 fn apply_effect_with_computed_writes(
20576 &mut self,
20577 effect: &Effect,
20578 delta: Option<&mut DeltaCollector>,
20579 log: Option<&mut ChangeLog>,
20580 computed_writes: Option<&mut ComputedWriteBuffer>,
20581 ) -> Result<(), ExcelError> {
20582 match effect {
20583 Effect::WriteCell { vertex_id, value } => {
20584 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
20585 }
20586 Effect::SpillClear { anchor_vertex } => {
20587 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
20588 }
20589 Effect::SpillCommit {
20590 anchor_vertex,
20591 anchor_cell: _,
20592 target_cells,
20593 values,
20594 } => {
20595 self.apply_spill_commit(
20596 *anchor_vertex,
20597 target_cells,
20598 values.clone(),
20599 delta,
20600 log,
20601 computed_writes,
20602 )?;
20603 }
20604 }
20605 Ok(())
20606 }
20607
20608 fn apply_write_cell(
20610 &mut self,
20611 vertex_id: VertexId,
20612 value: &LiteralValue,
20613 delta: Option<&mut DeltaCollector>,
20614 mut computed_writes: Option<&mut ComputedWriteBuffer>,
20615 ) -> Result<(), ExcelError> {
20616 if let Some(d) = delta
20617 && d.mode != DeltaMode::Off
20618 {
20619 if let Some(buffer) = computed_writes.as_deref_mut() {
20620 self.flush_computed_write_buffer(buffer)?;
20621 }
20622 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
20623 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20624 let old = self
20625 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20626 .unwrap_or(LiteralValue::Empty);
20627 if old != *value {
20628 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20629 }
20630 }
20631 }
20632 self.graph.update_vertex_value_ref(vertex_id, value);
20633 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
20634 Ok(())
20635 }
20636
20637 fn apply_spill_clear(
20639 &mut self,
20640 anchor_vertex: VertexId,
20641 delta: Option<&mut DeltaCollector>,
20642 log: Option<&mut ChangeLog>,
20643 computed_writes: Option<&mut ComputedWriteBuffer>,
20644 ) -> Result<(), ExcelError> {
20645 if let Some(buffer) = computed_writes {
20646 self.flush_computed_write_buffer(buffer)?;
20647 }
20648
20649 let spill_cells = self.graph.spill_cells_to_clear(anchor_vertex);
20650 if spill_cells.is_empty() {
20651 return Ok(());
20652 }
20653
20654 let snapshot = if log.is_some() {
20656 self.snapshot_spill_for_anchor(anchor_vertex)
20657 } else {
20658 None
20659 };
20660
20661 if let Some(d) = delta
20663 && d.mode != DeltaMode::Off
20664 {
20665 let empty = LiteralValue::Empty;
20666 for cell in spill_cells.iter() {
20667 let sheet_name = self.graph.sheet_name(cell.sheet_id);
20668 let old = self
20669 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20670 .unwrap_or(LiteralValue::Empty);
20671 if old != empty {
20672 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
20673 }
20674 }
20675 }
20676
20677 self.graph.clear_spill_region(anchor_vertex);
20678 if let Some(scope) = Self::structural_scope_from_cells(&spill_cells) {
20679 self.record_structural_change(scope);
20680 }
20681
20682 if self.config.arrow_storage_enabled
20684 && self.config.delta_overlay_enabled
20685 && self.config.write_formula_overlay_enabled
20686 {
20687 let empty = LiteralValue::Empty;
20688 for cell in spill_cells.iter() {
20689 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
20690 self.mirror_value_to_computed_overlay(
20691 &sheet_name,
20692 cell.coord.row() + 1,
20693 cell.coord.col() + 1,
20694 &empty,
20695 );
20696 }
20697 }
20698
20699 if let Some(log) = log
20701 && let Some(old) = snapshot
20702 {
20703 log.record(ChangeEvent::SpillCleared {
20704 anchor: anchor_vertex,
20705 old,
20706 });
20707 }
20708 Ok(())
20709 }
20710
20711 fn apply_spill_commit(
20713 &mut self,
20714 anchor_vertex: VertexId,
20715 target_cells: &[CellRef],
20716 values: Vec<Vec<LiteralValue>>,
20717 delta: Option<&mut DeltaCollector>,
20718 log: Option<&mut ChangeLog>,
20719 computed_writes: Option<&mut ComputedWriteBuffer>,
20720 ) -> Result<(), ExcelError> {
20721 self.guard_pending_spill_commit(anchor_vertex, target_cells)?;
20722 if let Some(buffer) = computed_writes {
20723 self.flush_computed_write_buffer(buffer)?;
20724 }
20725
20726 let old_snapshot = if log.is_some() {
20728 self.snapshot_spill_for_anchor(anchor_vertex)
20729 } else {
20730 None
20731 };
20732
20733 self.commit_spill_and_mirror(anchor_vertex, target_cells, values.clone(), delta)?;
20735
20736 if let Some(log) = log {
20738 log.record(ChangeEvent::SpillCommitted {
20739 anchor: anchor_vertex,
20740 old: old_snapshot,
20741 new: SpillSnapshot {
20742 target_cells: target_cells.to_vec(),
20743 values,
20744 },
20745 });
20746 }
20747 Ok(())
20748 }
20749
20750 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
20755 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
20756 if cells.is_empty() {
20757 return None;
20758 }
20759
20760 let max = self.config.spill.max_spill_cells as usize;
20761 let mut cells = cells;
20762 if cells.len() > max {
20763 cells.truncate(max);
20764 }
20765
20766 let first = *cells.first().expect("non-empty spill cells");
20767 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
20768 let row0 = first.coord.row();
20769 let col0 = first.coord.col();
20770
20771 let mut max_row = row0;
20772 let mut max_col = col0;
20773 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
20774 for cell in &cells {
20775 max_row = max_row.max(cell.coord.row());
20776 max_col = max_col.max(cell.coord.col());
20777 let v = self
20778 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
20779 .unwrap_or(LiteralValue::Empty);
20780 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
20781 }
20782
20783 let rows = (max_row - row0 + 1) as usize;
20784 let cols = (max_col - col0 + 1) as usize;
20785 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
20786 for r in 0..rows {
20787 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
20788 for c in 0..cols {
20789 row.push(
20790 by_coord
20791 .get(&(row0 + r as u32, col0 + c as u32))
20792 .cloned()
20793 .unwrap_or(LiteralValue::Empty),
20794 );
20795 }
20796 values.push(row);
20797 }
20798
20799 Some(SpillSnapshot {
20800 target_cells: cells,
20801 values,
20802 })
20803 }
20804
20805 fn flush_before_range_dependent_vertex(
20806 &mut self,
20807 vertex_id: VertexId,
20808 computed_writes: &mut ComputedWriteBuffer,
20809 ) -> Result<(), ExcelError> {
20810 if self.graph.reads_compressed_range(vertex_id) {
20811 self.flush_computed_write_buffer(computed_writes)?;
20812 }
20813 Ok(())
20814 }
20815
20816 fn plan_vertex_effects_with_computed_flush(
20817 &mut self,
20818 vertex_id: VertexId,
20819 computed_value: LiteralValue,
20820 computed_writes: &mut ComputedWriteBuffer,
20821 ) -> Result<Vec<Effect>, ExcelError> {
20822 if matches!(&computed_value, LiteralValue::Array(_)) {
20823 self.flush_computed_write_buffer(computed_writes)?;
20824 }
20825 self.plan_vertex_effects(vertex_id, computed_value)
20826 }
20827
20828 fn evaluate_small_layer_direct_effects(
20831 &mut self,
20832 layer: &super::scheduler::Layer,
20833 delta: Option<&mut DeltaCollector>,
20834 log: Option<&mut ChangeLog>,
20835 cancel_flag: Option<&AtomicBool>,
20836 cancel_check_every: usize,
20837 cancel_message: &'static str,
20838 ) -> Result<usize, ExcelError> {
20839 let cancel = cancel_flag.map(|flag| (flag, cancel_check_every, cancel_message));
20840 self.evaluate_layer_units(layer, delta, log, cancel, false)
20841 }
20842
20843 fn evaluate_layer_units(
20850 &mut self,
20851 layer: &super::scheduler::Layer,
20852 delta: Option<&mut DeltaCollector>,
20853 log: Option<&mut ChangeLog>,
20854 cancel: Option<(&AtomicBool, usize, &'static str)>,
20855 buffered: bool,
20856 ) -> Result<usize, ExcelError> {
20857 self.evaluate_layer_units_until(layer, delta, log, cancel, buffered, None)
20858 }
20859
20860 fn evaluate_layer_units_until(
20864 &mut self,
20865 layer: &super::scheduler::Layer,
20866 mut delta: Option<&mut DeltaCollector>,
20867 mut log: Option<&mut ChangeLog>,
20868 cancel: Option<(&AtomicBool, usize, &'static str)>,
20869 buffered: bool,
20870 stop_at: Option<crate::instant::FzInstant>,
20871 ) -> Result<usize, ExcelError> {
20872 let mut chain_values = None;
20875 if layer.sequential && !layer.runs.is_empty() {
20876 chain_values = match layer.runs.as_slice() {
20877 [run] if run.start == 0 && run.len as usize == layer.vertices.len() => {
20878 self.try_chain_lift(*run, &layer.vertices)
20879 }
20880 _ => None,
20881 };
20882 if chain_values.is_none() {
20883 let cells = super::scheduler::Layer {
20884 vertices: layer.vertices.clone(),
20885 runs: Vec::new(),
20886 sequential: true,
20887 };
20888 return self.evaluate_layer_units_until(&cells, delta, log, cancel, false, stop_at);
20889 }
20890 }
20891 let chained = chain_values.is_some();
20892 let buffered = buffered || chained;
20894 let track_unflushed = buffered
20901 && self.freshness_armed()
20902 && layer.vertices.iter().any(|&v| self.graph.is_dynamic(v));
20903 let mut committed_unit: &[VertexId] = &[];
20904 let mut computed_writes = ComputedWriteBuffer::default();
20905 let mut next_check = 0usize;
20906 let mut done = 0usize;
20907 for unit in layer_units(layer) {
20908 if done > 0
20909 && let Some(stop_at) = stop_at
20910 && crate::instant::FzInstant::now() >= stop_at
20911 {
20912 self.flush_computed_write_buffer(&mut computed_writes)?;
20913 if track_unflushed {
20914 self.freshness_flushed();
20915 }
20916 return Ok(done);
20917 }
20918 if let Some((flag, every, message)) = cancel
20919 && every > 0
20920 && done >= next_check
20921 {
20922 next_check = (done / every + 1) * every;
20923 if flag.load(Ordering::Relaxed) {
20924 if buffered {
20925 self.flush_computed_write_buffer(&mut computed_writes)?;
20926 }
20927 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
20928 .with_message(message.to_string()));
20929 }
20930 }
20931 if buffered && self.unit_reads_compressed_range(layer, unit) {
20932 self.flush_computed_write_buffer(&mut computed_writes)?;
20933 }
20934 if track_unflushed {
20938 if computed_writes.is_empty() {
20939 self.freshness_flushed();
20940 } else {
20941 self.freshness_note_unflushed(committed_unit);
20942 }
20943 committed_unit = unit_members(layer, unit);
20944 }
20945 let values = match (chain_values.take(), unit) {
20946 (Some(chain), LayerUnit::Run(run)) => {
20947 if let Err(e) = self.live_cancellation_after_work(UNIT_CANCELLED) {
20948 self.flush_computed_write_buffer(&mut computed_writes)?;
20949 return Err(e);
20950 }
20951 let members =
20952 &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20953 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20954 match self.commit_run_numbers(
20955 run,
20956 members,
20957 &chain,
20958 delta_active,
20959 Some(&mut computed_writes),
20960 ) {
20961 Ok(true) => {
20962 done += chain.len();
20963 continue;
20964 }
20965 Ok(false) => {}
20966 Err(e) => {
20967 self.flush_computed_write_buffer(&mut computed_writes)?;
20968 return Err(e);
20969 }
20970 }
20971 members
20972 .iter()
20973 .copied()
20974 .zip(chain.into_iter().map(LiteralValue::Number))
20975 .collect()
20976 }
20977 (_, unit) => self.evaluate_unit_immutable(layer, unit),
20978 };
20979 if let Err(e) = self.live_cancellation_after_work(UNIT_CANCELLED) {
20982 self.flush_computed_write_buffer(&mut computed_writes)?;
20983 return Err(e);
20984 }
20985 done += values.len();
20986 if let LayerUnit::Run(run) = unit {
20987 let members = &layer.vertices[run.start as usize..(run.start + run.len) as usize];
20988 let delta_active = delta.as_deref().is_some_and(|d| d.mode != DeltaMode::Off);
20989 let committed = self.commit_run_scalars(
20990 run,
20991 members,
20992 &values,
20993 delta_active,
20994 buffered.then_some(&mut computed_writes),
20995 );
20996 match committed {
20997 Ok(true) => continue,
20998 Ok(false) => {}
20999 Err(e) => {
21000 self.flush_computed_write_buffer(&mut computed_writes)?;
21001 return Err(e);
21002 }
21003 }
21004 }
21005 let guarded = self.freshness_begin_batch_commit(&values);
21008 for (vertex_id, value) in values {
21009 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21010 let effects = if buffered {
21011 self.plan_vertex_effects_with_computed_flush(
21012 vertex_id,
21013 value,
21014 &mut computed_writes,
21015 )
21016 } else {
21017 self.plan_vertex_effects(vertex_id, value)
21018 };
21019 let effects = match effects {
21020 Ok(effects) => effects,
21021 Err(e) => {
21022 self.flush_computed_write_buffer(&mut computed_writes)?;
21023 return Err(e);
21024 }
21025 };
21026 for effect in &effects {
21027 if let Err(e) = self.apply_effect_with_computed_writes(
21028 effect,
21029 delta.as_deref_mut(),
21030 log.as_deref_mut(),
21031 buffered.then_some(&mut computed_writes),
21032 ) {
21033 self.flush_computed_write_buffer(&mut computed_writes)?;
21034 return Err(e);
21035 }
21036 }
21037 self.freshness_keep_redirtied(redirtied, vertex_id);
21038 }
21039 }
21040 self.flush_computed_write_buffer(&mut computed_writes)?;
21041 if track_unflushed {
21042 self.freshness_flushed();
21043 }
21044 #[cfg(debug_assertions)]
21047 if chained {
21048 for &v in &layer.vertices {
21049 let cell = self.graph.get_cell_ref(v);
21050 let (sheet, row, col) = cell
21051 .map(|c| {
21052 (
21053 self.graph.sheet_name(c.sheet_id).to_string(),
21054 c.coord.row() + 1,
21055 c.coord.col() + 1,
21056 )
21057 })
21058 .expect("chain member cell");
21059 let written = self.get_cell_value(&sheet, row, col);
21060 let oracle = self
21061 .evaluate_vertex_immutable(v)
21062 .unwrap_or_else(LiteralValue::Error);
21063 assert!(
21064 written
21065 .as_ref()
21066 .is_some_and(|w| same_value_bits(w, &oracle)),
21067 "chain member {sheet}!R{row}C{col}: {written:?} vs per-cell {oracle:?}"
21068 );
21069 }
21070 }
21071 #[cfg(not(debug_assertions))]
21072 let _ = chained;
21073 Ok(layer.vertices.len())
21074 }
21075
21076 fn evaluate_layer_sequential_effects(
21078 &mut self,
21079 layer: &super::scheduler::Layer,
21080 ) -> Result<usize, ExcelError> {
21081 let buffered = buffer_layer_writes(layer);
21082 self.evaluate_layer_units(layer, None, None, None, buffered)
21083 }
21084
21085 fn evaluate_layer_sequential_with_delta_effects(
21087 &mut self,
21088 layer: &super::scheduler::Layer,
21089 delta: &mut DeltaCollector,
21090 ) -> Result<usize, ExcelError> {
21091 let buffered = buffer_layer_writes(layer);
21092 self.evaluate_layer_units(layer, Some(delta), None, None, buffered)
21093 }
21094
21095 fn evaluate_layer_sequential_cancellable_effects(
21097 &mut self,
21098 layer: &super::scheduler::Layer,
21099 cancel_flag: &AtomicBool,
21100 ) -> Result<usize, ExcelError> {
21101 let buffered = buffer_layer_writes(layer);
21102 let cancel = (cancel_flag, 256, "Evaluation cancelled within layer");
21103 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
21104 }
21105
21106 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
21108 &mut self,
21109 layer: &super::scheduler::Layer,
21110 cancel_flag: &AtomicBool,
21111 ) -> Result<usize, ExcelError> {
21112 let buffered = buffer_layer_writes(layer);
21113 let cancel = (
21114 cancel_flag,
21115 128,
21116 "Demand-driven evaluation cancelled within layer",
21117 );
21118 self.evaluate_layer_units(layer, None, None, Some(cancel), buffered)
21119 }
21120
21121 fn evaluate_layer_parallel_effects(
21123 &mut self,
21124 layer: &super::scheduler::Layer,
21125 min_chunk: u32,
21126 ) -> Result<usize, ExcelError> {
21127 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
21128
21129 let phases = self.parallel_phases(layer);
21130 let mut applied = 0usize;
21131
21132 for (units, group) in &phases {
21133 let group = &group[..];
21134 if group.is_empty() {
21135 continue;
21136 }
21137 let mut computed_writes = ComputedWriteBuffer::default();
21138
21139 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
21140 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, min_chunk));
21141
21142 let results = results.and_then(|results| {
21145 self.live_cancellation_after_work(GROUP_CANCELLED)
21146 .map(|()| results)
21147 });
21148 self.freshness_gate_group(group);
21151 match results {
21152 Ok(vertex_results) => {
21153 let (vertex_results, committed) = self.commit_parallel_runs(
21154 layer,
21155 units,
21156 vertex_results,
21157 false,
21158 &mut computed_writes,
21159 )?;
21160 applied = applied.saturating_add(committed);
21161 let guarded = self.freshness_begin_batch_commit(&vertex_results);
21166 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
21167 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
21168 for (vertex_id, result) in vertex_results {
21169 if matches!(result, LiteralValue::Array(_)) {
21170 arrays.push((vertex_id, result));
21171 } else {
21172 others.push((vertex_id, result));
21173 }
21174 }
21175 for (vertex_id, result) in arrays {
21176 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21177 let effects = match self.plan_vertex_effects_with_computed_flush(
21178 vertex_id,
21179 result,
21180 &mut computed_writes,
21181 ) {
21182 Ok(effects) => effects,
21183 Err(e) => {
21184 self.flush_computed_write_buffer(&mut computed_writes)?;
21185 return Err(e);
21186 }
21187 };
21188 for effect in &effects {
21189 if let Err(e) = self.apply_effect_with_computed_writes(
21190 effect,
21191 None,
21192 None,
21193 Some(&mut computed_writes),
21194 ) {
21195 self.flush_computed_write_buffer(&mut computed_writes)?;
21196 return Err(e);
21197 }
21198 }
21199 self.freshness_keep_redirtied(redirtied, vertex_id);
21200 applied = applied.saturating_add(1);
21201 }
21202 self.flush_computed_write_buffer(&mut computed_writes)?;
21204 for (vertex_id, result) in others {
21205 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21206 let effects = match self.plan_vertex_effects_with_computed_flush(
21207 vertex_id,
21208 result,
21209 &mut computed_writes,
21210 ) {
21211 Ok(effects) => effects,
21212 Err(e) => {
21213 self.flush_computed_write_buffer(&mut computed_writes)?;
21214 return Err(e);
21215 }
21216 };
21217 for effect in &effects {
21218 if let Err(e) = self.apply_effect_with_computed_writes(
21219 effect,
21220 None,
21221 None,
21222 Some(&mut computed_writes),
21223 ) {
21224 self.flush_computed_write_buffer(&mut computed_writes)?;
21225 return Err(e);
21226 }
21227 }
21228 self.freshness_keep_redirtied(redirtied, vertex_id);
21229 applied = applied.saturating_add(1);
21230 }
21231 self.flush_computed_write_buffer(&mut computed_writes)?;
21233 }
21234 Err(e) => {
21235 self.flush_computed_write_buffer(&mut computed_writes)?;
21236 return Err(e);
21237 }
21238 }
21239 }
21240
21241 Ok(applied)
21242 }
21243
21244 fn evaluate_layer_parallel_with_delta_effects(
21246 &mut self,
21247 layer: &super::scheduler::Layer,
21248 delta: &mut DeltaCollector,
21249 ) -> Result<usize, ExcelError> {
21250 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
21251
21252 let phases = self.parallel_phases(layer);
21253 let mut applied = 0usize;
21254
21255 for (units, group) in &phases {
21256 let group = &group[..];
21257 if group.is_empty() {
21258 continue;
21259 }
21260 let mut computed_writes = ComputedWriteBuffer::default();
21261 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
21262 thread_pool.install(|| self.evaluate_units_parallel(layer, units, None, 8));
21263
21264 let results = results.and_then(|results| {
21267 self.live_cancellation_after_work(GROUP_CANCELLED)
21268 .map(|()| results)
21269 });
21270 self.freshness_gate_group(group);
21273 match results {
21274 Ok(vertex_results) => {
21275 let (vertex_results, committed) = self.commit_parallel_runs(
21276 layer,
21277 units,
21278 vertex_results,
21279 delta.mode != DeltaMode::Off,
21280 &mut computed_writes,
21281 )?;
21282 applied = applied.saturating_add(committed);
21283 let guarded = self.freshness_begin_batch_commit(&vertex_results);
21286 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
21287 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
21288 for (vertex_id, result) in vertex_results {
21289 if matches!(result, LiteralValue::Array(_)) {
21290 arrays.push((vertex_id, result));
21291 } else {
21292 others.push((vertex_id, result));
21293 }
21294 }
21295 for (vertex_id, result) in arrays {
21296 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21297 let effects = match self.plan_vertex_effects_with_computed_flush(
21298 vertex_id,
21299 result,
21300 &mut computed_writes,
21301 ) {
21302 Ok(effects) => effects,
21303 Err(e) => {
21304 self.flush_computed_write_buffer(&mut computed_writes)?;
21305 return Err(e);
21306 }
21307 };
21308 for effect in &effects {
21309 if let Err(e) = self.apply_effect_with_computed_writes(
21310 effect,
21311 Some(delta),
21312 None,
21313 Some(&mut computed_writes),
21314 ) {
21315 self.flush_computed_write_buffer(&mut computed_writes)?;
21316 return Err(e);
21317 }
21318 }
21319 self.freshness_keep_redirtied(redirtied, vertex_id);
21320 applied = applied.saturating_add(1);
21321 }
21322 self.flush_computed_write_buffer(&mut computed_writes)?;
21323 for (vertex_id, result) in others {
21324 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21325 let effects = match self.plan_vertex_effects_with_computed_flush(
21326 vertex_id,
21327 result,
21328 &mut computed_writes,
21329 ) {
21330 Ok(effects) => effects,
21331 Err(e) => {
21332 self.flush_computed_write_buffer(&mut computed_writes)?;
21333 return Err(e);
21334 }
21335 };
21336 for effect in &effects {
21337 if let Err(e) = self.apply_effect_with_computed_writes(
21338 effect,
21339 Some(delta),
21340 None,
21341 Some(&mut computed_writes),
21342 ) {
21343 self.flush_computed_write_buffer(&mut computed_writes)?;
21344 return Err(e);
21345 }
21346 }
21347 self.freshness_keep_redirtied(redirtied, vertex_id);
21348 applied = applied.saturating_add(1);
21349 }
21350 self.flush_computed_write_buffer(&mut computed_writes)?;
21351 }
21352 Err(e) => {
21353 self.flush_computed_write_buffer(&mut computed_writes)?;
21354 return Err(e);
21355 }
21356 }
21357 }
21358
21359 Ok(applied)
21360 }
21361
21362 fn evaluate_layer_parallel_cancellable_effects(
21364 &mut self,
21365 layer: &super::scheduler::Layer,
21366 cancel_flag: &AtomicBool,
21367 ) -> Result<usize, ExcelError> {
21368 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
21369
21370 if cancel_flag.load(Ordering::Relaxed) {
21371 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
21372 .with_message("Parallel evaluation cancelled before starting".to_string()));
21373 }
21374
21375 let phases = self.parallel_phases(layer);
21376 let mut applied = 0usize;
21377
21378 for (units, group) in &phases {
21379 let group = &group[..];
21380 if group.is_empty() {
21381 continue;
21382 }
21383 let mut computed_writes = ComputedWriteBuffer::default();
21384
21385 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> = thread_pool
21386 .install(|| self.evaluate_units_parallel(layer, units, Some(cancel_flag), 8));
21387
21388 let results = results.and_then(|results| {
21391 self.live_cancellation_after_work(GROUP_CANCELLED)
21392 .map(|()| results)
21393 });
21394 self.freshness_gate_group(group);
21397 match results {
21398 Ok(vertex_results) => {
21399 let (vertex_results, committed) = self.commit_parallel_runs(
21400 layer,
21401 units,
21402 vertex_results,
21403 false,
21404 &mut computed_writes,
21405 )?;
21406 applied = applied.saturating_add(committed);
21407 let guarded = self.freshness_begin_batch_commit(&vertex_results);
21410 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
21411 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
21412 for (vertex_id, result) in vertex_results {
21413 if matches!(result, LiteralValue::Array(_)) {
21414 arrays.push((vertex_id, result));
21415 } else {
21416 others.push((vertex_id, result));
21417 }
21418 }
21419 for (vertex_id, result) in arrays {
21420 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21421 let effects = match self.plan_vertex_effects_with_computed_flush(
21422 vertex_id,
21423 result,
21424 &mut computed_writes,
21425 ) {
21426 Ok(effects) => effects,
21427 Err(e) => {
21428 self.flush_computed_write_buffer(&mut computed_writes)?;
21429 return Err(e);
21430 }
21431 };
21432 for effect in &effects {
21433 if let Err(e) = self.apply_effect_with_computed_writes(
21434 effect,
21435 None,
21436 None,
21437 Some(&mut computed_writes),
21438 ) {
21439 self.flush_computed_write_buffer(&mut computed_writes)?;
21440 return Err(e);
21441 }
21442 }
21443 self.freshness_keep_redirtied(redirtied, vertex_id);
21444 applied = applied.saturating_add(1);
21445 }
21446 self.flush_computed_write_buffer(&mut computed_writes)?;
21447 for (vertex_id, result) in others {
21448 let redirtied = self.freshness_batch_redirtied(guarded, vertex_id);
21449 let effects = match self.plan_vertex_effects_with_computed_flush(
21450 vertex_id,
21451 result,
21452 &mut computed_writes,
21453 ) {
21454 Ok(effects) => effects,
21455 Err(e) => {
21456 self.flush_computed_write_buffer(&mut computed_writes)?;
21457 return Err(e);
21458 }
21459 };
21460 for effect in &effects {
21461 if let Err(e) = self.apply_effect_with_computed_writes(
21462 effect,
21463 None,
21464 None,
21465 Some(&mut computed_writes),
21466 ) {
21467 self.flush_computed_write_buffer(&mut computed_writes)?;
21468 return Err(e);
21469 }
21470 }
21471 self.freshness_keep_redirtied(redirtied, vertex_id);
21472 applied = applied.saturating_add(1);
21473 }
21474 self.flush_computed_write_buffer(&mut computed_writes)?;
21475 }
21476 Err(e) => {
21477 self.flush_computed_write_buffer(&mut computed_writes)?;
21478 return Err(e);
21479 }
21480 }
21481 }
21482
21483 Ok(applied)
21484 }
21485
21486 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
21493 self.observe_evaluation_resource_request(EvaluationRequestKind::FullLogged, |engine| {
21494 engine.evaluate_all_logged_unobserved(log)
21495 })
21496 }
21497
21498 fn evaluate_all_logged_unobserved(
21499 &mut self,
21500 log: &mut ChangeLog,
21501 ) -> Result<EvalResult, ExcelError> {
21502 self.observe_function_semantic_epoch()?;
21503 let _source_cache = self.source_cache_session();
21504 self.validate_deterministic_mode()?;
21505 if self.config.defer_graph_building {
21506 self.build_graph_all()?;
21507 }
21508 self.require_unified_authority()?;
21509 self.begin_evaluation_request();
21510 self.reset_virtual_dep_telemetry_if_disabled();
21511 let start = crate::instant::FzInstant::now();
21512 let mut computed_vertices = 0;
21513 let mut cycle_errors = 0;
21514
21515 let mut replan_iterations = 0;
21516 const MAX_REPLAN: usize = 5;
21517 let mut telemetry = self
21518 .config
21519 .enable_virtual_dep_telemetry
21520 .then(|| self.start_virtual_dep_telemetry());
21521
21522 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
21523
21524 let result = (|| -> Result<EvalResult, ExcelError> {
21525 loop {
21526 let to_evaluate = self.graph.get_evaluation_vertices();
21527 if to_evaluate.is_empty() {
21528 if let Some(t) = telemetry.as_mut()
21529 && t.bailout_reason.is_none()
21530 {
21531 t.bailout_reason = Some("no_work");
21532 }
21533 break;
21534 }
21535
21536 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
21537 if let Some(t) = telemetry.as_mut() {
21538 Self::accumulate_schedule_meta(t, &meta);
21539 }
21540
21541 self.begin_pass(&schedule);
21544 for (unit_index, &unit) in schedule.units.iter().enumerate() {
21545 match unit {
21546 ScheduleUnit::Cycle(i) => {
21547 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0
21558 {
21559 cycle_errors += 1;
21560 }
21561 }
21562 ScheduleUnit::Layer(i) => {
21563 computed_vertices +=
21564 self.evaluate_layer_logged(schedule.unit_layer(i), log)?;
21565 }
21566 }
21567 if self.stop_after_unit(&schedule, unit_index) {
21568 break;
21569 }
21570 }
21571
21572 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
21573 if let Some(t) = telemetry.as_mut() {
21574 t.changed_vdeps_total += changed_vertices.len();
21575 }
21576 self.resource_checkpoint(0)?;
21577 if !self.finish_pass_dirty(&to_evaluate, &changed_vertices) {
21578 if let Some(t) = telemetry.as_mut() {
21579 t.bailout_reason = Some("converged");
21580 }
21581 break;
21582 }
21583 if replan_iterations >= MAX_REPLAN {
21584 if let Some(mut t) = telemetry.take() {
21585 t.bailout_reason = Some("max_replan");
21586 t.replan_iterations = replan_iterations;
21587 self.last_virtual_dep_telemetry = t;
21588 }
21589 return Err(
21590 self.replan_exhausted_error(MAX_REPLAN, "dynamic dependency evaluation")
21591 );
21592 }
21593 replan_iterations += 1;
21594 }
21595
21596 if let Some(mut t) = telemetry {
21597 t.replan_iterations = replan_iterations;
21598 self.last_virtual_dep_telemetry = t;
21599 }
21600
21601 self.redirty_for_next_recalc();
21602 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
21603
21604 Ok(EvalResult {
21605 computed_vertices,
21606 cycle_errors,
21607 elapsed: start.elapsed(),
21608 })
21609 })();
21610 log.end_compound();
21611 result
21612 }
21613
21614 fn evaluate_layer_logged(
21616 &mut self,
21617 layer: &super::scheduler::Layer,
21618 log: &mut ChangeLog,
21619 ) -> Result<usize, ExcelError> {
21620 self.resource_checkpoint(layer.vertices.len() as u64)?;
21621 self.evaluate_layer_units(layer, None, Some(log), None, true)
21622 }
21623}