1use crate::SheetId;
2use crate::arrow_store::{OverlayFragment, OverlayValue, SheetStore};
3use crate::engine::arena::AstNodeId;
4use crate::engine::eval_delta::{DeltaCollector, DeltaMode, EvalDelta};
5use crate::engine::ingest_pipeline::{DependencyPlanRow, FormulaAstInput};
6use crate::engine::live_edges::{LiveEdgeCollector, RecordingContext};
7use crate::engine::live_graph::analyze_live_graph;
8use crate::engine::lookup_index_cache::{
9 BuildOutcome, LookupAxis, LookupIndex, LookupIndexCache, LookupIndexCacheReport,
10 LookupIndexKey, estimate_bytes,
11};
12use crate::engine::named_range::{NameScope, NamedDefinition};
13use crate::engine::range_view::RangeView;
14use crate::engine::row_visibility::RowVisibilityState;
15use crate::engine::spill::{RegionLockManager, SpillMeta, SpillShape};
16use crate::engine::virtual_deps::VirtualDepBuilder;
17use crate::engine::{
18 CycleDetection, CyclePolicy, DependencyGraph, EvalConfig, FormulaIngestBatch,
19 FormulaIngestRecord, FormulaIngestReport, FormulaParseDiagnostic, FormulaParsePolicy,
20 FormulaPlaneMode, RowVisibilitySource, ScheduleUnit, Scheduler, VertexId, VertexKind,
21 VisibilityMaskMode,
22};
23use crate::formula_plane::placement::{
24 CandidateAnalysis, FormulaPlacementCandidate, FormulaPlacementResult, PlacementFallbackReason,
25 place_candidate_family_with_analyses, split_candidate_affine_literal_runs,
26};
27use crate::formula_plane::producer::{
28 DirtyProjectionRule, FormulaConsumerReadIndex, FormulaProducerId, FormulaProducerResultIndex,
29 FormulaProducerWork, ProducerDirtyDomain, SpanReadSummary,
30};
31use crate::formula_plane::region_index::{DirtyDomain, Region};
32use crate::formula_plane::runtime::{
33 FormulaPlane, FormulaSpanId, FormulaSpanRef, PlacementCoord, PlacementDomain, ResultRegion,
34};
35use crate::formula_plane::scheduler::{
36 MixedSchedule, MixedScheduleFallbackReason, build_mixed_schedule,
37};
38#[cfg(test)]
39use crate::formula_plane::span_eval::SpanEvalReport;
40use crate::formula_plane::span_eval::{SpanComputedWriteSink, SpanEvalTask, SpanEvaluator};
41use crate::formula_plane::structural::relocate_ast_for_template_placement;
42use crate::formula_plane::structural_shift::{SpanShiftPlan, StructuralOp, classify_span_for_op};
43use crate::interpreter::Interpreter;
44use crate::reference::{CellRef, Coord, RangeRef};
45use crate::traits::FunctionProvider;
46use crate::traits::{EvaluationContext, ReferenceInfo, Resolver};
47use chrono::Timelike;
48use formualizer_common::{
49 CoordBuildHasher, LiteralValue, col_letters_from_1based, parse_a1_1based,
50};
51use formualizer_parse::parser::ReferenceType;
52use formualizer_parse::{ASTNode, ASTNodeType, ExcelError, ExcelErrorKind};
53use rayon::ThreadPoolBuilder;
54use rustc_hash::{FxHashMap, FxHashSet};
55use std::collections::{BTreeMap, BTreeSet, VecDeque};
56use std::sync::Arc;
57use std::sync::atomic::{AtomicBool, Ordering};
58
59type StagedFormulaEntry = (u32, u32, String);
60
61#[derive(Debug, Default, Clone)]
71pub(crate) struct StagedSheet {
72 entries: Vec<StagedFormulaEntry>,
73 index: FxHashMap<(u32, u32), usize>,
74}
75
76impl StagedSheet {
77 fn stage(&mut self, row: u32, col: u32, text: String) {
78 match self.index.entry((row, col)) {
79 std::collections::hash_map::Entry::Occupied(slot) => {
80 self.entries[*slot.get()].2 = text;
81 }
82 std::collections::hash_map::Entry::Vacant(slot) => {
83 slot.insert(self.entries.len());
84 self.entries.push((row, col, text));
85 }
86 }
87 }
88
89 fn remove(&mut self, row: u32, col: u32) -> Option<String> {
90 let idx = self.index.remove(&(row, col))?;
91 let (_, _, text) = self.entries.remove(idx);
92 for slot in self.index.values_mut() {
94 if *slot > idx {
95 *slot -= 1;
96 }
97 }
98 Some(text)
99 }
100
101 fn get(&self, row: u32, col: u32) -> Option<&str> {
102 self.index
103 .get(&(row, col))
104 .map(|&i| self.entries[i].2.as_str())
105 }
106
107 fn len(&self) -> usize {
108 self.entries.len()
109 }
110
111 fn is_empty(&self) -> bool {
112 self.entries.is_empty()
113 }
114
115 fn into_entries(self) -> Vec<StagedFormulaEntry> {
117 self.entries
118 }
119}
120
121type StagedFormulaMap = std::collections::HashMap<String, StagedSheet>;
122
123fn producer_dirty_to_span_dirty(
124 dirty: ProducerDirtyDomain,
125 span_ref: FormulaSpanRef,
126) -> DirtyDomain {
127 match dirty {
128 ProducerDirtyDomain::Whole => DirtyDomain::WholeSpan(span_ref),
129 ProducerDirtyDomain::Cells(cells) => DirtyDomain::Cells(cells),
130 ProducerDirtyDomain::Regions(regions) => DirtyDomain::Regions(regions),
131 }
132}
133type PreparedFormulaBatches = Vec<FormulaIngestBatch>;
134type StagedFormulaBatches = Vec<(String, Vec<StagedFormulaEntry>)>;
135type FormulaPlaneMixedScheduleBuild = (
136 MixedSchedule,
137 BTreeMap<crate::formula_plane::runtime::FormulaSpanId, FormulaSpanRef>,
138 u64,
139 Vec<VertexId>,
140);
141
142type PlannedFormulaMaterialize = BTreeMap<String, Vec<(u32, u32, AstNodeId, DependencyPlanRow)>>;
143
144const COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH: usize = 8;
148
149#[derive(Debug, Clone, PartialEq)]
150pub(crate) enum ComputedWrite {
151 Cell {
152 seq: u64,
153 sheet_id: SheetId,
154 row0: u32,
155 col0: u32,
156 value: OverlayValue,
157 },
158 Rect {
159 seq: u64,
160 sheet_id: SheetId,
161 sr0: u32,
162 sc0: u32,
163 values: Vec<Vec<OverlayValue>>,
164 },
165}
166
167impl ComputedWrite {
168 #[inline]
169 pub(crate) fn seq(&self) -> u64 {
170 match self {
171 ComputedWrite::Cell { seq, .. } | ComputedWrite::Rect { seq, .. } => *seq,
172 }
173 }
174}
175
176#[derive(Debug, Default)]
177pub(crate) struct ComputedWriteBuffer {
178 writes: Vec<ComputedWrite>,
179 next_seq: u64,
180 estimated_bytes: usize,
181}
182
183impl ComputedWriteBuffer {
184 const ENTRY_BASE_BYTES: usize = 32;
185
186 #[inline]
187 pub(crate) fn is_empty(&self) -> bool {
188 self.writes.is_empty()
189 }
190
191 #[inline]
192 pub(crate) fn len(&self) -> usize {
193 self.writes.len()
194 }
195
196 #[inline]
197 pub(crate) fn estimated_bytes(&self) -> usize {
198 self.estimated_bytes
199 }
200
201 #[inline]
202 pub(crate) fn writes(&self) -> &[ComputedWrite] {
203 &self.writes
204 }
205
206 pub(crate) fn push_cell(
207 &mut self,
208 sheet_id: SheetId,
209 row0: u32,
210 col0: u32,
211 value: OverlayValue,
212 ) {
213 let seq = self.next_sequence();
214 self.estimated_bytes = self
215 .estimated_bytes
216 .saturating_add(Self::estimate_value_bytes(&value));
217 self.writes.push(ComputedWrite::Cell {
218 seq,
219 sheet_id,
220 row0,
221 col0,
222 value,
223 });
224 }
225
226 pub(crate) fn push_rect(
227 &mut self,
228 sheet_id: SheetId,
229 sr0: u32,
230 sc0: u32,
231 values: Vec<Vec<OverlayValue>>,
232 ) {
233 let seq = self.next_sequence();
234 let added = values
235 .iter()
236 .flat_map(|row| row.iter())
237 .map(Self::estimate_value_bytes)
238 .fold(0usize, usize::saturating_add);
239 self.estimated_bytes = self.estimated_bytes.saturating_add(added);
240 self.writes.push(ComputedWrite::Rect {
241 seq,
242 sheet_id,
243 sr0,
244 sc0,
245 values,
246 });
247 }
248
249 pub(crate) fn clear(&mut self) {
250 self.writes.clear();
251 self.estimated_bytes = 0;
252 }
253
254 fn take_writes(&mut self) -> Vec<ComputedWrite> {
255 self.estimated_bytes = 0;
256 std::mem::take(&mut self.writes)
257 }
258
259 fn next_sequence(&mut self) -> u64 {
260 let seq = self.next_seq;
261 self.next_seq = self.next_seq.wrapping_add(1);
262 seq
263 }
264
265 #[inline]
266 fn estimate_value_bytes(value: &OverlayValue) -> usize {
267 Self::ENTRY_BASE_BYTES.saturating_add(value.estimated_payload_bytes())
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
272struct ComputedWriteChunkKey {
273 sheet_id: SheetId,
274 col0: u32,
275 chunk_idx: usize,
276 chunk_start_row0: u32,
277}
278
279#[derive(Debug, Clone, PartialEq)]
280pub(crate) struct ComputedWriteChunkEntryPlan {
281 pub(crate) row_in_chunk: usize,
282 pub(crate) seq: u64,
283 pub(crate) value: OverlayValue,
284}
285
286#[derive(Debug, Clone, PartialEq, Eq)]
287pub(crate) enum ComputedWriteChunkPlanShape {
288 Point,
289 SparseOffsets {
290 entries: usize,
291 span_len: usize,
292 },
293 DenseRange {
294 start: usize,
295 len: usize,
296 },
297 RunRange {
298 start: usize,
299 len: usize,
300 runs: usize,
301 },
302}
303
304#[derive(Debug, Clone, PartialEq)]
305pub(crate) struct ComputedWriteChunkPlan {
306 pub(crate) sheet_id: SheetId,
307 pub(crate) col0: u32,
308 pub(crate) chunk_idx: usize,
309 pub(crate) chunk_start_row0: u32,
310 pub(crate) entries: Vec<ComputedWriteChunkEntryPlan>,
311 pub(crate) shape: ComputedWriteChunkPlanShape,
312}
313
314#[derive(Debug, Clone, Default, PartialEq)]
315pub(crate) struct ComputedWriteCoalescingPlan {
316 pub(crate) chunks: Vec<ComputedWriteChunkPlan>,
317 pub(crate) input_cells: usize,
318 pub(crate) coalesced_cells: usize,
319 pub(crate) overwritten_cells: usize,
320}
321
322impl ComputedWriteCoalescingPlan {
323 #[inline]
324 pub(crate) fn is_empty(&self) -> bool {
325 self.chunks.is_empty()
326 }
327}
328
329impl ComputedWriteChunkPlan {
330 fn from_group(
331 key: ComputedWriteChunkKey,
332 mut entries: Vec<ComputedWriteChunkEntryPlan>,
333 ) -> (Self, usize) {
334 entries.sort_by_key(|entry| (entry.row_in_chunk, entry.seq));
335 let input_len = entries.len();
336 let mut coalesced: Vec<ComputedWriteChunkEntryPlan> = Vec::with_capacity(input_len);
337 for entry in entries {
338 if let Some(prev) = coalesced.last_mut()
339 && prev.row_in_chunk == entry.row_in_chunk
340 {
341 *prev = entry;
342 continue;
343 }
344 coalesced.push(entry);
345 }
346 let overwritten = input_len.saturating_sub(coalesced.len());
347 let shape = Self::classify_shape(&coalesced);
348 (
349 Self {
350 sheet_id: key.sheet_id,
351 col0: key.col0,
352 chunk_idx: key.chunk_idx,
353 chunk_start_row0: key.chunk_start_row0,
354 entries: coalesced,
355 shape,
356 },
357 overwritten,
358 )
359 }
360
361 fn classify_shape(entries: &[ComputedWriteChunkEntryPlan]) -> ComputedWriteChunkPlanShape {
362 debug_assert!(!entries.is_empty());
363 if entries.len() == 1 {
364 return ComputedWriteChunkPlanShape::Point;
365 }
366
367 let start = entries[0].row_in_chunk;
368 let end = entries[entries.len() - 1].row_in_chunk;
369 let span_len = end.saturating_sub(start).saturating_add(1);
370 if span_len != entries.len() {
371 return ComputedWriteChunkPlanShape::SparseOffsets {
372 entries: entries.len(),
373 span_len,
374 };
375 }
376
377 let runs = Self::run_count(entries);
378 if runs < entries.len() {
379 ComputedWriteChunkPlanShape::RunRange {
380 start,
381 len: entries.len(),
382 runs,
383 }
384 } else {
385 ComputedWriteChunkPlanShape::DenseRange {
386 start,
387 len: entries.len(),
388 }
389 }
390 }
391
392 fn run_count(entries: &[ComputedWriteChunkEntryPlan]) -> usize {
393 let mut runs = 0usize;
394 let mut prev: Option<&OverlayValue> = None;
395 for entry in entries {
396 if prev != Some(&entry.value) {
397 runs = runs.saturating_add(1);
398 prev = Some(&entry.value);
399 }
400 }
401 runs
402 }
403}
404
405pub struct Engine<R> {
406 pub(crate) graph: DependencyGraph,
407 resolver: R,
408 pub config: EvalConfig,
409 workbook_load_limits: crate::engine::WorkbookLoadLimits,
410 clock: crate::timezone::SnapshotClock,
415 thread_pool: Option<Arc<rayon::ThreadPool>>,
416 pub recalc_epoch: u64,
417 snapshot_id: std::sync::atomic::AtomicU64,
418 topology_epoch: u64,
419 cached_static_schedule: Option<CachedScheduleEntry>,
420 spill_mgr: ShimSpillManager,
421 arrow_sheets: SheetStore,
423 has_edited: bool,
425 overlay_compactions: u64,
427
428 computed_overlay_bytes_estimate: usize,
430 computed_overlay_mirroring_disabled: bool,
431 pub(crate) force_materialize_range_views: bool,
434 row_bounds_cache: std::sync::RwLock<Option<RowBoundsCache>>,
436 used_axis_bounds_cache: std::sync::RwLock<Option<UsedAxisBoundsCache>>,
438 lookup_index_cache: LookupIndexCache,
439 source_cache: Arc<std::sync::RwLock<SourceCache>>,
440 staged_formulas: StagedFormulaMap,
442 row_visibility: FxHashMap<SheetId, RowVisibilityState>,
444 row_visibility_mask_cache: std::sync::RwLock<
446 FxHashMap<VisibilityMaskCacheKey, std::sync::Arc<arrow_array::BooleanArray>>,
447 >,
448 formula_parse_diagnostics: Vec<FormulaParseDiagnostic>,
450 last_formula_ingest_report: Option<FormulaIngestReport>,
452 formula_ingest_report_total: FormulaIngestReport,
454 formula_plane_cycle_member_span_demotions: u64,
464 formula_plane_capacity_bailouts: u64,
470 active_cancel_flag: Option<Arc<AtomicBool>>,
472
473 action_depth: u32,
478
479 last_virtual_dep_telemetry: VirtualDepTelemetry,
481 virtual_dep_fallback_activations: u64,
482
483 last_cycle_telemetry: CycleTelemetry,
485
486 pending_iterative_redirty: Vec<VertexId>,
500
501 iterative_state_values: FxHashMap<VertexId, LiteralValue>,
511
512 formula_plane_indexes_epoch_seen: u64,
517
518 #[cfg(test)]
519 last_formula_plane_span_eval_report: Option<SpanEvalReport>,
520}
521
522impl<R: EvaluationContext> Engine<R> {
527 pub(crate) fn ingest_pipeline(&mut self) -> crate::engine::ingest_pipeline::IngestPipeline<'_> {
528 self.graph.ingest_pipeline(&self.resolver)
529 }
530}
531
532pub struct EngineAction<'a, R>
533where
534 R: EvaluationContext,
535{
536 engine: &'a mut Engine<R>,
537 name: String,
538 log: Option<*mut crate::engine::ChangeLog>,
541 arrow_undo: Option<*mut crate::engine::ArrowUndoBatch>,
544 atomic_policy: bool,
546}
547
548impl<'a, R> EngineAction<'a, R>
549where
550 R: EvaluationContext,
551{
552 #[inline]
553 fn addr_for(&mut self, sheet: &str, row: u32, col: u32) -> crate::reference::CellRef {
554 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
555 let coord = crate::reference::Coord::from_excel(row, col, true, true);
556 crate::reference::CellRef::new(sheet_id, coord)
557 }
558
559 #[inline]
560 pub fn name(&self) -> &str {
561 &self.name
562 }
563
564 #[inline]
565 pub fn set_cell_value(
566 &mut self,
567 sheet: &str,
568 row: u32,
569 col: u32,
570 value: LiteralValue,
571 ) -> Result<(), crate::engine::EditorError> {
572 if self.log.is_some() {
573 let old_value = self.engine.read_cell_value(sheet, row, col);
574 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
575 let addr = self.addr_for(sheet, row, col);
576 let Some(log_ptr) = self.log else {
577 return Err(crate::engine::EditorError::TransactionFailed {
578 reason: "action_with_logger: missing ChangeLog".to_string(),
579 });
580 };
581
582 let old_comp = if self.arrow_undo.is_some() {
585 self.engine.read_computed_overlay_cell(sheet, row, col)
586 } else {
587 None
588 };
589
590 self.engine.demote_span_containing_cell_for_write(
591 addr.sheet_id,
592 addr.coord.row(),
593 addr.coord.col(),
594 )?;
595 if old_formula.is_none() {
596 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
597 }
598
599 let delta_old_sem = if old_formula.is_some() {
600 None
601 } else {
602 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
603 };
604
605 let start_len = unsafe { (&*log_ptr).len() };
606
607 let log = unsafe { &mut *log_ptr };
609 self.engine.edit_with_logger(log, |editor| {
610 editor.set_cell_value_with_old_state(
611 addr,
612 value.clone(),
613 old_value.clone(),
614 old_formula.clone(),
615 );
616 });
617 self.engine
618 .record_formula_plane_structural_change(StructuralScope::Cell {
619 sheet: addr.sheet_id,
620 row: addr.coord.row(),
621 col: addr.coord.col(),
622 });
623
624 if let Some(undo_ptr) = self.arrow_undo {
625 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
627 let undo = unsafe { &mut *undo_ptr };
628 self.engine
629 .record_spill_ops_into_arrow_undo(undo, new_events);
630
631 let new_comp = self.engine.read_computed_overlay_cell(sheet, row, col);
633 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
634 let row0 = row.saturating_sub(1);
635 let col0 = col.saturating_sub(1);
636 let delta_new_sem = Some(value.clone());
637 undo.record_delta_cell(sheet_id, row0, col0, delta_old_sem, delta_new_sem);
638 undo.record_computed_cell(sheet_id, row0, col0, old_comp, new_comp);
639 }
640 Ok(())
641 } else {
642 self.engine
643 .set_cell_value(sheet, row, col, value)
644 .map_err(crate::engine::EditorError::from)
645 }
646 }
647
648 #[inline]
649 pub fn set_cell_formula(
650 &mut self,
651 sheet: &str,
652 row: u32,
653 col: u32,
654 ast: ASTNode,
655 ) -> Result<(), crate::engine::EditorError> {
656 if self.log.is_some() {
657 let old_value = self.engine.read_cell_value(sheet, row, col);
658 let mut old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
659 let addr = self.addr_for(sheet, row, col);
660 let Some(log_ptr) = self.log else {
661 return Err(crate::engine::EditorError::TransactionFailed {
662 reason: "action_with_logger: missing ChangeLog".to_string(),
663 });
664 };
665
666 self.engine.demote_span_containing_cell_for_write(
667 addr.sheet_id,
668 addr.coord.row(),
669 addr.coord.col(),
670 )?;
671 if old_formula.is_none() {
672 old_formula = self.engine.read_cell_formula_ast(sheet, row, col);
673 }
674 let delta_old = if self.arrow_undo.is_some() {
675 if old_formula.is_some() {
676 None
677 } else {
678 Some(old_value.clone().unwrap_or(LiteralValue::Empty))
679 }
680 } else {
681 None
682 };
683 let start_len = unsafe { (&*log_ptr).len() };
684
685 let log = unsafe { &mut *log_ptr };
687 self.engine.edit_with_logger(log, |editor| {
688 editor.set_cell_formula_with_old_state(addr, ast.clone(), old_value, old_formula);
689 });
690 self.engine
691 .record_formula_plane_structural_change(StructuralScope::Cell {
692 sheet: addr.sheet_id,
693 row: addr.coord.row(),
694 col: addr.coord.col(),
695 });
696
697 if let Some(undo_ptr) = self.arrow_undo {
698 let new_events = &unsafe { (&*log_ptr).events() }[start_len..];
699 let undo = unsafe { &mut *undo_ptr };
700 self.engine
701 .record_spill_ops_into_arrow_undo(undo, new_events);
702 let delta_new: Option<LiteralValue> = None;
703 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
704 let row0 = row.saturating_sub(1);
705 let col0 = col.saturating_sub(1);
706 undo.record_delta_cell(sheet_id, row0, col0, delta_old, delta_new);
707 }
708 Ok(())
709 } else {
710 self.engine
711 .set_cell_formula(sheet, row, col, ast)
712 .map_err(crate::engine::EditorError::from)
713 }
714 }
715
716 #[inline]
717 pub fn set_row_hidden(
718 &mut self,
719 sheet: &str,
720 row_1based: u32,
721 hidden: bool,
722 source: RowVisibilitySource,
723 ) -> Result<(), crate::engine::EditorError> {
724 if self.log.is_some() {
725 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
726 let row0 = Engine::<R>::normalize_row_1based(row_1based)?;
727 let old_hidden = self
728 .engine
729 .row_visibility
730 .get(&sheet_id)
731 .map(|state| state.is_row_hidden(row0, Some(source)))
732 .unwrap_or(false);
733 if old_hidden == hidden {
734 return Ok(());
735 }
736
737 let _ = self
738 .engine
739 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
740
741 let Some(log_ptr) = self.log else {
742 return Err(crate::engine::EditorError::TransactionFailed {
743 reason: "action_with_logger: missing ChangeLog".to_string(),
744 });
745 };
746 unsafe { &mut *log_ptr }.record(crate::engine::ChangeEvent::SetRowVisibility {
747 sheet_id,
748 row0,
749 source,
750 old_hidden,
751 new_hidden: hidden,
752 });
753
754 Ok(())
755 } else {
756 self.engine
757 .set_row_hidden(sheet, row_1based, hidden, source)
758 }
759 }
760
761 #[inline]
762 pub fn set_rows_hidden(
763 &mut self,
764 sheet: &str,
765 start_row_1based: u32,
766 end_row_1based: u32,
767 hidden: bool,
768 source: RowVisibilitySource,
769 ) -> Result<(), crate::engine::EditorError> {
770 if self.log.is_some() {
771 let sheet_id = self.engine.ensure_known_sheet_id(sheet)?;
772 let (start_row0, end_row0) =
773 Engine::<R>::normalize_row_range_1based(start_row_1based, end_row_1based)?;
774
775 let Some(log_ptr) = self.log else {
776 return Err(crate::engine::EditorError::TransactionFailed {
777 reason: "action_with_logger: missing ChangeLog".to_string(),
778 });
779 };
780 let log = unsafe { &mut *log_ptr };
781
782 for row0 in start_row0..=end_row0 {
783 let old_hidden = self
784 .engine
785 .row_visibility
786 .get(&sheet_id)
787 .map(|state| state.is_row_hidden(row0, Some(source)))
788 .unwrap_or(false);
789 if old_hidden == hidden {
790 continue;
791 }
792
793 let _ = self
794 .engine
795 .set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source);
796
797 log.record(crate::engine::ChangeEvent::SetRowVisibility {
798 sheet_id,
799 row0,
800 source,
801 old_hidden,
802 new_hidden: hidden,
803 });
804 }
805
806 Ok(())
807 } else {
808 self.engine
809 .set_rows_hidden(sheet, start_row_1based, end_row_1based, hidden, source)
810 }
811 }
812
813 #[inline]
814 pub fn insert_rows(
815 &mut self,
816 sheet: &str,
817 before: u32,
818 count: u32,
819 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
820 if self.log.is_some() {
821 let Some(log_ptr) = self.log else {
822 return Err(crate::engine::EditorError::TransactionFailed {
823 reason: "action_atomic: missing ChangeLog".to_string(),
824 });
825 };
826
827 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
828 let before0 = before.saturating_sub(1);
829 let op = StructuralOp::InsertRows {
830 sheet_id,
831 before: before0,
832 count,
833 };
834 self.engine.demote_spans_for_structural_op(
835 op,
836 Engine::<R>::structural_row_region(sheet_id, before0),
837 )?;
838
839 let summary = {
841 let log = unsafe { &mut *log_ptr };
842 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
843 Ok(crate::engine::ShiftSummary::default());
844 self.engine.edit_with_logger(log, |editor| {
845 out = editor.insert_rows(sheet_id, before0, count);
846 });
847 out?
848 };
849
850 self.engine.ensure_arrow_sheet(sheet);
852 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
853 asheet.insert_rows(before0 as usize, count as usize);
854 }
855 self.engine
856 .shift_row_visibility_insert(sheet_id, before0, count);
857 if let Some(undo_ptr) = self.arrow_undo {
858 unsafe { &mut *undo_ptr }.record_insert_rows(sheet_id, before0, count);
859 }
860 Ok(summary)
861 } else {
862 self.engine.insert_rows(sheet, before, count)
863 }
864 }
865
866 #[inline]
867 pub fn delete_rows(
868 &mut self,
869 sheet: &str,
870 start: u32,
871 count: u32,
872 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
873 if self.atomic_policy {
874 return Err(crate::engine::EditorError::TransactionUnsupported {
875 reason:
876 "delete_rows is not supported inside atomic actions (conservative rollback policy)"
877 .to_string(),
878 });
879 }
880 self.engine.delete_rows(sheet, start, count)
881 }
882
883 #[inline]
884 pub fn insert_columns(
885 &mut self,
886 sheet: &str,
887 before: u32,
888 count: u32,
889 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
890 if self.log.is_some() {
891 let Some(log_ptr) = self.log else {
892 return Err(crate::engine::EditorError::TransactionFailed {
893 reason: "action_atomic: missing ChangeLog".to_string(),
894 });
895 };
896
897 let sheet_id = self.engine.graph.sheet_id_mut(sheet);
898 let before0 = before.saturating_sub(1);
899 let op = StructuralOp::InsertColumns {
900 sheet_id,
901 before: before0,
902 count,
903 };
904 self.engine.demote_spans_for_structural_op(
905 op,
906 Engine::<R>::structural_col_region(sheet_id, before0),
907 )?;
908
909 let summary = {
910 let log = unsafe { &mut *log_ptr };
911 let mut out: Result<crate::engine::ShiftSummary, crate::engine::EditorError> =
912 Ok(crate::engine::ShiftSummary::default());
913 self.engine.edit_with_logger(log, |editor| {
914 out = editor.insert_columns(sheet_id, before0, count);
915 });
916 out?
917 };
918
919 self.engine.ensure_arrow_sheet(sheet);
920 if let Some(asheet) = self.engine.arrow_sheets.sheet_mut(sheet) {
921 asheet.insert_columns(before0 as usize, count as usize);
922 }
923 if let Some(undo_ptr) = self.arrow_undo {
924 unsafe { &mut *undo_ptr }.record_insert_cols(sheet_id, before0, count);
925 }
926 Ok(summary)
927 } else {
928 self.engine.insert_columns(sheet, before, count)
929 }
930 }
931
932 #[inline]
933 pub fn delete_columns(
934 &mut self,
935 sheet: &str,
936 start: u32,
937 count: u32,
938 ) -> Result<crate::engine::ShiftSummary, crate::engine::EditorError> {
939 if self.atomic_policy {
940 return Err(crate::engine::EditorError::TransactionUnsupported {
941 reason:
942 "delete_columns is not supported inside atomic actions (conservative rollback policy)"
943 .to_string(),
944 });
945 }
946 self.engine.delete_columns(sheet, start, count)
947 }
948
949 #[inline]
954 pub fn action<T>(
955 &mut self,
956 name: impl AsRef<str>,
957 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
958 ) -> Result<T, crate::engine::EditorError> {
959 self.engine.action(name, f)
960 }
961}
962
963struct ActionDepthGuard<'a, R> {
964 engine: *mut Engine<R>,
965 _marker: std::marker::PhantomData<&'a mut Engine<R>>,
966}
967
968impl<'a, R> Drop for ActionDepthGuard<'a, R> {
969 fn drop(&mut self) {
970 unsafe {
973 let e = &mut *self.engine;
974 e.action_depth = e.action_depth.saturating_sub(1);
975 }
976 }
977}
978
979#[derive(Default)]
980struct SourceCache {
981 scalars: FxHashMap<(String, Option<u64>), LiteralValue>,
982 tables: FxHashMap<(String, Option<u64>), Arc<dyn crate::traits::Table>>,
983}
984
985#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
986struct VisibilityMaskCacheKey {
987 sheet_id: SheetId,
988 start_row0: u32,
989 end_row0: u32,
990 mode: VisibilityMaskMode,
991 version: u64,
992}
993
994#[derive(Debug, Clone, Copy, PartialEq, Eq)]
995enum StructuralScope {
996 Cell { sheet: SheetId, row: u32, col: u32 },
997 Region(Region),
998 Sheet(SheetId),
999 RemovedSheet(SheetId),
1000 AllSheets,
1001}
1002
1003struct SourceCacheSession {
1004 cache: Arc<std::sync::RwLock<SourceCache>>,
1005}
1006
1007impl Drop for SourceCacheSession {
1008 fn drop(&mut self) {
1009 if let Ok(mut g) = self.cache.write() {
1010 *g = SourceCache::default();
1011 }
1012 }
1013}
1014
1015#[derive(Debug)]
1016pub struct EvalResult {
1017 pub computed_vertices: usize,
1018 pub cycle_errors: usize,
1019 pub elapsed: std::time::Duration,
1020}
1021
1022#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
1027pub struct EngineBaselineStats {
1028 pub graph_vertex_count: usize,
1029 pub graph_formula_vertex_count: usize,
1030 pub graph_edge_count: usize,
1031 pub dirty_vertex_count: usize,
1032 pub evaluation_vertex_count: usize,
1033 pub formula_ast_root_count: usize,
1034 pub formula_ast_node_count: usize,
1035 pub staged_formula_count: usize,
1036 pub formula_plane_active_span_count: usize,
1037 pub formula_plane_producer_result_entries: usize,
1038 pub formula_plane_consumer_read_entries: usize,
1039 pub formula_plane_cycle_member_span_demotions: u64,
1042}
1043
1044#[derive(Debug, Clone, Default)]
1045pub struct VirtualDepTelemetry {
1046 pub candidate_vertices_total: usize,
1047 pub vdeps_vertices_total: usize,
1048 pub vdeps_edges_total: usize,
1049 pub builder_elapsed_ms_total: u128,
1050 pub schedule_virtual_passes: usize,
1051 pub schedule_static_passes: usize,
1052 pub schedule_cache_hits: usize,
1053 pub schedule_cache_misses: usize,
1054 pub reused_schedule_vertices_total: usize,
1055 pub replan_iterations: usize,
1056 pub changed_vdeps_total: usize,
1057 pub bailout_reason: Option<&'static str>,
1058 pub fallback_mode_activations: u64,
1059}
1060
1061#[derive(Debug, Clone, Default, PartialEq)]
1070pub struct CycleTelemetry {
1071 pub static_sccs: usize,
1073 pub phantom_sccs: usize,
1075 pub live_cycles_witnessed: usize,
1077 pub circ_cells_stamped: usize,
1079 pub settle_passes_total: usize,
1082 pub max_passes_single_scc: usize,
1084 pub iterated_sccs: usize,
1087 pub converged_sccs: usize,
1090 pub capped_sccs: usize,
1096 pub max_abs_delta_at_stop: f64,
1100 pub nan_converged: usize,
1103 pub elapsed_ms: u128,
1105}
1106
1107#[derive(Debug, Clone, Copy)]
1108struct ScheduleBuildMeta {
1109 candidate_vertices: usize,
1110 vdeps_vertices: usize,
1111 vdeps_edges: usize,
1112 builder_elapsed_ms: u128,
1113 used_virtual_schedule: bool,
1114 schedule_cache_hit: bool,
1115 schedule_cache_eligible: bool,
1116}
1117
1118#[derive(Debug, Clone)]
1119struct CachedScheduleEntry {
1120 topology_epoch: u64,
1121 candidate_vertices: Vec<VertexId>,
1122 schedule: crate::engine::scheduler::Schedule,
1123}
1124
1125type ScheduleBuildOutput = (
1126 crate::engine::scheduler::Schedule,
1127 FxHashMap<VertexId, Vec<VertexId>>,
1128 ScheduleBuildMeta,
1129);
1130
1131#[derive(Debug)]
1133pub struct RecalcPlan {
1134 schedule: crate::engine::Schedule,
1135 has_dynamic_refs: bool,
1136}
1137
1138impl RecalcPlan {
1139 pub fn layer_count(&self) -> usize {
1140 self.schedule.layers.len()
1141 }
1142
1143 pub fn has_dynamic_refs(&self) -> bool {
1144 self.has_dynamic_refs
1145 }
1146}
1147
1148#[cfg(test)]
1149pub(crate) mod criteria_mask_test_hooks {
1150 use std::cell::Cell;
1151
1152 thread_local! {
1153 static TEXT_SEGMENTS_TOTAL: Cell<usize> = const { Cell::new(0) };
1154 static TEXT_SEGMENTS_ALL_NULL: Cell<usize> = const { Cell::new(0) };
1155 }
1156
1157 pub fn reset_text_segment_counters() {
1158 TEXT_SEGMENTS_TOTAL.with(|c| c.set(0));
1159 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(0));
1160 }
1161
1162 pub fn text_segment_counters() -> (usize, usize) {
1163 let a = TEXT_SEGMENTS_TOTAL.with(|c| c.get());
1164 let b = TEXT_SEGMENTS_ALL_NULL.with(|c| c.get());
1165 (a, b)
1166 }
1167
1168 pub(crate) fn inc_total() {
1169 TEXT_SEGMENTS_TOTAL.with(|c| c.set(c.get() + 1));
1170 }
1171 pub(crate) fn inc_all_null() {
1172 TEXT_SEGMENTS_ALL_NULL.with(|c| c.set(c.get() + 1));
1173 }
1174}
1175
1176#[cfg(test)]
1177pub(crate) mod visibility_mask_test_hooks {
1178 use std::cell::Cell;
1179
1180 thread_local! {
1181 static HITS: Cell<usize> = const { Cell::new(0) };
1182 static MISSES: Cell<usize> = const { Cell::new(0) };
1183 static EVICTIONS: Cell<usize> = const { Cell::new(0) };
1184 }
1185
1186 pub fn reset() {
1187 HITS.with(|c| c.set(0));
1188 MISSES.with(|c| c.set(0));
1189 EVICTIONS.with(|c| c.set(0));
1190 }
1191
1192 pub fn counters() -> (usize, usize, usize) {
1193 let hits = HITS.with(|c| c.get());
1194 let misses = MISSES.with(|c| c.get());
1195 let evictions = EVICTIONS.with(|c| c.get());
1196 (hits, misses, evictions)
1197 }
1198
1199 pub(crate) fn inc_hit() {
1200 HITS.with(|c| c.set(c.get() + 1));
1201 }
1202
1203 pub(crate) fn inc_miss() {
1204 MISSES.with(|c| c.set(c.get() + 1));
1205 }
1206
1207 pub(crate) fn inc_eviction() {
1208 EVICTIONS.with(|c| c.set(c.get() + 1));
1209 }
1210}
1211
1212fn compute_criteria_mask(
1213 view: &RangeView<'_>,
1214 col_in_view: usize,
1215 pred: &crate::args::CriteriaPredicate,
1216) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
1217 use crate::compute_prelude::{boolean, cmp, concat_arrays};
1218 use arrow::compute::kernels::comparison::{ilike, nilike};
1219 use arrow_array::{
1220 Array as _, ArrayRef, BooleanArray, Float64Array, StringArray, builder::BooleanBuilder,
1221 };
1222
1223 fn apply_numeric_pred(
1225 chunk: &Float64Array,
1226 pred: &crate::args::CriteriaPredicate,
1227 ) -> Option<BooleanArray> {
1228 match pred {
1229 crate::args::CriteriaPredicate::Gt(n) => {
1230 cmp::gt(chunk, &Float64Array::new_scalar(*n)).ok()
1231 }
1232 crate::args::CriteriaPredicate::Ge(n) => {
1233 cmp::gt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
1234 }
1235 crate::args::CriteriaPredicate::Lt(n) => {
1236 cmp::lt(chunk, &Float64Array::new_scalar(*n)).ok()
1237 }
1238 crate::args::CriteriaPredicate::Le(n) => {
1239 cmp::lt_eq(chunk, &Float64Array::new_scalar(*n)).ok()
1240 }
1241 crate::args::CriteriaPredicate::Eq(v) => match v {
1242 formualizer_common::LiteralValue::Number(x) => {
1243 cmp::eq(chunk, &Float64Array::new_scalar(*x)).ok()
1244 }
1245 formualizer_common::LiteralValue::Int(i) => {
1246 cmp::eq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
1247 }
1248 _ => None,
1249 },
1250 crate::args::CriteriaPredicate::Ne(v) => match v {
1251 formualizer_common::LiteralValue::Number(x) => {
1252 cmp::neq(chunk, &Float64Array::new_scalar(*x)).ok()
1253 }
1254 formualizer_common::LiteralValue::Int(i) => {
1255 cmp::neq(chunk, &Float64Array::new_scalar(*i as f64)).ok()
1256 }
1257 _ => None,
1258 },
1259 _ => None,
1260 }
1261 }
1262
1263 let is_numeric_pred = matches!(
1265 pred,
1266 crate::args::CriteriaPredicate::Gt(_)
1267 | crate::args::CriteriaPredicate::Ge(_)
1268 | crate::args::CriteriaPredicate::Lt(_)
1269 | crate::args::CriteriaPredicate::Le(_)
1270 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Number(_))
1271 | crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Int(_))
1272 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Number(_))
1273 | crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Int(_))
1274 );
1275
1276 if is_numeric_pred {
1280 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1281 for res in view.numbers_slices() {
1282 let (_rs, _rl, cols_seg) = res.ok()?;
1283 if col_in_view < cols_seg.len() {
1284 let chunk = cols_seg[col_in_view].as_ref();
1285 let mask = apply_numeric_pred(chunk, pred)?;
1286 bool_parts.push(mask);
1287 }
1288 }
1289
1290 if bool_parts.is_empty() {
1291 return None;
1292 } else if bool_parts.len() == 1 {
1293 return Some(std::sync::Arc::new(bool_parts.remove(0)));
1294 } else {
1295 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1297 .iter()
1298 .map(|a| a as &dyn arrow_array::Array)
1299 .collect();
1300 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1301 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1302 return Some(std::sync::Arc::new(ba));
1303 }
1304 }
1305
1306 let (text_kind, text_pat, empty_special) = match pred {
1309 crate::args::CriteriaPredicate::Eq(formualizer_common::LiteralValue::Text(t)) => {
1310 (0u8, t.to_lowercase(), t.is_empty())
1311 }
1312 crate::args::CriteriaPredicate::Ne(formualizer_common::LiteralValue::Text(t)) => {
1313 (1u8, t.to_lowercase(), false)
1314 }
1315 crate::args::CriteriaPredicate::TextLike {
1316 pattern,
1317 case_insensitive,
1318 } => {
1319 let p = if *case_insensitive {
1320 pattern.to_lowercase()
1321 } else {
1322 pattern.clone()
1323 };
1324 (2u8, p.replace('*', "%").replace('?', "_"), false)
1325 }
1326 _ => return None,
1327 };
1328
1329 let ne_matches_blank = text_kind == 1 && !text_pat.is_empty();
1330 let pat = StringArray::new_scalar(text_pat);
1331 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1332
1333 for res in view.iter_row_chunks() {
1334 let cs = res.ok()?;
1335 if cs.row_len == 0 {
1336 continue;
1337 }
1338 #[cfg(test)]
1339 criteria_mask_test_hooks::inc_total();
1340
1341 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
1342 if col_in_view >= slices.len() {
1343 return None;
1344 }
1345
1346 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
1347 let seg = match seg_opt {
1348 Some(s) => s,
1349 None => {
1350 #[cfg(test)]
1351 criteria_mask_test_hooks::inc_all_null();
1352 if (text_kind == 0 && empty_special) || ne_matches_blank {
1353 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
1355 bb.append_n(cs.row_len, true);
1356 bool_parts.push(bb.finish());
1357 } else {
1358 bool_parts.push(BooleanArray::new_null(cs.row_len));
1360 }
1361 continue;
1362 }
1363 };
1364
1365 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
1366 let mut m = match text_kind {
1367 0 => ilike(seg_sa, &pat).ok()?,
1368 1 => nilike(seg_sa, &pat).ok()?,
1369 2 => ilike(seg_sa, &pat).ok()?,
1370 _ => return None,
1371 };
1372
1373 if ((text_kind == 0 && empty_special) || ne_matches_blank) && seg_sa.null_count() > 0 {
1379 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
1381 for i in 0..seg_sa.len() {
1382 bb.append_value(seg_sa.is_null(i));
1383 }
1384 let nulls = bb.finish();
1385 m = boolean::or_kleene(&m, &nulls).ok()?;
1386 }
1387
1388 bool_parts.push(m);
1389 }
1390
1391 if bool_parts.is_empty() {
1392 None
1393 } else if bool_parts.len() == 1 {
1394 Some(std::sync::Arc::new(bool_parts.remove(0)))
1395 } else {
1396 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1397 .iter()
1398 .map(|a| a as &dyn arrow_array::Array)
1399 .collect();
1400 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1401 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1402 Some(std::sync::Arc::new(ba))
1403 }
1404}
1405
1406#[derive(Debug, Clone)]
1407pub struct LayerInfo {
1408 pub vertex_count: usize,
1409 pub parallel_eligible: bool,
1410 pub sample_cells: Vec<String>, }
1412
1413#[derive(Debug, Clone)]
1414pub struct EvalPlan {
1415 pub total_vertices_to_evaluate: usize,
1416 pub layers: Vec<LayerInfo>,
1417 pub cycles_detected: usize,
1418 pub dirty_count: usize,
1419 pub volatile_count: usize,
1420 pub parallel_enabled: bool,
1421 pub estimated_parallel_layers: usize,
1422 pub target_cells: Vec<String>,
1423}
1424
1425impl<R> Engine<R>
1426where
1427 R: EvaluationContext,
1428{
1429 pub fn new(resolver: R, config: EvalConfig) -> Self {
1436 if let Err(msg) = config.cycle.validate() {
1437 panic!("invalid CycleConfig: {msg}");
1438 }
1439 crate::builtins::load_builtins();
1440
1441 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1442 #[cfg(feature = "system-clock")]
1443 {
1444 Arc::new(crate::timezone::SystemClock::new(
1445 crate::timezone::TimeZoneSpec::default(),
1446 ))
1447 }
1448 #[cfg(not(feature = "system-clock"))]
1449 {
1450 Arc::new(crate::timezone::FixedClock::new(
1451 chrono::DateTime::UNIX_EPOCH,
1452 crate::timezone::TimeZoneSpec::Utc,
1453 ))
1454 }
1455 });
1456
1457 let thread_pool = if config.enable_parallel {
1459 let mut builder = ThreadPoolBuilder::new();
1460 if let Some(max_threads) = config.max_threads {
1461 builder = builder.num_threads(max_threads);
1462 }
1463
1464 match builder.build() {
1465 Ok(pool) => Some(Arc::new(pool)),
1466 Err(_) => {
1467 None
1469 }
1470 }
1471 } else {
1472 None
1473 };
1474
1475 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1476 let mut engine = Self {
1477 graph: DependencyGraph::new_with_config(config.clone()),
1478 resolver,
1479 config,
1480 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1481 clock: crate::timezone::SnapshotClock::new(clock),
1482 thread_pool,
1483 recalc_epoch: 0,
1484 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1485 topology_epoch: 0,
1486 cached_static_schedule: None,
1487 spill_mgr: ShimSpillManager::default(),
1488 arrow_sheets: SheetStore::default(),
1489 has_edited: false,
1490 overlay_compactions: 0,
1491 computed_overlay_bytes_estimate: 0,
1492 computed_overlay_mirroring_disabled: false,
1493 force_materialize_range_views: false,
1494 row_bounds_cache: std::sync::RwLock::new(None),
1495 used_axis_bounds_cache: std::sync::RwLock::new(None),
1496 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1497 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1498 staged_formulas: std::collections::HashMap::new(),
1499 row_visibility: FxHashMap::default(),
1500 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1501 formula_parse_diagnostics: Vec::new(),
1502 last_formula_ingest_report: None,
1503 formula_ingest_report_total: FormulaIngestReport::default(),
1504 formula_plane_cycle_member_span_demotions: 0,
1505 formula_plane_capacity_bailouts: 0,
1506 active_cancel_flag: None,
1507 action_depth: 0,
1508 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1509 virtual_dep_fallback_activations: 0,
1510 last_cycle_telemetry: CycleTelemetry::default(),
1511 pending_iterative_redirty: Vec::new(),
1512 iterative_state_values: FxHashMap::default(),
1513 formula_plane_indexes_epoch_seen: 0,
1514 #[cfg(test)]
1515 last_formula_plane_span_eval_report: None,
1516 };
1517 engine.config.arrow_storage_enabled = true;
1519 engine.config.delta_overlay_enabled = true;
1520 engine.config.write_formula_overlay_enabled = true;
1521 let default_sheet = engine.graph.default_sheet_name().to_string();
1522 engine.ensure_arrow_sheet(&default_sheet);
1523 engine
1524 }
1525
1526 pub fn with_thread_pool(
1531 resolver: R,
1532 config: EvalConfig,
1533 thread_pool: Arc<rayon::ThreadPool>,
1534 ) -> Self {
1535 if let Err(msg) = config.cycle.validate() {
1536 panic!("invalid CycleConfig: {msg}");
1537 }
1538 crate::builtins::load_builtins();
1539 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1540 #[cfg(feature = "system-clock")]
1541 {
1542 Arc::new(crate::timezone::SystemClock::new(
1543 crate::timezone::TimeZoneSpec::default(),
1544 ))
1545 }
1546 #[cfg(not(feature = "system-clock"))]
1547 {
1548 Arc::new(crate::timezone::FixedClock::new(
1549 chrono::DateTime::UNIX_EPOCH,
1550 crate::timezone::TimeZoneSpec::Utc,
1551 ))
1552 }
1553 });
1554 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1555 let mut engine = Self {
1556 graph: DependencyGraph::new_with_config(config.clone()),
1557 resolver,
1558 config,
1559 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1560 clock: crate::timezone::SnapshotClock::new(clock),
1561 thread_pool: Some(thread_pool),
1562 recalc_epoch: 0,
1563 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1564 topology_epoch: 0,
1565 cached_static_schedule: None,
1566 spill_mgr: ShimSpillManager::default(),
1567 arrow_sheets: SheetStore::default(),
1568 has_edited: false,
1569 overlay_compactions: 0,
1570 computed_overlay_bytes_estimate: 0,
1571 computed_overlay_mirroring_disabled: false,
1572 force_materialize_range_views: false,
1573 row_bounds_cache: std::sync::RwLock::new(None),
1574 used_axis_bounds_cache: std::sync::RwLock::new(None),
1575 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1576 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1577 staged_formulas: std::collections::HashMap::new(),
1578 row_visibility: FxHashMap::default(),
1579 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1580 formula_parse_diagnostics: Vec::new(),
1581 last_formula_ingest_report: None,
1582 formula_ingest_report_total: FormulaIngestReport::default(),
1583 formula_plane_cycle_member_span_demotions: 0,
1584 formula_plane_capacity_bailouts: 0,
1585 active_cancel_flag: None,
1586 action_depth: 0,
1587 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1588 virtual_dep_fallback_activations: 0,
1589 last_cycle_telemetry: CycleTelemetry::default(),
1590 pending_iterative_redirty: Vec::new(),
1591 iterative_state_values: FxHashMap::default(),
1592 formula_plane_indexes_epoch_seen: 0,
1593 #[cfg(test)]
1594 last_formula_plane_span_eval_report: None,
1595 };
1596 engine.config.arrow_storage_enabled = true;
1598 engine.config.delta_overlay_enabled = true;
1599 engine.config.write_formula_overlay_enabled = true;
1600 let default_sheet = engine.graph.default_sheet_name().to_string();
1601 engine.ensure_arrow_sheet(&default_sheet);
1602 engine
1603 }
1604
1605 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
1606 &self.workbook_load_limits
1607 }
1608
1609 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
1610 self.workbook_load_limits = limits;
1611 }
1612
1613 fn clear_source_cache(&self) {
1614 if let Ok(mut g) = self.source_cache.write() {
1615 *g = SourceCache::default();
1616 }
1617 }
1618
1619 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
1620 &self.last_virtual_dep_telemetry
1621 }
1622
1623 pub fn last_cycle_telemetry(&self) -> &CycleTelemetry {
1627 &self.last_cycle_telemetry
1628 }
1629
1630 fn begin_evaluation_request(&mut self) {
1634 self.last_cycle_telemetry = CycleTelemetry::default();
1635 self.pending_iterative_redirty.clear();
1638 self.clock.refresh();
1642 }
1643
1644 fn redirty_for_next_recalc(&mut self) {
1651 self.graph.redirty_volatiles();
1652 let pending = std::mem::take(&mut self.pending_iterative_redirty);
1653 self.iterative_state_values.clear();
1659 for &vertex in &pending {
1660 if !self.graph.vertex_exists(vertex) {
1661 continue;
1662 }
1663 if let Some(cell) = self.graph.get_cell_ref(vertex) {
1664 let sheet_name = self.graph.sheet_name(cell.sheet_id);
1665 if let Some(value) =
1666 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
1667 && !matches!(value, LiteralValue::Empty)
1668 {
1669 self.iterative_state_values.insert(vertex, value);
1670 }
1671 }
1672 }
1673 if !pending.is_empty() {
1674 self.graph.redirty_iterative_members(&pending);
1675 }
1676 }
1677
1678 pub fn virtual_dep_fallback_activations(&self) -> u64 {
1679 self.virtual_dep_fallback_activations
1680 }
1681
1682 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
1683 self.lookup_index_cache.report()
1684 }
1685
1686 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
1687 let start_row = view.start_row();
1688 let end_row = view.end_row();
1689 let start_col = view.start_col();
1690 let end_col = view.end_col();
1691 for row in start_row..=end_row {
1692 let Ok(row_u32) = u32::try_from(row) else {
1693 return true;
1694 };
1695 for col in start_col..=end_col {
1696 let Ok(col_u32) = u32::try_from(col) else {
1697 return true;
1698 };
1699 let cell_ref = self
1700 .graph
1701 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
1702 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
1703 && self.graph.is_volatile(*vertex_id)
1704 {
1705 return true;
1706 }
1707 }
1708 }
1709 false
1710 }
1711
1712 fn build_lookup_index_impl(
1713 &self,
1714 view: &RangeView<'_>,
1715 axis: LookupAxis,
1716 ) -> Option<Arc<LookupIndex>> {
1717 let (rows, cols) = view.dims();
1718 if rows == 0 || cols == 0 {
1719 self.lookup_index_cache.note_skipped_tiny();
1720 return None;
1721 }
1722 let len = match axis {
1723 LookupAxis::ColumnInView(col) => {
1724 if col >= cols {
1725 self.lookup_index_cache.note_skipped_tiny();
1726 return None;
1727 }
1728 rows
1729 }
1730 LookupAxis::RowInView(row) => {
1731 if row >= rows {
1732 self.lookup_index_cache.note_skipped_tiny();
1733 return None;
1734 }
1735 cols
1736 }
1737 };
1738 if len < 64 {
1739 self.lookup_index_cache.note_skipped_tiny();
1740 return None;
1741 }
1742
1743 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
1744 let key = LookupIndexKey {
1745 sheet_id,
1746 start_row: u32::try_from(view.start_row()).ok()?,
1747 start_col: u32::try_from(view.start_col()).ok()?,
1748 end_row: u32::try_from(view.end_row()).ok()?,
1749 end_col: u32::try_from(view.end_col()).ok()?,
1750 axis,
1751 snapshot_id: self.data_snapshot_id(),
1752 };
1753 if let Some(index) = self.lookup_index_cache.get(&key) {
1754 return Some(index);
1755 }
1756 if self
1757 .lookup_index_cache
1758 .would_exceed_cap(estimate_bytes(len, 0))
1759 {
1760 self.lookup_index_cache.note_skipped_cap();
1761 return None;
1762 }
1763 if !self.lookup_index_cache.should_build(key) {
1764 return None;
1765 }
1766 if self.lookup_index_cache.is_known_volatile(&key) {
1767 self.lookup_index_cache.note_skipped_volatile();
1768 return None;
1769 }
1770 if self.lookup_view_contains_volatile(view, sheet_id) {
1771 self.lookup_index_cache.note_volatile_key(key);
1772 self.lookup_index_cache.note_skipped_volatile();
1773 return None;
1774 }
1775 match LookupIndex::build(view, axis).ok()? {
1776 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
1777 BuildOutcome::ErrorInLookupAxis => {
1778 self.lookup_index_cache.note_skipped_error();
1779 None
1780 }
1781 BuildOutcome::Degenerate => {
1782 self.lookup_index_cache.note_skipped_tiny();
1783 None
1784 }
1785 }
1786 }
1787
1788 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
1789 if !self.config.enable_virtual_dep_telemetry {
1790 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
1791 fallback_mode_activations: self.virtual_dep_fallback_activations,
1792 ..VirtualDepTelemetry::default()
1793 };
1794 }
1795 }
1796
1797 fn source_cache_session(&self) -> SourceCacheSession {
1798 self.clear_source_cache();
1799 SourceCacheSession {
1800 cache: self.source_cache.clone(),
1801 }
1802 }
1803
1804 fn resolve_source_scalar_cached(
1805 &self,
1806 name: &str,
1807 version: Option<u64>,
1808 ) -> Result<LiteralValue, ExcelError> {
1809 let key = (name.to_string(), version);
1810 if let Ok(mut g) = self.source_cache.write() {
1811 if let Some(v) = g.scalars.get(&key) {
1812 return Ok(v.clone());
1813 }
1814
1815 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
1816 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1817 ExcelError::new(ExcelErrorKind::Ref)
1818 .with_message(format!("Unresolved source scalar: {name}"))
1819 } else {
1820 err
1821 }
1822 })?;
1823 g.scalars.insert(key, v.clone());
1824 Ok(v)
1825 } else {
1826 self.resolver.resolve_source_scalar(name).map_err(|err| {
1827 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1828 ExcelError::new(ExcelErrorKind::Ref)
1829 .with_message(format!("Unresolved source scalar: {name}"))
1830 } else {
1831 err
1832 }
1833 })
1834 }
1835 }
1836
1837 fn resolve_source_table_cached(
1838 &self,
1839 name: &str,
1840 version: Option<u64>,
1841 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
1842 let key = (name.to_string(), version);
1843 if let Ok(mut g) = self.source_cache.write() {
1844 if let Some(t) = g.tables.get(&key) {
1845 return Ok(t.clone());
1846 }
1847
1848 let t = self.resolver.resolve_source_table(name).map_err(|err| {
1849 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1850 ExcelError::new(ExcelErrorKind::Ref)
1851 .with_message(format!("Unresolved source table: {name}"))
1852 } else {
1853 err
1854 }
1855 })?;
1856 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
1857 g.tables.insert(key, t.clone());
1858 Ok(t)
1859 } else {
1860 self.resolver
1861 .resolve_source_table(name)
1862 .map_err(|err| {
1863 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1864 ExcelError::new(ExcelErrorKind::Ref)
1865 .with_message(format!("Unresolved source table: {name}"))
1866 } else {
1867 err
1868 }
1869 })
1870 .map(Arc::from)
1871 }
1872 }
1873
1874 fn source_table_to_range_view(
1875 &self,
1876 table: &dyn crate::traits::Table,
1877 spec: &Option<formualizer_parse::parser::TableSpecifier>,
1878 ) -> Result<RangeView<'static>, ExcelError> {
1879 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1880
1881 let owned = match spec {
1882 Some(TableSpecifier::Column(c)) => {
1883 let c = c.trim();
1884 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
1885 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1886 "Complex structured references not yet supported".to_string(),
1887 ));
1888 }
1889 table.get_column(c)?.materialise().into_owned()
1890 }
1891 Some(TableSpecifier::ColumnRange(start, end)) => {
1892 let cols = table.columns();
1893 let start = start.trim();
1894 let end = end.trim();
1895 let start_key = start.to_lowercase();
1896 let end_key = end.to_lowercase();
1897 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1898 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1899 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1900 if si > ei {
1901 std::mem::swap(&mut si, &mut ei);
1902 }
1903 let h = table.data_height();
1904 let w = ei - si + 1;
1905 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1906 for (offset, ci) in (si..=ei).enumerate() {
1907 let cname = &cols[ci];
1908 let col_range = table.get_column(cname)?;
1909 let (rh, _) = col_range.dimensions();
1910 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1911 row[offset] = col_range.get(r, 0)?;
1912 }
1913 }
1914 rows
1915 } else {
1916 return Err(ExcelError::new(ExcelErrorKind::Ref)
1917 .with_message("Column range refers to unknown column(s)".to_string()));
1918 }
1919 }
1920 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
1921 | Some(TableSpecifier::Headers) => table
1922 .headers_row()
1923 .map(|r| r.materialise().into_owned())
1924 .unwrap_or_default(),
1925 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
1926 | Some(TableSpecifier::Totals) => table
1927 .totals_row()
1928 .map(|r| r.materialise().into_owned())
1929 .unwrap_or_default(),
1930 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
1931 table
1932 .data_body()
1933 .map(|r| r.materialise().into_owned())
1934 .unwrap_or_default()
1935 }
1936 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
1937 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1938 if let Some(h) = table.headers_row() {
1939 out.extend(h.iter_rows());
1940 }
1941 if let Some(body) = table.data_body() {
1942 out.extend(body.iter_rows());
1943 }
1944 if let Some(tr) = table.totals_row() {
1945 out.extend(tr.iter_rows());
1946 }
1947 out
1948 }
1949 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
1950 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1951 "@ (This Row) requires table-aware context; not yet supported".to_string(),
1952 ));
1953 }
1954 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
1955 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1956 .with_message("Complex structured references not yet supported".to_string()));
1957 }
1958 None => {
1959 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1960 .with_message("Table reference without specifier is unsupported".to_string()));
1961 }
1962 };
1963
1964 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
1965 }
1966
1967 pub fn default_sheet_id(&self) -> SheetId {
1968 self.graph.default_sheet_id()
1969 }
1970
1971 pub fn default_sheet_name(&self) -> &str {
1972 self.graph.default_sheet_name()
1973 }
1974
1975 pub fn set_workbook_seed(&mut self, seed: u64) {
1977 self.config.workbook_seed = seed;
1978 }
1979
1980 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
1982 self.config.volatile_level = level;
1983 }
1984
1985 pub fn set_deterministic_mode(
1987 &mut self,
1988 mode: crate::engine::DeterministicMode,
1989 ) -> Result<(), ExcelError> {
1990 let clock = mode.build_clock()?;
1991 self.config.deterministic_mode = mode;
1992 self.clock = crate::timezone::SnapshotClock::new(clock);
1993 Ok(())
1994 }
1995
1996 pub fn set_clock(&mut self, clock: Arc<dyn crate::timezone::ClockProvider>) {
2004 self.clock = crate::timezone::SnapshotClock::new(clock);
2005 }
2006
2007 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
2008 self.config.deterministic_mode.validate()
2009 }
2010
2011 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
2012 self.graph.sheet_id(name)
2013 }
2014
2015 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
2016 self.add_sheet(name)
2017 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
2018 }
2019
2020 pub fn sheet_name(&self, id: SheetId) -> &str {
2021 self.graph.sheet_name(id)
2022 }
2023
2024 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
2025 let id = self.graph.add_sheet(name)?;
2026 self.ensure_arrow_sheet(name);
2027 self.mark_topology_edited();
2032 Ok(id)
2033 }
2034
2035 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
2036 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
2037 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
2038 })?;
2039 self.demote_spans_preserving_computed_overlays(source_id, Region::whole_sheet(source_id))
2042 .map_err(Self::editor_error_to_excel)?;
2043 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
2044
2045 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
2046 let mut copied_sheet = source_sheet;
2047 copied_sheet.name = Arc::<str>::from(new_name);
2048 self.arrow_sheets.sheets.push(copied_sheet);
2049 } else {
2050 self.ensure_arrow_sheet(new_name);
2051 }
2052
2053 self.clear_all_computed_overlays();
2054 self.mark_all_formula_vertices_dirty();
2055 self.mark_topology_edited();
2056 Ok(new_id)
2057 }
2058
2059 fn ensure_arrow_sheet(&mut self, name: &str) {
2060 if self.arrow_sheets.sheet(name).is_some() {
2061 return;
2062 }
2063 self.arrow_sheets
2064 .sheets
2065 .push(crate::arrow_store::ArrowSheet {
2066 name: std::sync::Arc::<str>::from(name),
2067 columns: Vec::new(),
2068 nrows: 0,
2069 chunk_starts: Vec::new(),
2070 chunk_rows: 32 * 1024,
2071 });
2072 }
2073
2074 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
2075 let name = self.graph.sheet_name(sheet_id).to_string();
2076 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
2080 .map_err(Self::editor_error_to_excel)?;
2081 self.graph.remove_sheet(sheet_id)?;
2082 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
2083 self.clear_all_computed_overlays();
2084 self.mark_all_formula_vertices_dirty();
2085 self.staged_formulas.remove(&name);
2086 if self.row_visibility.remove(&sheet_id).is_some() {
2087 self.invalidate_row_visibility_mask_cache();
2088 }
2089 self.record_formula_plane_structural_change(StructuralScope::RemovedSheet(sheet_id));
2090 self.mark_topology_edited();
2091 Ok(())
2092 }
2093
2094 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
2096 if let Some(asheet) = self
2097 .arrow_sheets
2098 .sheets
2099 .iter_mut()
2100 .find(|s| s.name.as_ref() == target_name)
2101 {
2102 asheet.name = std::sync::Arc::<str>::from(new_name);
2103 return true;
2104 }
2105 false
2106 }
2107
2108 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
2109 let old_name = self.graph.sheet_name(sheet_id).to_string();
2110
2111 self.rename_sheet_in_arrow_store(&old_name, new_name);
2114
2115 match self.graph.rename_sheet(sheet_id, new_name) {
2117 Ok(_) => {
2118 self.rename_staged_formula_sheet(&old_name, new_name);
2119 let sheet_vertices: Vec<VertexId> =
2121 self.graph.vertices_in_sheet(sheet_id).collect();
2122 for v_id in sheet_vertices {
2123 self.graph.mark_vertex_dirty(v_id);
2124 }
2125 self.mark_topology_edited();
2129 Ok(())
2130 }
2131 Err(e) => {
2132 self.rename_sheet_in_arrow_store(new_name, &old_name);
2134 Err(e)
2135 }
2136 }
2137 }
2138
2139 pub fn named_ranges_iter(
2140 &self,
2141 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
2142 self.graph.named_ranges_iter()
2143 }
2144
2145 pub fn sheet_named_ranges_iter(
2146 &self,
2147 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
2148 self.graph.sheet_named_ranges_iter()
2149 }
2150
2151 pub fn resolve_name_entry(
2152 &self,
2153 name: &str,
2154 current_sheet: SheetId,
2155 ) -> Option<&crate::engine::named_range::NamedRange> {
2156 self.graph.resolve_name_entry(name, current_sheet)
2157 }
2158
2159 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
2160 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
2161
2162 for (name, named) in self.graph.named_ranges_iter() {
2163 out.push(crate::engine::named_range::NamedRangeSnapshot {
2164 name: name.clone(),
2165 scope: NameScope::Workbook,
2166 definition: named.definition.clone(),
2167 });
2168 }
2169
2170 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
2171 out.push(crate::engine::named_range::NamedRangeSnapshot {
2172 name: name.clone(),
2173 scope: NameScope::Sheet(*sheet_id),
2174 definition: named.definition.clone(),
2175 });
2176 }
2177
2178 out.sort_by(|a, b| {
2179 let a_scope = match a.scope {
2180 NameScope::Workbook => (0u8, 0u32),
2181 NameScope::Sheet(id) => (1u8, u32::from(id)),
2182 };
2183 let b_scope = match b.scope {
2184 NameScope::Workbook => (0u8, 0u32),
2185 NameScope::Sheet(id) => (1u8, u32::from(id)),
2186 };
2187 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
2188 });
2189
2190 out
2191 }
2192
2193 pub fn named_ranges_snapshot_for_sheet(
2194 &self,
2195 sheet_id: SheetId,
2196 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
2197 self.named_ranges_snapshot()
2198 .into_iter()
2199 .filter(|entry| match entry.scope {
2200 NameScope::Workbook => true,
2201 NameScope::Sheet(id) => id == sheet_id,
2202 })
2203 .collect()
2204 }
2205
2206 pub fn define_name(
2207 &mut self,
2208 name: &str,
2209 definition: NamedDefinition,
2210 scope: NameScope,
2211 ) -> Result<(), ExcelError> {
2212 self.invalidate_formula_plane_spans_for_name(name)?;
2218 self.graph.define_name(name, definition, scope)?;
2219 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2220 self.mark_topology_edited();
2221 Ok(())
2222 }
2223
2224 pub fn update_name(
2225 &mut self,
2226 name: &str,
2227 definition: NamedDefinition,
2228 scope: NameScope,
2229 ) -> Result<(), ExcelError> {
2230 self.invalidate_formula_plane_spans_for_name(name)?;
2236 self.graph.update_name(name, definition, scope)?;
2237 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2238 self.mark_topology_edited();
2239 Ok(())
2240 }
2241
2242 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
2243 self.invalidate_formula_plane_spans_for_name(name)?;
2247 self.graph.delete_name(name, scope)?;
2248 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2249 self.mark_topology_edited();
2250 Ok(())
2251 }
2252
2253 fn invalidate_formula_plane_spans_for_name(&mut self, name: &str) -> Result<(), ExcelError> {
2260 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
2261 return Ok(());
2262 }
2263 let regions: Vec<Region> = {
2264 let authority = self.graph.formula_authority();
2265 authority
2266 .plane
2267 .name_dependent_span_refs(name)
2268 .into_iter()
2269 .filter_map(|span_ref| authority.plane.spans.get(span_ref))
2270 .map(|span| Region::from_domain(span.result_region.domain()))
2271 .collect()
2272 };
2273 for region in regions {
2274 self.demote_spans_preserving_computed_overlays(region.sheet_id(), region)
2275 .map_err(Self::editor_error_to_excel)?;
2276 }
2277 Ok(())
2278 }
2279
2280 pub fn define_table(
2281 &mut self,
2282 name: &str,
2283 range: crate::reference::RangeRef,
2284 header_row: bool,
2285 headers: Vec<String>,
2286 totals_row: bool,
2287 ) -> Result<(), ExcelError> {
2288 self.graph
2289 .define_table(name, range, header_row, headers, totals_row)?;
2290 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2291 self.mark_topology_edited();
2292 Ok(())
2293 }
2294
2295 pub fn define_source_scalar(
2296 &mut self,
2297 name: &str,
2298 version: Option<u64>,
2299 ) -> Result<(), ExcelError> {
2300 self.graph.define_source_scalar(name, version)?;
2301 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2302 self.mark_topology_edited();
2303 Ok(())
2304 }
2305
2306 pub fn define_source_table(
2307 &mut self,
2308 name: &str,
2309 version: Option<u64>,
2310 ) -> Result<(), ExcelError> {
2311 self.graph.define_source_table(name, version)?;
2312 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2313 self.mark_topology_edited();
2314 Ok(())
2315 }
2316
2317 pub fn set_source_scalar_version(
2318 &mut self,
2319 name: &str,
2320 version: Option<u64>,
2321 ) -> Result<(), ExcelError> {
2322 self.graph.set_source_scalar_version(name, version)?;
2323 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2324 Ok(())
2325 }
2326
2327 pub fn set_source_table_version(
2328 &mut self,
2329 name: &str,
2330 version: Option<u64>,
2331 ) -> Result<(), ExcelError> {
2332 self.graph.set_source_table_version(name, version)?;
2333 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2334 Ok(())
2335 }
2336
2337 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
2338 self.graph.invalidate_source(name)?;
2339 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2340 Ok(())
2341 }
2342
2343 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
2344 self.graph.get_value(vertex)
2345 }
2346
2347 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
2348 self.graph.get_cell_value(sheet, row, col)
2349 }
2350
2351 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
2352 self.graph.get_vertex_for_cell(cell)
2353 }
2354
2355 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
2356 self.graph.get_evaluation_vertices()
2357 }
2358
2359 pub fn baseline_stats(&self) -> EngineBaselineStats {
2361 let graph = self.graph.baseline_stats();
2362 let formula_authority = self.graph.formula_authority();
2363 EngineBaselineStats {
2364 graph_vertex_count: graph.graph_vertex_count,
2365 graph_formula_vertex_count: graph.graph_formula_vertex_count,
2366 graph_edge_count: graph.graph_edge_count,
2367 dirty_vertex_count: graph.dirty_vertex_count,
2368 evaluation_vertex_count: graph.evaluation_vertex_count,
2369 formula_ast_root_count: graph.formula_ast_root_count,
2370 formula_ast_node_count: graph.formula_ast_node_count,
2371 staged_formula_count: self.staged_formula_count(),
2372 formula_plane_active_span_count: formula_authority.active_span_count(),
2373 formula_plane_producer_result_entries: formula_authority.producer_results.len(),
2374 formula_plane_consumer_read_entries: formula_authority.consumer_reads.len(),
2375 formula_plane_cycle_member_span_demotions: self
2376 .formula_plane_cycle_member_span_demotions,
2377 }
2378 }
2379
2380 #[cfg(test)]
2381 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
2382 self.used_axis_bounds_cache
2383 .read()
2384 .ok()
2385 .and_then(|guard| {
2386 guard.as_ref().map(|cache| {
2387 (
2388 cache.row_hits.load(Ordering::Relaxed),
2389 cache.row_misses.load(Ordering::Relaxed),
2390 cache.col_hits.load(Ordering::Relaxed),
2391 cache.col_misses.load(Ordering::Relaxed),
2392 )
2393 })
2394 })
2395 .unwrap_or((0, 0, 0, 0))
2396 }
2397
2398 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
2399 self.graph.set_first_load_assume_new(enabled);
2400 }
2401
2402 pub fn reset_ensure_touched(&mut self) {
2403 self.graph.reset_ensure_touched();
2404 }
2405
2406 pub fn finalize_sheet_index(&mut self, sheet: &str) {
2407 self.graph.finalize_sheet_index(sheet);
2408 }
2409
2410 pub fn action<T>(
2419 &mut self,
2420 name: impl AsRef<str>,
2421 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2422 ) -> Result<T, crate::engine::EditorError> {
2423 if self.action_depth != 0 {
2424 return Err(crate::engine::EditorError::TransactionFailed {
2425 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
2426 .to_string(),
2427 });
2428 }
2429
2430 self.action_depth = 1;
2431 let engine_ptr: *mut Engine<R> = self;
2432 let _guard = ActionDepthGuard {
2433 engine: engine_ptr,
2434 _marker: std::marker::PhantomData,
2435 };
2436
2437 let mut tx = EngineAction {
2438 engine: self,
2439 name: name.as_ref().to_string(),
2440 log: None,
2441 arrow_undo: None,
2442 atomic_policy: false,
2443 };
2444 f(&mut tx)
2445 }
2446
2447 pub fn action_atomic<T>(
2451 &mut self,
2452 name: impl Into<String>,
2453 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2454 ) -> Result<T, crate::engine::EditorError> {
2455 let (v, _j) = self.action_atomic_journal(name, f)?;
2456 Ok(v)
2457 }
2458
2459 pub fn action_atomic_journal<T>(
2461 &mut self,
2462 name: impl Into<String>,
2463 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2464 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2465 if self.action_depth != 0 {
2466 return Err(crate::engine::EditorError::TransactionFailed {
2467 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2468 .to_string(),
2469 });
2470 }
2471
2472 self.action_depth = 1;
2473 let engine_ptr: *mut Engine<R> = self;
2474 let _guard = ActionDepthGuard {
2475 engine: engine_ptr,
2476 _marker: std::marker::PhantomData,
2477 };
2478
2479 let name_str = name.into();
2480 let mut log = crate::engine::ChangeLog::new();
2481 let start_len = log.len();
2482 self.action_atomic_impl(&mut log, start_len, name_str, f)
2483 }
2484
2485 fn action_atomic_impl<T>(
2486 &mut self,
2487 log: &mut crate::engine::ChangeLog,
2488 start_len: usize,
2489 name: String,
2490 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2491 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2492 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
2493 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
2494
2495 let log_ptr: *mut crate::engine::ChangeLog = log;
2496 let mut tx = EngineAction {
2497 engine: self,
2498 name: name.clone(),
2499 log: Some(log_ptr),
2500 arrow_undo: Some(arrow_ptr),
2501 atomic_policy: true,
2502 };
2503
2504 let res = f(&mut tx);
2505
2506 let graph_events: Vec<crate::engine::ChangeEvent> =
2508 unsafe { (&*log_ptr).events() }[start_len..].to_vec();
2509 let graph_batch = crate::engine::GraphUndoBatch {
2510 events: graph_events,
2511 };
2512 let affected_cells = arrow_undo.ops.len();
2513 let journal = crate::engine::ActionJournal {
2514 name,
2515 graph: graph_batch,
2516 arrow: arrow_undo,
2517 affected_cells,
2518 };
2519
2520 match res {
2521 Ok(v) => {
2522 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2523 for event in &journal.graph.events {
2524 self.record_formula_plane_change_for_event(event);
2525 }
2526 self.mark_data_edited();
2527 }
2528 Ok((v, journal))
2529 }
2530 Err(e) => {
2531 if let Err(rb) = self.rollback_from_action_journal(&journal) {
2532 return Err(crate::engine::EditorError::TransactionFailed {
2533 reason: format!(
2534 "Engine::action_atomic rollback failed after error '{e}': {rb}"
2535 ),
2536 });
2537 }
2538 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2539 for event in &journal.graph.events {
2540 self.record_formula_plane_change_for_event(event);
2541 }
2542 }
2543 Err(e)
2544 }
2545 }
2546 }
2547
2548 pub fn action_with_logger<T>(
2555 &mut self,
2556 log: &mut crate::engine::ChangeLog,
2557 name: impl AsRef<str>,
2558 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2559 ) -> Result<T, crate::engine::EditorError> {
2560 if self.action_depth != 0 {
2561 return Err(crate::engine::EditorError::TransactionFailed {
2562 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2563 .to_string(),
2564 });
2565 }
2566
2567 self.action_depth = 1;
2568 let engine_ptr: *mut Engine<R> = self;
2569 let _guard = ActionDepthGuard {
2570 engine: engine_ptr,
2571 _marker: std::marker::PhantomData,
2572 };
2573
2574 let start_len = log.len();
2575 let name_str = name.as_ref().to_string();
2576 log.begin_compound(name_str.clone());
2577
2578 let res = self.action_atomic_impl(log, start_len, name_str, f);
2581
2582 match res {
2583 Ok((v, _journal)) => {
2584 log.end_compound();
2585 Ok(v)
2586 }
2587 Err(e) => {
2588 log.end_compound();
2590 log.truncate(start_len);
2591 Err(e)
2592 }
2593 }
2594 }
2595
2596 fn rollback_from_action_journal(
2597 &mut self,
2598 journal: &crate::engine::ActionJournal,
2599 ) -> Result<(), crate::engine::EditorError> {
2600 journal.graph.undo(&mut self.graph)?;
2602 self.apply_inverse_row_visibility_events(&journal.graph.events);
2604 self.apply_arrow_undo_batch(&journal.arrow, true);
2606 Ok(())
2607 }
2608
2609 fn rollback_from_change_events(
2610 &mut self,
2611 events: &[crate::engine::ChangeEvent],
2612 ) -> Result<(), crate::engine::EditorError> {
2613 use crate::engine::ChangeEvent;
2614
2615 {
2617 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
2618 let mut compound_stack: Vec<usize> = Vec::new();
2619 for ev in events.iter().rev() {
2620 match ev {
2621 ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
2622 ChangeEvent::CompoundStart { depth, .. } => {
2623 if compound_stack.last() == Some(depth) {
2624 compound_stack.pop();
2625 }
2626 }
2627 ChangeEvent::SetRowVisibility { .. } => {
2628 }
2630 _ => {
2631 editor.apply_inverse(ev.clone())?;
2632 }
2633 }
2634 }
2635 }
2636
2637 for ev in events.iter().rev() {
2639 self.apply_inverse_row_visibility_event(ev);
2640 }
2641
2642 for ev in events.iter().rev() {
2644 self.mirror_inverse_change_to_arrow(ev);
2645 }
2646
2647 Ok(())
2648 }
2649
2650 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
2651 let sheet_id = self.graph.sheet_id(sheet)?;
2652 let coord = Coord::from_excel(row, col, true, true);
2653 let cell = CellRef::new(sheet_id, coord);
2654 let vid = self.graph.get_vertex_for_cell(&cell)?;
2655 let ast_id = self.graph.get_formula_id(vid)?;
2656 self.graph
2657 .data_store()
2658 .retrieve_ast(ast_id, self.graph.sheet_reg())
2659 }
2660
2661 pub fn edit_with_logger<T>(
2662 &mut self,
2663 log: &mut crate::engine::ChangeLog,
2664 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
2665 ) -> T {
2666 let start_len = log.len();
2668
2669 struct ArrowSpillReader<'a> {
2672 sheets: &'a crate::arrow_store::SheetStore,
2673 }
2674 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
2675 fn read_cell_value(
2676 &self,
2677 sheet: &str,
2678 row: u32,
2679 col: u32,
2680 ) -> Option<formualizer_common::LiteralValue> {
2681 use formualizer_common::LiteralValue;
2682 let asheet = self.sheets.sheet(sheet)?;
2683 let r0 = row.saturating_sub(1) as usize;
2684 let c0 = col.saturating_sub(1) as usize;
2685 let v = asheet.get_cell_value(r0, c0);
2686 if matches!(v, LiteralValue::Empty) {
2687 None
2688 } else {
2689 Some(v)
2690 }
2691 }
2692 }
2693
2694 let ret = {
2695 let spill_reader = ArrowSpillReader {
2696 sheets: &self.arrow_sheets,
2697 };
2698 let mut editor = crate::engine::VertexEditor::with_logger_and_spill_reader(
2699 &mut self.graph,
2700 log,
2701 &spill_reader,
2702 );
2703 f(&mut editor)
2704 };
2705
2706 for ev in &log.events()[start_len..] {
2709 self.mirror_forward_change_to_arrow(ev);
2710 }
2711 for ev in &log.events()[start_len..] {
2712 self.record_formula_plane_change_for_event(ev);
2713 }
2714
2715 ret
2716 }
2717
2718 pub fn undo_logged(
2719 &mut self,
2720 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2721 log: &mut crate::engine::ChangeLog,
2722 ) -> Result<(), crate::engine::EditorError> {
2723 let batch = undo.undo(&mut self.graph, log)?;
2724 for item in batch.iter().rev() {
2725 self.apply_inverse_row_visibility_event(&item.event);
2726 self.apply_inverse_staged_formula_event(&item.event);
2727 }
2728 self.mirror_undo_batch_to_arrow(&batch);
2729 if !batch.is_empty() {
2730 for item in &batch {
2731 self.record_formula_plane_change_for_event(&item.event);
2732 }
2733 }
2734 Ok(())
2735 }
2736
2737 pub fn redo_logged(
2738 &mut self,
2739 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2740 log: &mut crate::engine::ChangeLog,
2741 ) -> Result<(), crate::engine::EditorError> {
2742 let batch = undo.redo(&mut self.graph, log)?;
2743 for item in &batch {
2744 self.apply_forward_row_visibility_event(&item.event);
2745 self.apply_forward_staged_formula_event(&item.event);
2746 }
2747 self.mirror_redo_batch_to_arrow(&batch);
2748 if !batch.is_empty() {
2749 for item in &batch {
2750 self.record_formula_plane_change_for_event(&item.event);
2751 }
2752 }
2753 Ok(())
2754 }
2755
2756 pub fn undo_action(
2760 &mut self,
2761 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2762 ) -> Result<(), crate::engine::EditorError> {
2763 let Some(journal) = undo.pop_undo_action() else {
2764 return Ok(());
2765 };
2766
2767 journal.graph.undo(&mut self.graph)?;
2768 self.apply_inverse_row_visibility_events(&journal.graph.events);
2769 self.apply_arrow_undo_batch(&journal.arrow, true);
2770 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2771 for event in &journal.graph.events {
2772 self.record_formula_plane_change_for_event(event);
2773 }
2774 self.mark_data_edited();
2775 }
2776
2777 undo.push_redo_action(journal);
2778 Ok(())
2779 }
2780
2781 pub fn redo_action(
2785 &mut self,
2786 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2787 ) -> Result<(), crate::engine::EditorError> {
2788 let Some(journal) = undo.pop_redo_action() else {
2789 return Ok(());
2790 };
2791
2792 journal.graph.redo(&mut self.graph)?;
2793 self.apply_forward_row_visibility_events(&journal.graph.events);
2794 self.apply_arrow_undo_batch(&journal.arrow, false);
2795 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2796 for event in &journal.graph.events {
2797 self.record_formula_plane_change_for_event(event);
2798 }
2799 self.mark_data_edited();
2800 }
2801
2802 undo.push_done_action(journal);
2803 Ok(())
2804 }
2805
2806 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
2807 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
2808 let row = addr.coord.row() + 1;
2810 let col = addr.coord.col() + 1;
2811 (sheet, row, col)
2812 }
2813
2814 fn mirror_undo_batch_to_arrow(
2815 &mut self,
2816 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2817 ) {
2818 for item in batch.iter().rev() {
2820 self.mirror_inverse_change_to_arrow(&item.event);
2821 }
2822 }
2823
2824 fn mirror_redo_batch_to_arrow(
2825 &mut self,
2826 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2827 ) {
2828 for item in batch.iter() {
2830 self.mirror_forward_change_to_arrow(&item.event);
2831 }
2832 }
2833
2834 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2835 use crate::engine::ChangeEvent;
2836 use formualizer_common::LiteralValue;
2837
2838 match ev {
2839 ChangeEvent::SetValue {
2840 addr,
2841 old_value,
2842 old_formula,
2843 ..
2844 } => {
2845 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2846 if old_formula.is_some() {
2847 self.clear_delta_overlay_cell(&sheet, row, col);
2848 } else {
2849 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2850 self.mirror_value_to_overlay(&sheet, row, col, &v);
2851 }
2852 }
2853 ChangeEvent::SetFormula {
2854 addr,
2855 old_value,
2856 old_formula,
2857 ..
2858 } => {
2859 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2860 if old_formula.is_some() {
2861 self.clear_delta_overlay_cell(&sheet, row, col);
2862 } else {
2863 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2864 self.mirror_value_to_overlay(&sheet, row, col, &v);
2865 }
2866 }
2867 ChangeEvent::SpillCommitted { old, new, .. } => {
2868 self.mirror_spill_snapshot(new, true);
2870 if let Some(snap) = old {
2871 self.mirror_spill_snapshot(snap, false);
2872 }
2873 }
2874 ChangeEvent::SpillCleared { old, .. } => {
2875 self.mirror_spill_snapshot(old, false);
2877 }
2878 ChangeEvent::SetRowVisibility { .. } => {
2879 }
2881 _ => {}
2882 }
2883 }
2884
2885 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2886 use crate::engine::ChangeEvent;
2887
2888 match ev {
2889 ChangeEvent::SetValue { addr, new, .. } => {
2890 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2891 self.mirror_value_to_overlay(&sheet, row, col, new);
2892 }
2893 ChangeEvent::SetFormula { addr, .. } => {
2894 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2895 self.clear_delta_overlay_cell(&sheet, row, col);
2896 }
2898 ChangeEvent::SpillCommitted { old, new, .. } => {
2899 if let Some(snap) = old {
2900 self.mirror_spill_snapshot(snap, true);
2901 }
2902 self.mirror_spill_snapshot(new, false);
2903 }
2904 ChangeEvent::SpillCleared { old, .. } => {
2905 self.mirror_spill_snapshot(old, true);
2906 }
2907 ChangeEvent::SetRowVisibility { .. } => {
2908 }
2910 _ => {
2911 }
2913 }
2914 }
2915
2916 fn mirror_spill_snapshot(
2917 &mut self,
2918 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
2919 clear_only: bool,
2920 ) {
2921 use formualizer_common::LiteralValue;
2922
2923 let mut i = 0usize;
2924 for row in &snap.values {
2925 for v in row {
2926 if let Some(cell) = snap.target_cells.get(i) {
2927 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2928 let out = if clear_only {
2929 LiteralValue::Empty
2930 } else {
2931 v.clone()
2932 };
2933 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
2934 }
2935 i += 1;
2936 }
2937 }
2938 if clear_only {
2940 for cell in snap.target_cells.iter().skip(i) {
2941 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2942 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
2943 }
2944 }
2945 }
2946
2947 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2948 self.graph.set_default_sheet_by_name(name);
2949 }
2950
2951 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2952 self.graph.set_default_sheet_by_id(id);
2953 }
2954
2955 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
2956 self.graph.set_sheet_index_mode(mode);
2957 }
2958
2959 fn clear_cached_static_schedule(&mut self) {
2960 self.cached_static_schedule = None;
2961 }
2962
2963 pub fn mark_data_edited(&mut self) {
2966 self.snapshot_id
2967 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2968 self.has_edited = true;
2969 }
2970
2971 pub fn mark_topology_edited(&mut self) {
2973 self.snapshot_id
2974 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2975 self.topology_epoch = self.topology_epoch.wrapping_add(1);
2976 self.clear_cached_static_schedule();
2977 self.has_edited = true;
2978 }
2979
2980 fn mark_all_formula_vertices_dirty(&mut self) {
2981 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
2982 for vertex in vertices {
2983 self.graph.mark_vertex_dirty(vertex);
2984 }
2985 }
2986
2987 fn mark_moved_formula_vertices_dirty(
2988 &mut self,
2989 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
2990 ) {
2991 for vertex in &summary.vertices_moved {
2992 if self.graph.get_formula_id(*vertex).is_some() {
2993 self.graph.mark_vertex_dirty(*vertex);
2994 }
2995 }
2996 }
2997
2998 pub fn sheet_store(&self) -> &SheetStore {
3000 &self.arrow_sheets
3001 }
3002
3003 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
3005 &mut self.arrow_sheets
3006 }
3007
3008 pub fn has_staged_formulas(&self) -> bool {
3009 !self.staged_formulas.is_empty()
3010 }
3011
3012 pub fn staged_formula_count(&self) -> usize {
3013 self.staged_formulas.values().map(StagedSheet::len).sum()
3014 }
3015
3016 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
3018 self.staged_formulas
3019 .entry(sheet.to_string())
3020 .or_default()
3021 .stage(row, col, text);
3022 }
3023
3024 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
3025 let mut removed = None;
3026 let mut remove_sheet = false;
3027 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
3028 removed = entries.remove(row, col);
3029 remove_sheet = entries.is_empty();
3030 }
3031 if remove_sheet {
3032 self.staged_formulas.remove(sheet);
3033 }
3034 removed
3035 }
3036
3037 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
3038 self.staged_formulas.remove(sheet);
3039 }
3040
3041 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
3042 let Some(entries) = self.staged_formulas.remove(old) else {
3043 return;
3044 };
3045 for (row, col, text) in entries.into_entries() {
3046 self.stage_formula_text(new, row, col, text);
3047 }
3048 }
3049
3050 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
3052 self.staged_formulas
3053 .get(sheet)
3054 .and_then(|v| v.get(row, col).map(str::to_owned))
3055 }
3056
3057 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
3058 &self.formula_parse_diagnostics
3059 }
3060
3061 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
3062 std::mem::take(&mut self.formula_parse_diagnostics)
3063 }
3064
3065 pub fn clear_formula_parse_diagnostics(&mut self) {
3066 self.formula_parse_diagnostics.clear();
3067 }
3068
3069 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
3070 self.last_formula_ingest_report.as_ref()
3071 }
3072
3073 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
3074 &self.formula_ingest_report_total
3075 }
3076
3077 #[cfg(test)]
3078 pub(crate) fn last_formula_plane_span_eval_report(&self) -> Option<&SpanEvalReport> {
3079 self.last_formula_plane_span_eval_report.as_ref()
3080 }
3081
3082 #[cfg(test)]
3083 pub(crate) fn formula_plane_indexes_epoch(&self) -> u64 {
3084 self.graph.formula_authority().indexes_epoch()
3085 }
3086
3087 #[cfg(test)]
3088 pub(crate) fn formula_plane_capacity_bailouts(&self) -> u64 {
3089 self.formula_plane_capacity_bailouts
3090 }
3091
3092 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
3093 self.formula_ingest_report_total.mode = report.mode;
3094 self.formula_ingest_report_total.accumulate(&report);
3095 self.last_formula_ingest_report = Some(report);
3096 }
3097
3098 fn analyze_formula_plane_shadow_candidates(
3099 &mut self,
3100 batches: &[FormulaIngestBatch],
3101 ) -> FormulaIngestReport {
3102 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Shadow);
3103 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3104
3105 let _active_epoch = self.graph.formula_authority().plane.epoch();
3108
3109 let batch_sheet_ids: Vec<SheetId> = batches
3110 .iter()
3111 .map(|batch| self.graph.sheet_id_mut(&batch.sheet_name))
3112 .collect();
3113 let mut groups: BTreeMap<
3114 (SheetId, u64, u32),
3115 Vec<(FormulaPlacementCandidate, CandidateAnalysis)>,
3116 > = BTreeMap::new();
3117 {
3118 let mut pipeline = self.ingest_pipeline();
3119 for (batch, sheet_id) in batches.iter().zip(batch_sheet_ids.iter().copied()) {
3120 for record in &batch.formulas {
3121 if record.row == 0 || record.col == 0 {
3122 report.shadow_candidate_cells =
3123 report.shadow_candidate_cells.saturating_add(1);
3124 report.shadow_fallback_cells =
3125 report.shadow_fallback_cells.saturating_add(1);
3126 Self::record_shadow_fallback_reason(
3127 &mut report,
3128 PlacementFallbackReason::UnsupportedShapeOrGaps,
3129 1,
3130 );
3131 continue;
3132 }
3133
3134 let placement = CellRef::new(
3135 sheet_id,
3136 Coord::from_excel(record.row, record.col, true, true),
3137 );
3138 let ingested = match pipeline.ingest_formula(
3139 FormulaAstInput::RawArena(record.ast_id),
3140 placement,
3141 record.formula_text.clone(),
3142 ) {
3143 Ok(ingested) => ingested,
3144 Err(_) => {
3145 report.shadow_candidate_cells =
3146 report.shadow_candidate_cells.saturating_add(1);
3147 report.shadow_fallback_cells =
3148 report.shadow_fallback_cells.saturating_add(1);
3149 Self::record_shadow_fallback_reason(
3150 &mut report,
3151 PlacementFallbackReason::UnsupportedCanonicalTemplate,
3152 1,
3153 );
3154 continue;
3155 }
3156 };
3157 let candidate = FormulaPlacementCandidate::new(
3158 sheet_id,
3159 record.row - 1,
3160 record.col - 1,
3161 ingested.ast_id,
3162 record.formula_text.clone(),
3163 );
3164 let analysis = match CandidateAnalysis::from_ingested(&candidate, &ingested) {
3165 Ok(analysis) => analysis,
3166 Err(reason) => {
3167 report.shadow_candidate_cells =
3168 report.shadow_candidate_cells.saturating_add(1);
3169 report.shadow_fallback_cells =
3170 report.shadow_fallback_cells.saturating_add(1);
3171 Self::record_shadow_fallback_reason(&mut report, reason, 1);
3172 continue;
3173 }
3174 };
3175 groups
3176 .entry((
3177 sheet_id,
3178 ingested.parameterized_canonical_hash,
3179 candidate.col,
3180 ))
3181 .or_default()
3182 .push((candidate, analysis));
3183 }
3184 }
3185 }
3186
3187 let mut scratch_plane = FormulaPlane::default();
3188 for entries in groups.into_values() {
3189 let (candidates, analyses): (Vec<_>, Vec<_>) = entries.into_iter().unzip();
3190 for (component, component_analyses) in
3191 Self::split_candidate_components_with_analyses(candidates, analyses)
3192 {
3193 let placement_report = place_candidate_family_with_analyses(
3194 &mut scratch_plane,
3195 component,
3196 component_analyses,
3197 );
3198 let counters = placement_report.counters;
3199 report.shadow_candidate_cells = report
3200 .shadow_candidate_cells
3201 .saturating_add(counters.formula_cells_seen);
3202 report.shadow_accepted_span_cells = report
3203 .shadow_accepted_span_cells
3204 .saturating_add(counters.accepted_span_cells);
3205 report.shadow_fallback_cells = report
3206 .shadow_fallback_cells
3207 .saturating_add(counters.legacy_cells);
3208 report.shadow_templates_interned = report
3209 .shadow_templates_interned
3210 .saturating_add(counters.templates_interned);
3211 report.shadow_spans_created = report
3212 .shadow_spans_created
3213 .saturating_add(counters.spans_created);
3214 report.graph_formula_vertices_avoided_shadow = report
3215 .graph_formula_vertices_avoided_shadow
3216 .saturating_add(counters.formula_vertices_avoided);
3217 report.ast_roots_avoided_shadow = report
3218 .ast_roots_avoided_shadow
3219 .saturating_add(counters.ast_roots_avoided);
3220 report.edge_rows_avoided_shadow = report
3221 .edge_rows_avoided_shadow
3222 .saturating_add(counters.edge_rows_avoided);
3223 for (reason, count) in counters.fallback_reasons {
3224 Self::record_shadow_fallback_reason(&mut report, reason, count);
3225 }
3226 }
3227 }
3228 report
3229 }
3230
3231 fn record_shadow_fallback_reason(
3232 report: &mut FormulaIngestReport,
3233 reason: PlacementFallbackReason,
3234 count: u64,
3235 ) {
3236 *report
3237 .fallback_reasons
3238 .entry(format!("{reason:?}"))
3239 .or_default() += count;
3240 }
3241
3242 fn analyze_formula_plane_authoritative_ingest(
3243 &mut self,
3244 batches: &[FormulaIngestBatch],
3245 ) -> (
3246 FormulaIngestReport,
3247 Vec<FormulaIngestBatch>,
3248 PlannedFormulaMaterialize,
3249 ) {
3250 let mut report =
3251 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
3252 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3253
3254 let mut pending_candidates: Vec<(String, FormulaPlacementCandidate)> = Vec::new();
3255 let mut fallback: BTreeMap<String, Vec<FormulaIngestRecord>> = BTreeMap::new();
3256 let mut planned_fallback: PlannedFormulaMaterialize = BTreeMap::new();
3257
3258 for batch in batches {
3259 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
3260 for record in &batch.formulas {
3261 if record.row == 0 || record.col == 0 {
3262 report.shadow_candidate_cells = report.shadow_candidate_cells.saturating_add(1);
3263 report.shadow_fallback_cells = report.shadow_fallback_cells.saturating_add(1);
3264 Self::record_shadow_fallback_reason(
3265 &mut report,
3266 PlacementFallbackReason::UnsupportedShapeOrGaps,
3267 1,
3268 );
3269 fallback
3270 .entry(batch.sheet_name.clone())
3271 .or_default()
3272 .push(record.clone());
3273 continue;
3274 }
3275
3276 pending_candidates.push((
3277 batch.sheet_name.clone(),
3278 FormulaPlacementCandidate::new(
3279 sheet_id,
3280 record.row - 1,
3281 record.col - 1,
3282 record.ast_id,
3283 record.formula_text.clone(),
3284 ),
3285 ));
3286 }
3287 }
3288
3289 let mut groups: BTreeMap<(SheetId, u64, u32), Vec<usize>> = BTreeMap::new();
3290 let mut analyses_by_index: Vec<Option<CandidateAnalysis>> =
3291 (0..pending_candidates.len()).map(|_| None).collect();
3292 let mut plans_by_index: Vec<Option<DependencyPlanRow>> =
3293 (0..pending_candidates.len()).map(|_| None).collect();
3294 {
3295 let mut pipeline = self.ingest_pipeline();
3296 for (idx, (sheet_name, candidate)) in pending_candidates.iter_mut().enumerate() {
3297 let placement = CellRef::new(
3298 candidate.sheet_id,
3299 Coord::from_excel(
3300 candidate.row.saturating_add(1),
3301 candidate.col.saturating_add(1),
3302 true,
3303 true,
3304 ),
3305 );
3306 let ingested = pipeline.ingest_formula(
3307 FormulaAstInput::RawArena(candidate.ast_id),
3308 placement,
3309 candidate.formula_text.clone(),
3310 );
3311 match ingested {
3312 Ok(ingested) => {
3313 candidate.ast_id = ingested.ast_id;
3314 let canonical_hash = ingested.parameterized_canonical_hash;
3315 let dep_plan = ingested.dep_plan.clone();
3316 match CandidateAnalysis::from_ingested(candidate, &ingested) {
3317 Ok(analysis) => {
3318 groups
3319 .entry((candidate.sheet_id, canonical_hash, candidate.col))
3320 .or_default()
3321 .push(idx);
3322 analyses_by_index[idx] = Some(analysis);
3323 plans_by_index[idx] = Some(dep_plan);
3324 }
3325 Err(reason) => {
3326 report.shadow_candidate_cells =
3327 report.shadow_candidate_cells.saturating_add(1);
3328 report.shadow_fallback_cells =
3329 report.shadow_fallback_cells.saturating_add(1);
3330 Self::record_shadow_fallback_reason(&mut report, reason, 1);
3331 planned_fallback
3332 .entry(sheet_name.clone())
3333 .or_default()
3334 .push((
3335 candidate.row.saturating_add(1),
3336 candidate.col.saturating_add(1),
3337 candidate.ast_id,
3338 dep_plan,
3339 ));
3340 }
3341 }
3342 }
3343 Err(_) => {
3344 report.shadow_candidate_cells =
3345 report.shadow_candidate_cells.saturating_add(1);
3346 report.shadow_fallback_cells =
3347 report.shadow_fallback_cells.saturating_add(1);
3348 Self::record_shadow_fallback_reason(
3349 &mut report,
3350 PlacementFallbackReason::UnsupportedCanonicalTemplate,
3351 1,
3352 );
3353 fallback.entry(sheet_name.clone()).or_default().push(
3354 FormulaIngestRecord::new(
3355 candidate.row.saturating_add(1),
3356 candidate.col.saturating_add(1),
3357 candidate.ast_id,
3358 candidate.formula_text.clone(),
3359 ),
3360 );
3361 }
3362 }
3363 }
3364 }
3365
3366 for ((_sheet_id, _canonical_hash, _col), candidate_indices) in groups {
3367 let sheet_name = pending_candidates[candidate_indices[0]].0.clone();
3368 let mut plans_by_coord: BTreeMap<(u32, u32), Vec<DependencyPlanRow>> = BTreeMap::new();
3369 for idx in &candidate_indices {
3370 if let Some(plan) = plans_by_index[*idx].take() {
3373 let candidate = &pending_candidates[*idx].1;
3374 plans_by_coord
3375 .entry((candidate.row, candidate.col))
3376 .or_default()
3377 .push(plan);
3378 }
3379 }
3380 let candidates: Vec<_> = candidate_indices
3381 .iter()
3382 .map(|idx| pending_candidates[*idx].1.clone())
3383 .collect();
3384 let components = Self::split_shadow_candidate_components(candidates);
3385 let analyzed_components =
3386 if components.len() == 1 && components[0].len() == candidate_indices.len() {
3387 let component = components.into_iter().next().expect("one component");
3388 let component_analyses = candidate_indices
3389 .iter()
3390 .map(|idx| {
3391 analyses_by_index[*idx]
3392 .take()
3393 .expect("candidate analysis must be used once")
3394 })
3395 .collect();
3396 vec![(component, component_analyses)]
3397 } else {
3398 let mut indices_by_coord: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3399 for idx in candidate_indices.iter().rev() {
3400 let candidate = &pending_candidates[*idx].1;
3401 indices_by_coord
3402 .entry((candidate.row, candidate.col))
3403 .or_default()
3404 .push(*idx);
3405 }
3406
3407 components
3408 .into_iter()
3409 .map(|component| {
3410 let mut component_analyses = Vec::with_capacity(component.len());
3411 for candidate in &component {
3412 let idx = indices_by_coord
3413 .get_mut(&(candidate.row, candidate.col))
3414 .and_then(Vec::pop)
3415 .expect("component candidate must have a precomputed analysis");
3416 component_analyses.push(
3417 analyses_by_index[idx]
3418 .take()
3419 .expect("candidate analysis must be used once"),
3420 );
3421 }
3422 (component, component_analyses)
3423 })
3424 .collect()
3425 };
3426
3427 for (component, component_analyses) in analyzed_components {
3428 for (component, component_analyses) in
3429 split_candidate_affine_literal_runs(component, component_analyses)
3430 {
3431 let placement_report = {
3432 let authority = self.graph.formula_authority_mut();
3433 place_candidate_family_with_analyses(
3434 &mut authority.plane,
3435 component.clone(),
3436 component_analyses,
3437 )
3438 };
3439 Self::accumulate_formula_plane_placement_report(&mut report, &placement_report);
3440
3441 let mut candidate_by_placement: FxHashMap<
3449 crate::formula_plane::runtime::PlacementCoord,
3450 &FormulaPlacementCandidate,
3451 > = FxHashMap::with_capacity_and_hasher(component.len(), Default::default());
3452 for candidate in &component {
3453 candidate_by_placement
3454 .entry(candidate.placement())
3455 .or_insert(candidate);
3456 }
3457 for result in &placement_report.results {
3458 let FormulaPlacementResult::Legacy { placement, .. } = result else {
3459 continue;
3460 };
3461 if let Some(&candidate) = candidate_by_placement.get(placement) {
3462 let plan = plans_by_coord
3463 .get_mut(&(candidate.row, candidate.col))
3464 .and_then(Vec::pop);
3465 if let Some(plan) = plan {
3466 planned_fallback
3467 .entry(sheet_name.clone())
3468 .or_default()
3469 .push((
3470 candidate.row.saturating_add(1),
3471 candidate.col.saturating_add(1),
3472 candidate.ast_id,
3473 plan,
3474 ));
3475 } else {
3476 fallback.entry(sheet_name.clone()).or_default().push(
3477 FormulaIngestRecord::new(
3478 candidate.row.saturating_add(1),
3479 candidate.col.saturating_add(1),
3480 candidate.ast_id,
3481 candidate.formula_text.clone(),
3482 ),
3483 );
3484 }
3485 }
3486 }
3487 }
3488 }
3489 }
3490
3491 let _index_report = self.graph.formula_authority_mut().rebuild_indexes();
3492
3493 let fallback_batches = fallback
3494 .into_iter()
3495 .map(|(sheet_name, formulas)| FormulaIngestBatch::new(sheet_name, formulas))
3496 .collect();
3497 (report, fallback_batches, planned_fallback)
3498 }
3499
3500 fn accumulate_formula_plane_placement_report(
3501 report: &mut FormulaIngestReport,
3502 placement_report: &crate::formula_plane::placement::FormulaPlacementReport,
3503 ) {
3504 let counters = &placement_report.counters;
3505 report.shadow_candidate_cells = report
3506 .shadow_candidate_cells
3507 .saturating_add(counters.formula_cells_seen);
3508 report.shadow_accepted_span_cells = report
3509 .shadow_accepted_span_cells
3510 .saturating_add(counters.accepted_span_cells);
3511 report.shadow_fallback_cells = report
3512 .shadow_fallback_cells
3513 .saturating_add(counters.legacy_cells);
3514 report.shadow_templates_interned = report
3515 .shadow_templates_interned
3516 .saturating_add(counters.templates_interned);
3517 report.shadow_spans_created = report
3518 .shadow_spans_created
3519 .saturating_add(counters.spans_created);
3520 report.graph_formula_vertices_avoided_shadow = report
3521 .graph_formula_vertices_avoided_shadow
3522 .saturating_add(counters.formula_vertices_avoided);
3523 report.ast_roots_avoided_shadow = report
3524 .ast_roots_avoided_shadow
3525 .saturating_add(counters.ast_roots_avoided);
3526 report.edge_rows_avoided_shadow = report
3527 .edge_rows_avoided_shadow
3528 .saturating_add(counters.edge_rows_avoided);
3529 for (reason, count) in &counters.fallback_reasons {
3530 Self::record_shadow_fallback_reason(report, *reason, *count);
3531 }
3532 }
3533
3534 fn split_candidate_components_with_analyses(
3535 candidates: Vec<FormulaPlacementCandidate>,
3536 mut analyses: Vec<CandidateAnalysis>,
3537 ) -> Vec<(Vec<FormulaPlacementCandidate>, Vec<CandidateAnalysis>)> {
3538 let components = Self::split_shadow_candidate_components(candidates.clone());
3539 let mut analysis_by_coord: BTreeMap<(u32, u32), Vec<CandidateAnalysis>> = BTreeMap::new();
3540 for (candidate, analysis) in candidates.into_iter().zip(analyses.drain(..)) {
3541 analysis_by_coord
3542 .entry((candidate.row, candidate.col))
3543 .or_default()
3544 .push(analysis);
3545 }
3546 components
3547 .into_iter()
3548 .flat_map(|component| {
3549 let mut component_analyses = Vec::with_capacity(component.len());
3550 for candidate in &component {
3551 let analysis = analysis_by_coord
3552 .get_mut(&(candidate.row, candidate.col))
3553 .and_then(Vec::pop)
3554 .expect("component candidate must have a precomputed analysis");
3555 component_analyses.push(analysis);
3556 }
3557 split_candidate_affine_literal_runs(component, component_analyses)
3558 })
3559 .collect()
3560 }
3561
3562 fn split_shadow_candidate_components(
3563 candidates: Vec<FormulaPlacementCandidate>,
3564 ) -> Vec<Vec<FormulaPlacementCandidate>> {
3565 if candidates.len() <= 1 {
3566 return vec![candidates];
3567 }
3568
3569 let is_row_run = candidates.windows(2).all(|w| {
3573 w[0].sheet_id == w[1].sheet_id && w[0].col == w[1].col && w[0].row + 1 == w[1].row
3574 });
3575 let is_col_run = candidates.windows(2).all(|w| {
3576 w[0].sheet_id == w[1].sheet_id && w[0].row == w[1].row && w[0].col + 1 == w[1].col
3577 });
3578 if is_row_run || is_col_run {
3579 return vec![candidates];
3580 }
3581
3582 let mut coord_to_indices: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3583 for (idx, candidate) in candidates.iter().enumerate() {
3584 coord_to_indices
3585 .entry((candidate.row, candidate.col))
3586 .or_default()
3587 .push(idx);
3588 }
3589
3590 let mut remaining: BTreeSet<usize> = (0..candidates.len()).collect();
3591 let mut components = Vec::new();
3592 while let Some(&start) = remaining.iter().next() {
3593 remaining.remove(&start);
3594 let mut queue = VecDeque::from([start]);
3595 let mut component_indices = Vec::new();
3596
3597 while let Some(idx) = queue.pop_front() {
3598 component_indices.push(idx);
3599 let candidate = &candidates[idx];
3600 let mut neighbor_coords = Vec::with_capacity(5);
3601 neighbor_coords.push((candidate.row, candidate.col));
3602 if let Some(row) = candidate.row.checked_sub(1) {
3603 neighbor_coords.push((row, candidate.col));
3604 }
3605 neighbor_coords.push((candidate.row.saturating_add(1), candidate.col));
3606 if let Some(col) = candidate.col.checked_sub(1) {
3607 neighbor_coords.push((candidate.row, col));
3608 }
3609 neighbor_coords.push((candidate.row, candidate.col.saturating_add(1)));
3610
3611 for coord in neighbor_coords {
3612 if let Some(indices) = coord_to_indices.get(&coord) {
3613 for &neighbor in indices {
3614 if remaining.remove(&neighbor) {
3615 queue.push_back(neighbor);
3616 }
3617 }
3618 }
3619 }
3620 }
3621
3622 component_indices.sort_by_key(|idx| {
3623 let candidate = &candidates[*idx];
3624 (candidate.row, candidate.col, *idx)
3625 });
3626 components.push(
3627 component_indices
3628 .into_iter()
3629 .map(|idx| candidates[idx].clone())
3630 .collect(),
3631 );
3632 }
3633
3634 components
3635 }
3636
3637 pub fn ingest_formula_batches(
3638 &mut self,
3639 batches: Vec<FormulaIngestBatch>,
3640 ) -> Result<FormulaIngestReport, ExcelError> {
3641 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3642 let (mut report, materialize_batches, planned_materialize) =
3643 match self.config.formula_plane_mode {
3644 FormulaPlaneMode::Off => (
3645 FormulaIngestReport::with_mode(FormulaPlaneMode::Off),
3646 batches,
3647 BTreeMap::new(),
3648 ),
3649 FormulaPlaneMode::Shadow => (
3650 self.analyze_formula_plane_shadow_candidates(&batches),
3651 batches,
3652 BTreeMap::new(),
3653 ),
3654 FormulaPlaneMode::AuthoritativeExperimental => {
3655 self.analyze_formula_plane_authoritative_ingest(&batches)
3656 }
3657 };
3658 report.formula_cells_seen = formula_cells_seen;
3659
3660 if !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
3661 || !planned_materialize.is_empty()
3662 {
3663 let mut builder = self.begin_bulk_ingest();
3664 for batch in materialize_batches {
3665 if batch.is_empty() {
3666 continue;
3667 }
3668 let sheet_id = builder.add_sheet(&batch.sheet_name);
3669 builder.add_formula_ids(
3670 sheet_id,
3671 batch
3672 .formulas
3673 .into_iter()
3674 .map(|record| (record.row, record.col, record.ast_id)),
3675 );
3676 }
3677 for (sheet_name, formulas) in planned_materialize {
3678 if formulas.is_empty() {
3679 continue;
3680 }
3681 let sheet_id = builder.add_sheet(&sheet_name);
3682 builder.add_formula_plans(sheet_id, formulas);
3683 }
3684 let summary = builder.finish()?;
3685 report.graph_formula_cells_materialized = summary.formulas as u64;
3686 report.graph_vertices_created = summary.vertices as u64;
3687 report.graph_edges_created = summary.edges as u64;
3688 }
3689
3690 self.record_formula_ingest_report(report.clone());
3691 Ok(report)
3692 }
3693
3694 pub fn handle_formula_parse_error(
3695 &mut self,
3696 sheet: &str,
3697 row: u32,
3698 col: u32,
3699 formula: &str,
3700 message: String,
3701 ) -> Result<Option<ASTNode>, ExcelError> {
3702 let policy = self.config.formula_parse_policy;
3703
3704 if policy == FormulaParsePolicy::Strict {
3705 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
3706 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
3707 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
3708 )));
3709 }
3710
3711 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
3712 sheet: sheet.to_string(),
3713 row,
3714 col,
3715 formula: formula.to_string(),
3716 message: message.clone(),
3717 policy,
3718 });
3719
3720 match policy {
3721 FormulaParsePolicy::Strict => unreachable!(),
3722 FormulaParsePolicy::KeepCachedValue => Ok(None),
3723 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
3724 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
3725 None,
3726 ))),
3727 FormulaParsePolicy::CoerceToError => {
3728 let err = ExcelError::new(ExcelErrorKind::Error)
3729 .with_message(format!("Malformed formula: {message}"));
3730 Ok(Some(ASTNode::new(
3731 ASTNodeType::Literal(LiteralValue::Error(err)),
3732 None,
3733 )))
3734 }
3735 }
3736 }
3737
3738 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
3740 if self.staged_formulas.is_empty() {
3741 return Ok(());
3742 }
3743 let staged = std::mem::take(&mut self.staged_formulas);
3745 for sheet in staged.keys() {
3746 let _ = self.add_sheet(sheet);
3747 }
3748
3749 let mut prepared: PreparedFormulaBatches = Vec::new();
3751 for (sheet, entries) in staged {
3752 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3753 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3754 rustc_hash::FxHashMap::default();
3755 cache.reserve(4096);
3756
3757 for (row, col, txt) in entries.into_entries() {
3758 let key = if txt.starts_with('=') {
3759 txt
3760 } else {
3761 format!("={txt}")
3762 };
3763 let ast_id = if let Some(cached) = cache.get(&key) {
3764 *cached
3765 } else {
3766 let parsed = match formualizer_parse::parser::parse(&key) {
3767 Ok(parsed) => Some(parsed),
3768 Err(e) => {
3769 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3770 }
3771 };
3772 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3773 cache.insert(key.clone(), ast_id);
3774 ast_id
3775 };
3776
3777 if let Some(ast_id) = ast_id {
3778 formulas.push(FormulaIngestRecord::new(
3779 row,
3780 col,
3781 ast_id,
3782 Some(Arc::<str>::from(key.clone())),
3783 ));
3784 }
3785 }
3786
3787 if !formulas.is_empty() {
3788 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3789 }
3790 }
3791
3792 if !prepared.is_empty() {
3793 let _ = self.ingest_formula_batches(prepared)?;
3794 }
3795 Ok(())
3796 }
3797
3798 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
3800 &mut self,
3801 sheets: I,
3802 ) -> Result<(), formualizer_parse::ExcelError> {
3803 let mut collected: StagedFormulaBatches = Vec::new();
3804 for s in sheets {
3805 if let Some(entries) = self.staged_formulas.remove(s) {
3806 collected.push((s.to_string(), entries.into_entries()));
3807 }
3808 }
3809
3810 if collected.is_empty() {
3811 return Ok(());
3812 }
3813
3814 for (sheet, _) in &collected {
3815 let _ = self.add_sheet(sheet);
3816 }
3817
3818 let mut prepared: PreparedFormulaBatches = Vec::new();
3820 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3821 rustc_hash::FxHashMap::default();
3822 cache.reserve(4096);
3823
3824 for (sheet, entries) in collected {
3825 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3826 for (row, col, txt) in entries {
3827 let key = if txt.starts_with('=') {
3828 txt
3829 } else {
3830 format!("={txt}")
3831 };
3832 let ast_id = if let Some(cached) = cache.get(&key) {
3833 *cached
3834 } else {
3835 let parsed = match formualizer_parse::parser::parse(&key) {
3836 Ok(parsed) => Some(parsed),
3837 Err(e) => {
3838 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3839 }
3840 };
3841 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3842 cache.insert(key.clone(), ast_id);
3843 ast_id
3844 };
3845
3846 if let Some(ast_id) = ast_id {
3847 formulas.push(FormulaIngestRecord::new(
3848 row,
3849 col,
3850 ast_id,
3851 Some(Arc::<str>::from(key.clone())),
3852 ));
3853 }
3854 }
3855 if !formulas.is_empty() {
3856 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3857 }
3858 }
3859
3860 if !prepared.is_empty() {
3861 let _ = self.ingest_formula_batches(prepared)?;
3862 }
3863 Ok(())
3864 }
3865
3866 pub fn begin_bulk_ingest_arrow(
3868 &mut self,
3869 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
3870 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
3871 }
3872
3873 pub fn begin_bulk_update_arrow(
3875 &mut self,
3876 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
3877 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
3878 }
3879
3880 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
3881 self.graph.sheet_id(sheet).ok_or(
3882 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
3883 name: sheet.to_string(),
3884 reason: "Unknown sheet".to_string(),
3885 },
3886 )
3887 }
3888
3889 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
3890 if row_1based == 0 {
3891 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3892 }
3893 Ok(row_1based - 1)
3894 }
3895
3896 fn normalize_row_range_1based(
3897 start_row_1based: u32,
3898 end_row_1based: u32,
3899 ) -> Result<(u32, u32), crate::engine::EditorError> {
3900 if start_row_1based == 0 || end_row_1based == 0 {
3901 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3902 }
3903 if start_row_1based > end_row_1based {
3904 return Err(crate::engine::EditorError::TransactionFailed {
3905 reason: "Row range start is greater than end".to_string(),
3906 });
3907 }
3908 Ok((start_row_1based - 1, end_row_1based - 1))
3909 }
3910
3911 fn invalidate_row_visibility_mask_cache(&self) {
3912 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
3913 cache.clear();
3914 }
3915 }
3916
3917 fn set_row_hidden_by_sheet_id(
3918 &mut self,
3919 sheet_id: SheetId,
3920 row0: u32,
3921 hidden: bool,
3922 source: RowVisibilitySource,
3923 ) -> bool {
3924 let changed = {
3925 let state = self.row_visibility.entry(sheet_id).or_default();
3926 state.set_row_hidden(row0, hidden, source)
3927 };
3928
3929 let remove_entry = self
3930 .row_visibility
3931 .get(&sheet_id)
3932 .map(|state| state.is_empty())
3933 .unwrap_or(false);
3934 if remove_entry {
3935 self.row_visibility.remove(&sheet_id);
3936 }
3937
3938 if changed {
3939 self.invalidate_row_visibility_mask_cache();
3940 }
3941
3942 changed
3943 }
3944
3945 fn set_rows_hidden_by_sheet_id(
3946 &mut self,
3947 sheet_id: SheetId,
3948 start_row0: u32,
3949 end_row0: u32,
3950 hidden: bool,
3951 source: RowVisibilitySource,
3952 ) -> bool {
3953 let changed = {
3954 let state = self.row_visibility.entry(sheet_id).or_default();
3955 state.set_rows_hidden(start_row0, end_row0, hidden, source)
3956 };
3957
3958 let remove_entry = self
3959 .row_visibility
3960 .get(&sheet_id)
3961 .map(|state| state.is_empty())
3962 .unwrap_or(false);
3963 if remove_entry {
3964 self.row_visibility.remove(&sheet_id);
3965 }
3966
3967 if changed {
3968 self.invalidate_row_visibility_mask_cache();
3969 }
3970
3971 changed
3972 }
3973
3974 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
3975 if count == 0 {
3976 return;
3977 }
3978 let mut changed = false;
3979 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3980 changed = state.insert_rows(before0, count);
3981 state.is_empty()
3982 } else {
3983 false
3984 };
3985 if remove_entry {
3986 self.row_visibility.remove(&sheet_id);
3987 }
3988 if changed {
3989 self.invalidate_row_visibility_mask_cache();
3990 }
3991 }
3992
3993 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
3994 if count == 0 {
3995 return;
3996 }
3997 let mut changed = false;
3998 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3999 changed = state.delete_rows(start0, count);
4000 state.is_empty()
4001 } else {
4002 false
4003 };
4004 if remove_entry {
4005 self.row_visibility.remove(&sheet_id);
4006 }
4007 if changed {
4008 self.invalidate_row_visibility_mask_cache();
4009 }
4010 }
4011
4012 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
4013 if let crate::engine::ChangeEvent::SetRowVisibility {
4014 sheet_id,
4015 row0,
4016 source,
4017 old_hidden,
4018 ..
4019 } = event
4020 {
4021 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
4022 }
4023 }
4024
4025 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
4026 if let crate::engine::ChangeEvent::SetRowVisibility {
4027 sheet_id,
4028 row0,
4029 source,
4030 new_hidden,
4031 ..
4032 } = event
4033 {
4034 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
4035 }
4036 }
4037
4038 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
4039 for event in events.iter().rev() {
4040 self.apply_inverse_row_visibility_event(event);
4041 }
4042 }
4043
4044 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
4045 for event in events {
4046 self.apply_forward_row_visibility_event(event);
4047 }
4048 }
4049
4050 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
4051 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
4052 sheet,
4053 row,
4054 col,
4055 old,
4056 ..
4057 } = event
4058 {
4059 self.apply_staged_formula_cell(sheet, *row, *col, old.as_deref());
4060 }
4061 }
4062
4063 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
4064 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
4065 sheet,
4066 row,
4067 col,
4068 new,
4069 ..
4070 } = event
4071 {
4072 self.apply_staged_formula_cell(sheet, *row, *col, new.as_deref());
4073 }
4074 }
4075
4076 fn apply_staged_formula_cell(&mut self, sheet: &str, row: u32, col: u32, target: Option<&str>) {
4079 match target {
4080 Some(text) => self.stage_formula_text(sheet, row, col, text.to_string()),
4081 None => {
4082 self.clear_staged_formula_text(sheet, row, col);
4083 }
4084 }
4085 }
4086
4087 pub fn set_row_hidden(
4088 &mut self,
4089 sheet: &str,
4090 row_1based: u32,
4091 hidden: bool,
4092 source: RowVisibilitySource,
4093 ) -> Result<(), crate::engine::EditorError> {
4094 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4095 let row0 = Self::normalize_row_1based(row_1based)?;
4096 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
4097 self.record_formula_plane_structural_change(StructuralScope::Region(
4098 Region::whole_row(sheet_id, row0),
4099 ));
4100 self.mark_data_edited();
4101 }
4102 Ok(())
4103 }
4104
4105 pub fn set_rows_hidden(
4106 &mut self,
4107 sheet: &str,
4108 start_row_1based: u32,
4109 end_row_1based: u32,
4110 hidden: bool,
4111 source: RowVisibilitySource,
4112 ) -> Result<(), crate::engine::EditorError> {
4113 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4114 let (start_row0, end_row0) =
4115 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
4116 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
4117 if start_row0 == end_row0 {
4118 self.record_formula_plane_structural_change(StructuralScope::Region(
4119 Region::whole_row(sheet_id, start_row0),
4120 ));
4121 } else {
4122 self.record_formula_plane_structural_change(StructuralScope::Sheet(sheet_id));
4123 }
4124 self.mark_data_edited();
4125 }
4126 Ok(())
4127 }
4128
4129 pub fn is_row_hidden(
4130 &self,
4131 sheet: &str,
4132 row_1based: u32,
4133 source: Option<RowVisibilitySource>,
4134 ) -> Option<bool> {
4135 let sheet_id = self.graph.sheet_id(sheet)?;
4136 let row0 = row_1based.checked_sub(1)?;
4137 Some(
4138 self.row_visibility
4139 .get(&sheet_id)
4140 .map(|state| state.is_row_hidden(row0, source))
4141 .unwrap_or(false),
4142 )
4143 }
4144
4145 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
4146 let sheet_id = self.graph.sheet_id(sheet)?;
4147 Some(
4148 self.row_visibility
4149 .get(&sheet_id)
4150 .map(|state| state.version())
4151 .unwrap_or(0),
4152 )
4153 }
4154
4155 fn build_row_visibility_mask_for_view(
4156 &self,
4157 view: &RangeView<'_>,
4158 mode: VisibilityMaskMode,
4159 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
4160 let sheet_rows = view.sheet().nrows as usize;
4161 if sheet_rows == 0 || view.start_row() >= sheet_rows {
4162 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
4163 }
4164
4165 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
4166 let start_row0 = view.start_row() as u32;
4167 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
4168 let version = self
4169 .row_visibility
4170 .get(&sheet_id)
4171 .map(|state| state.version())
4172 .unwrap_or(0);
4173 let key = VisibilityMaskCacheKey {
4174 sheet_id,
4175 start_row0,
4176 end_row0,
4177 mode,
4178 version,
4179 };
4180
4181 if let Ok(cache) = self.row_visibility_mask_cache.read()
4182 && let Some(mask) = cache.get(&key)
4183 {
4184 #[cfg(test)]
4185 visibility_mask_test_hooks::inc_hit();
4186 return Some(mask.clone());
4187 }
4188
4189 #[cfg(test)]
4190 visibility_mask_test_hooks::inc_miss();
4191
4192 let state = self.row_visibility.get(&sheet_id);
4193 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
4194 for row0 in start_row0..=end_row0 {
4195 let manual_hidden = state
4196 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
4197 .unwrap_or(false);
4198 let filter_hidden = state
4199 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
4200 .unwrap_or(false);
4201
4202 let include = match mode {
4203 VisibilityMaskMode::IncludeAll => true,
4204 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
4205 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
4206 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
4207 !(manual_hidden || filter_hidden)
4208 }
4209 };
4210 out.push(include);
4211 }
4212
4213 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
4214 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
4215 const MAX_CACHE_ENTRIES: usize = 4096;
4216 if cache.len() >= MAX_CACHE_ENTRIES {
4217 cache.clear();
4218 #[cfg(test)]
4219 visibility_mask_test_hooks::inc_eviction();
4220 }
4221 cache.insert(key, mask.clone());
4222 }
4223
4224 Some(mask)
4225 }
4226
4227 fn editor_error_to_excel(error: crate::engine::EditorError) -> ExcelError {
4228 match error {
4229 crate::engine::EditorError::Excel(error) => error,
4230 other => ExcelError::new(ExcelErrorKind::Value).with_message(other.to_string()),
4231 }
4232 }
4233
4234 fn demote_span_containing_cell_for_write(
4235 &mut self,
4236 sheet_id: SheetId,
4237 row0: u32,
4238 col0: u32,
4239 ) -> Result<(), crate::engine::EditorError> {
4240 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
4241 return Ok(());
4242 }
4243 let placement = PlacementCoord::new(sheet_id, row0, col0);
4244 let inside_active_span = self
4245 .graph
4246 .formula_authority()
4247 .plane
4248 .spans
4249 .find_at(placement)
4250 .is_some();
4251 if inside_active_span {
4252 self.demote_spans_preserving_computed_overlays(
4253 sheet_id,
4254 Region::point(sheet_id, row0, col0),
4255 )?;
4256 }
4257 Ok(())
4258 }
4259
4260 fn demote_spans_preserving_computed_overlays(
4261 &mut self,
4262 _sheet_id: SheetId,
4263 affected_region: Region,
4264 ) -> Result<(), crate::engine::EditorError> {
4265 self.demote_spans_for_structural_op_impl(None, affected_region, false)
4269 }
4270
4271 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
4272 Region::rows_from(sheet_id, start_row0)
4273 }
4274
4275 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
4276 Region::cols_from(sheet_id, start_col0)
4277 }
4278
4279 fn span_result_region_intersects_affected(
4280 span: &crate::formula_plane::runtime::FormulaSpan,
4281 affected_region: &Region,
4282 ) -> bool {
4283 Region::from_domain(span.result_region.domain()).intersects(affected_region)
4284 }
4285
4286 fn span_any_read_region_intersects_affected(
4287 plane: &FormulaPlane,
4288 span: &crate::formula_plane::runtime::FormulaSpan,
4289 affected_region: &Region,
4290 ) -> bool {
4291 span.read_summary_id
4292 .and_then(|read_summary_id| plane.span_read_summaries.get(read_summary_id))
4293 .is_some_and(|summary| {
4294 summary
4295 .dependencies
4296 .iter()
4297 .any(|dependency| dependency.read_region.intersects(affected_region))
4298 })
4299 }
4300
4301 fn insert_formula_plane_dirty_coords_for_span(
4302 &self,
4303 span_ref: FormulaSpanRef,
4304 dirty: ProducerDirtyDomain,
4305 out: &mut FxHashSet<(SheetId, u32, u32)>,
4306 ) -> Result<(), crate::engine::EditorError> {
4307 let authority = self.graph.formula_authority();
4308 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
4309 ExcelError::new(ExcelErrorKind::NImpl)
4310 .with_message("FormulaPlane dirty transfer referenced a stale span")
4311 })?;
4312 match dirty {
4313 ProducerDirtyDomain::Whole => {
4314 out.extend(
4315 span.domain
4316 .iter()
4317 .map(|coord| (coord.sheet_id, coord.row, coord.col)),
4318 );
4319 }
4320 ProducerDirtyDomain::Cells(cells) => {
4321 out.extend(cells.into_iter().filter_map(|key| {
4322 let coord = PlacementCoord::new(key.sheet_id, key.row, key.col);
4323 span.domain
4324 .contains(coord)
4325 .then_some((coord.sheet_id, coord.row, coord.col))
4326 }));
4327 }
4328 ProducerDirtyDomain::Regions(regions) => {
4329 out.extend(span.domain.iter().filter_map(|coord| {
4330 let key = crate::formula_plane::region_index::RegionKey::from(coord);
4331 regions
4332 .iter()
4333 .any(|region| region.contains_key(key))
4334 .then_some((coord.sheet_id, coord.row, coord.col))
4335 }));
4336 }
4337 }
4338 Ok(())
4339 }
4340
4341 fn compute_current_formula_plane_dirty_result_coords(
4342 &self,
4343 ) -> Result<FxHashSet<(SheetId, u32, u32)>, crate::engine::EditorError> {
4344 use crate::formula_plane::producer::compute_dirty_closure;
4345
4346 let authority = self.graph.formula_authority();
4347 let span_refs = authority.active_span_refs();
4348 let span_refs_by_id = span_refs
4349 .iter()
4350 .copied()
4351 .map(|span_ref| (span_ref.id, span_ref))
4352 .collect::<BTreeMap<_, _>>();
4353 let mut dirty_coords = FxHashSet::default();
4354
4355 if self.formula_plane_indexes_epoch_seen != authority.indexes_epoch() {
4356 for span_ref in span_refs {
4357 self.insert_formula_plane_dirty_coords_for_span(
4358 span_ref,
4359 ProducerDirtyDomain::Whole,
4360 &mut dirty_coords,
4361 )?;
4362 }
4363 return Ok(dirty_coords);
4364 }
4365
4366 let pending_changed_regions = authority.pending_changed_regions();
4367 if pending_changed_regions.is_empty() {
4368 return Ok(dirty_coords);
4369 }
4370
4371 let closure = compute_dirty_closure(
4372 &authority.consumer_reads,
4373 pending_changed_regions.iter().copied(),
4374 |producer| authority.producer_results.producer_result_region(producer),
4375 );
4376 for work in closure.work {
4377 let FormulaProducerId::Span(span_id) = work.producer else {
4378 continue;
4379 };
4380 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4381 continue;
4382 };
4383 self.insert_formula_plane_dirty_coords_for_span(
4384 span_ref,
4385 work.dirty,
4386 &mut dirty_coords,
4387 )?;
4388 }
4389 for fallback in closure.fallbacks {
4390 let FormulaProducerId::Span(span_id) = fallback.consumer else {
4391 continue;
4392 };
4393 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4394 continue;
4395 };
4396 self.insert_formula_plane_dirty_coords_for_span(
4397 span_ref,
4398 ProducerDirtyDomain::Whole,
4399 &mut dirty_coords,
4400 )?;
4401 }
4402
4403 Ok(dirty_coords)
4404 }
4405
4406 fn demote_spans_for_structural_op(
4416 &mut self,
4417 op: StructuralOp,
4418 affected_region: Region,
4419 ) -> Result<(), crate::engine::EditorError> {
4420 if op.count() == 0 {
4421 return Ok(());
4422 }
4423 self.demote_spans_for_structural_op_impl(Some(op), affected_region, true)
4424 }
4425
4426 fn demote_spans_for_structural_op_impl(
4427 &mut self,
4428 op: Option<StructuralOp>,
4429 affected_region: Region,
4430 clear_computed_overlays: bool,
4431 ) -> Result<(), crate::engine::EditorError> {
4432 struct SpanPlan {
4433 span_ref: FormulaSpanRef,
4434 sheet_id: SheetId,
4435 ast: ASTNode,
4436 origin_row: u32,
4437 origin_col: u32,
4438 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4439 placements: Vec<(u32, u32)>,
4440 }
4441
4442 fn substitute_literal_slots_for_template_placement(
4443 ast: &ASTNode,
4444 binding: &[LiteralValue],
4445 ) -> ASTNode {
4446 fn clone_with_slots(
4447 ast: &ASTNode,
4448 binding: &[LiteralValue],
4449 next: &mut usize,
4450 in_array: bool,
4451 ) -> ASTNode {
4452 let node_type = match &ast.node_type {
4453 ASTNodeType::Literal(_) if !in_array => {
4454 let value = binding.get(*next).cloned().unwrap_or(LiteralValue::Empty);
4455 *next = next.saturating_add(1);
4456 ASTNodeType::Literal(value)
4457 }
4458 ASTNodeType::Literal(value) => ASTNodeType::Literal(value.clone()),
4459 ASTNodeType::Reference {
4460 original,
4461 reference,
4462 } => ASTNodeType::Reference {
4463 original: original.clone(),
4464 reference: reference.clone(),
4465 },
4466 ASTNodeType::UnaryOp { op, expr } => ASTNodeType::UnaryOp {
4467 op: op.clone(),
4468 expr: Box::new(clone_with_slots(expr, binding, next, in_array)),
4469 },
4470 ASTNodeType::BinaryOp { op, left, right } => ASTNodeType::BinaryOp {
4471 op: op.clone(),
4472 left: Box::new(clone_with_slots(left, binding, next, in_array)),
4473 right: Box::new(clone_with_slots(right, binding, next, in_array)),
4474 },
4475 ASTNodeType::Function { name, args } => ASTNodeType::Function {
4476 name: name.clone(),
4477 args: args
4478 .iter()
4479 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4480 .collect(),
4481 },
4482 ASTNodeType::Call { callee, args } => ASTNodeType::Call {
4483 callee: Box::new(clone_with_slots(callee, binding, next, in_array)),
4484 args: args
4485 .iter()
4486 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4487 .collect(),
4488 },
4489 ASTNodeType::Array(rows) => ASTNodeType::Array(
4490 rows.iter()
4491 .map(|row| {
4492 row.iter()
4493 .map(|cell| clone_with_slots(cell, binding, next, true))
4494 .collect()
4495 })
4496 .collect(),
4497 ),
4498 };
4499 ASTNode::new(node_type, ast.source_token.clone())
4500 }
4501 let mut next = 0usize;
4502 clone_with_slots(ast, binding, &mut next, false)
4503 }
4504
4505 let span_refs = self.graph.formula_authority().active_span_refs();
4506 if span_refs.is_empty() {
4507 return Ok(());
4508 }
4509 let dirty_span_coords = if clear_computed_overlays {
4510 FxHashSet::default()
4511 } else {
4512 self.compute_current_formula_plane_dirty_result_coords()?
4513 };
4514
4515 struct ShiftPlan {
4516 span_ref: FormulaSpanRef,
4517 template_id: crate::formula_plane::ids::FormulaTemplateId,
4518 new_origin_row: u32,
4519 new_origin_col: u32,
4520 new_domain: crate::formula_plane::runtime::PlacementDomain,
4521 new_read_summary: Option<SpanReadSummary>,
4522 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4523 force_binding_residual_axes: bool,
4524 }
4525
4526 fn checked_shift_u32(value: u32, delta: i64) -> Option<u32> {
4527 u32::try_from(i64::from(value).checked_add(delta)?).ok()
4528 }
4529
4530 fn shifted_read_summary(
4531 read_summary: &SpanReadSummary,
4532 new_result_region: Region,
4533 op: StructuralOp,
4534 row_delta: i64,
4535 col_delta: i64,
4536 ) -> Option<SpanReadSummary> {
4537 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4538 for dependency in &read_summary.dependencies {
4539 let read_region = match op.classify_region(dependency.read_region) {
4540 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4541 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4542 dependency.read_region
4543 }
4544 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4545 ..
4546 } => dependency
4547 .read_region
4548 .project_through_axis_shift(row_delta, col_delta)?,
4549 crate::formula_plane::structural_shift::AxisShiftCase::Straddles
4550 | crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4551 return None;
4552 }
4553 };
4554 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4555 read_region,
4556 projection: dependency.projection,
4557 });
4558 }
4559 Some(SpanReadSummary {
4560 result_region: new_result_region,
4561 dependencies,
4562 })
4563 }
4564
4565 fn compact_axis_through_delete(
4566 min: u32,
4567 max: u32,
4568 start: u32,
4569 count: u32,
4570 ) -> Option<(u32, u32)> {
4571 let end = start.saturating_add(count);
4572 if max < start || min >= end {
4573 return Some((min.saturating_sub(count), max.saturating_sub(count)));
4574 }
4575 let keeps_left = min < start;
4576 let keeps_right = max >= end;
4577 match (keeps_left, keeps_right) {
4578 (false, false) => None,
4579 (true, false) => Some((min, start.checked_sub(1)?)),
4580 (false, true) => Some((start, max.checked_sub(count)?)),
4581 (true, true) => Some((min, max.checked_sub(count)?)),
4582 }
4583 }
4584
4585 fn compact_domain_through_delete(
4586 domain: &PlacementDomain,
4587 op: StructuralOp,
4588 ) -> Option<PlacementDomain> {
4589 match (domain, op) {
4590 (
4591 PlacementDomain::RowRun {
4592 sheet_id,
4593 row_start,
4594 row_end,
4595 col,
4596 },
4597 StructuralOp::DeleteRows { start, count, .. },
4598 ) => {
4599 let (row_start, row_end) =
4600 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4601 Some(PlacementDomain::row_run(
4602 *sheet_id, row_start, row_end, *col,
4603 ))
4604 }
4605 (
4606 PlacementDomain::Rect {
4607 sheet_id,
4608 row_start,
4609 row_end,
4610 col_start,
4611 col_end,
4612 },
4613 StructuralOp::DeleteRows { start, count, .. },
4614 ) => {
4615 let (row_start, row_end) =
4616 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4617 Some(PlacementDomain::rect(
4618 *sheet_id, row_start, row_end, *col_start, *col_end,
4619 ))
4620 }
4621 (
4622 PlacementDomain::ColRun {
4623 sheet_id,
4624 row,
4625 col_start,
4626 col_end,
4627 },
4628 StructuralOp::DeleteColumns { start, count, .. },
4629 ) => {
4630 let (col_start, col_end) =
4631 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4632 Some(PlacementDomain::col_run(
4633 *sheet_id, *row, col_start, col_end,
4634 ))
4635 }
4636 (
4637 PlacementDomain::Rect {
4638 sheet_id,
4639 row_start,
4640 row_end,
4641 col_start,
4642 col_end,
4643 },
4644 StructuralOp::DeleteColumns { start, count, .. },
4645 ) => {
4646 let (col_start, col_end) =
4647 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4648 Some(PlacementDomain::rect(
4649 *sheet_id, *row_start, *row_end, col_start, col_end,
4650 ))
4651 }
4652 _ => None,
4653 }
4654 }
4655
4656 fn compact_axis_range_through_delete(
4657 axis: crate::formula_plane::region_index::AxisRange,
4658 start: u32,
4659 count: u32,
4660 ) -> Option<crate::formula_plane::region_index::AxisRange> {
4661 use crate::formula_plane::region_index::AxisRange;
4662 match axis {
4663 AxisRange::Point(point) => compact_axis_through_delete(point, point, start, count)
4664 .map(|(point, _)| AxisRange::Point(point)),
4665 AxisRange::Span(min, max) => compact_axis_through_delete(min, max, start, count)
4666 .map(|(min, max)| AxisRange::Span(min, max)),
4667 AxisRange::All => Some(AxisRange::All),
4668 AxisRange::From(_) | AxisRange::To(_) => None,
4669 }
4670 }
4671
4672 fn compact_region_through_delete(region: Region, op: StructuralOp) -> Option<Region> {
4673 let (rows, cols) = region.axis_ranges();
4674 match op {
4675 StructuralOp::DeleteRows {
4676 sheet_id,
4677 start,
4678 count,
4679 } if region.sheet_id() == sheet_id => Some(Region {
4680 sheet_id,
4681 rows: compact_axis_range_through_delete(rows, start, count)?,
4682 cols,
4683 }),
4684 StructuralOp::DeleteColumns {
4685 sheet_id,
4686 start,
4687 count,
4688 } if region.sheet_id() == sheet_id => Some(Region {
4689 sheet_id,
4690 rows,
4691 cols: compact_axis_range_through_delete(cols, start, count)?,
4692 }),
4693 _ => Some(region),
4694 }
4695 }
4696
4697 fn compact_read_summary_through_delete(
4698 read_summary: &SpanReadSummary,
4699 new_result_region: Region,
4700 op: StructuralOp,
4701 ) -> Option<SpanReadSummary> {
4702 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4703 for dependency in &read_summary.dependencies {
4704 let read_region = match op.classify_region(dependency.read_region) {
4705 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4706 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4707 dependency.read_region
4708 }
4709 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4710 ..
4711 } => {
4712 let (row_delta, col_delta) = op.axis_shift_delta();
4713 dependency
4714 .read_region
4715 .project_through_axis_shift(row_delta, col_delta)?
4716 }
4717 crate::formula_plane::structural_shift::AxisShiftCase::Straddles => {
4718 compact_region_through_delete(dependency.read_region, op)?
4719 }
4720 crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4721 return None;
4722 }
4723 };
4724 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4725 read_region,
4726 projection: dependency.projection,
4727 });
4728 }
4729 Some(SpanReadSummary {
4730 result_region: new_result_region,
4731 dependencies,
4732 })
4733 }
4734
4735 fn domain_origin_1_based(domain: &PlacementDomain) -> (u32, u32) {
4736 match domain {
4737 PlacementDomain::RowRun { row_start, col, .. } => (row_start + 1, col + 1),
4738 PlacementDomain::ColRun { row, col_start, .. } => (row + 1, col_start + 1),
4739 PlacementDomain::Rect {
4740 row_start,
4741 col_start,
4742 ..
4743 } => (row_start + 1, col_start + 1),
4744 }
4745 }
4746
4747 let mut shift_plans = Vec::new();
4748 let mut remove_refs = Vec::new();
4749 let mut demote_refs = Vec::new();
4750 for span_ref in span_refs {
4751 let authority = self.graph.formula_authority();
4752 let Some(span) = authority.plane.spans.get(span_ref) else {
4753 continue;
4754 };
4755 let read_summary = span
4756 .read_summary_id
4757 .and_then(|id| authority.plane.span_read_summaries.get(id));
4758 let Some(op) = op else {
4759 let result_region_affected =
4764 Self::span_result_region_intersects_affected(span, &affected_region);
4765 let read_region_affected = Self::span_any_read_region_intersects_affected(
4766 &authority.plane,
4767 span,
4768 &affected_region,
4769 );
4770 if result_region_affected || read_region_affected {
4771 demote_refs.push(span_ref);
4772 }
4773 continue;
4774 };
4775 match classify_span_for_op(span, read_summary, op) {
4776 SpanShiftPlan::NoOp => {}
4777 SpanShiftPlan::Remove => {
4778 remove_refs.push(span_ref);
4779 }
4780 SpanShiftPlan::Demote {
4781 reason:
4782 crate::formula_plane::structural_shift::SpanDemoteReason::DeletePartiallyOverlaps,
4783 } => {
4784 let binding_compaction_safe = span
4785 .binding_set_id
4786 .and_then(|id| authority.plane.binding_sets.get(id))
4787 .is_none_or(|binding_set| binding_set.is_single_literal_binding());
4788 if binding_compaction_safe
4789 && let Some(new_domain) = compact_domain_through_delete(&span.domain, op)
4790 {
4791 let new_result_region = Region::from_domain(&new_domain);
4792 let new_read_summary = if let Some(summary) = read_summary {
4793 compact_read_summary_through_delete(summary, new_result_region, op)
4794 } else {
4795 None
4796 };
4797 if read_summary.is_none() || new_read_summary.is_some() {
4798 let (new_origin_row, new_origin_col) = domain_origin_1_based(&new_domain);
4799 let Some(template) = authority.plane.templates.get(span.template_id)
4800 else {
4801 return Err(ExcelError::new(ExcelErrorKind::Ref)
4802 .with_message(
4803 "FormulaPlane delete compaction found a span with a missing template",
4804 )
4805 .into());
4806 };
4807 let force_binding_residual_axes = span
4808 .binding_set_id
4809 .and_then(|id| authority.plane.binding_sets.get(id))
4810 .is_some_and(|binding_set| {
4811 !binding_set.value_ref_slots.is_empty()
4812 && (new_origin_row != template.origin_row
4813 || new_origin_col != template.origin_col)
4814 });
4815 shift_plans.push(ShiftPlan {
4816 span_ref,
4817 template_id: span.template_id,
4818 new_origin_row,
4819 new_origin_col,
4820 new_domain,
4821 new_read_summary,
4822 binding_set_id: span.binding_set_id,
4823 force_binding_residual_axes,
4824 });
4825 } else {
4826 demote_refs.push(span_ref);
4827 }
4828 } else {
4829 demote_refs.push(span_ref);
4830 }
4831 }
4832 SpanShiftPlan::Demote { .. } => {
4833 demote_refs.push(span_ref);
4834 }
4835 SpanShiftPlan::Shift {
4836 row_delta,
4837 col_delta,
4838 origin_row_delta,
4839 origin_col_delta,
4840 } => {
4841 let Some(template) = authority.plane.templates.get(span.template_id) else {
4842 return Err(ExcelError::new(ExcelErrorKind::Ref)
4843 .with_message("FormulaPlane shift found a span with a missing template")
4844 .into());
4845 };
4846 let Some(new_origin_row) =
4847 checked_shift_u32(template.origin_row, origin_row_delta)
4848 else {
4849 return Err(ExcelError::new(ExcelErrorKind::Ref)
4850 .with_message("FormulaPlane shift overflowed template origin row")
4851 .into());
4852 };
4853 let Some(new_origin_col) =
4854 checked_shift_u32(template.origin_col, origin_col_delta)
4855 else {
4856 return Err(ExcelError::new(ExcelErrorKind::Ref)
4857 .with_message("FormulaPlane shift overflowed template origin column")
4858 .into());
4859 };
4860 let Some(new_domain) =
4861 span.domain.project_through_axis_shift(row_delta, col_delta)
4862 else {
4863 return Err(ExcelError::new(ExcelErrorKind::Ref)
4864 .with_message("FormulaPlane shift overflowed span domain")
4865 .into());
4866 };
4867 let new_result_region = Region::from_domain(&new_domain);
4868 let new_read_summary = if let Some(summary) = read_summary {
4869 Some(
4870 shifted_read_summary(
4871 summary,
4872 new_result_region,
4873 op,
4874 row_delta,
4875 col_delta,
4876 )
4877 .ok_or_else(|| {
4878 ExcelError::new(ExcelErrorKind::Ref).with_message(
4879 "FormulaPlane shift could not project read summary",
4880 )
4881 })?,
4882 )
4883 } else {
4884 None
4885 };
4886 let force_binding_residual_axes = span
4887 .binding_set_id
4888 .and_then(|id| authority.plane.binding_sets.get(id))
4889 .is_some_and(|binding_set| {
4890 !binding_set.value_ref_slots.is_empty()
4891 && (origin_row_delta != 0 || origin_col_delta != 0)
4892 });
4893 shift_plans.push(ShiftPlan {
4894 span_ref,
4895 template_id: span.template_id,
4896 new_origin_row,
4897 new_origin_col,
4898 new_domain,
4899 new_read_summary,
4900 binding_set_id: span.binding_set_id,
4901 force_binding_residual_axes,
4902 });
4903 }
4904 }
4905 }
4906 if !shift_plans.is_empty() || !remove_refs.is_empty() {
4907 let authority = self.graph.formula_authority_mut();
4908 for span_ref in remove_refs {
4909 authority.plane.remove_overlays_for_source_span(span_ref);
4910 authority.plane.remove_span(span_ref);
4911 }
4912 for plan in shift_plans {
4913 let Some(template_id) = authority.plane.intern_shifted_template_origin(
4914 plan.template_id,
4915 plan.new_origin_row,
4916 plan.new_origin_col,
4917 ) else {
4918 return Err(ExcelError::new(ExcelErrorKind::Ref)
4919 .with_message("FormulaPlane shift could not clone template origin")
4920 .into());
4921 };
4922 if let Some(binding_set_id) = plan.binding_set_id {
4923 let Some(template) = authority.plane.templates.get(template_id) else {
4924 return Err(ExcelError::new(ExcelErrorKind::Ref)
4925 .with_message("FormulaPlane shift could not find shifted template")
4926 .into());
4927 };
4928 let (ast_id, origin_row, origin_col) =
4929 (template.ast_id, template.origin_row, template.origin_col);
4930 authority.plane.set_binding_template_anchor(
4931 binding_set_id,
4932 ast_id,
4933 origin_row,
4934 origin_col,
4935 );
4936 }
4937 let read_summary_id = plan
4938 .new_read_summary
4939 .map(|summary| authority.plane.insert_span_read_summary(summary));
4940 let result_region = ResultRegion::scalar_cells(plan.new_domain.clone());
4941 if !authority.plane.replace_span_geometry(
4942 plan.span_ref,
4943 template_id,
4944 plan.new_domain,
4945 result_region,
4946 read_summary_id,
4947 ) {
4948 return Err(ExcelError::new(ExcelErrorKind::Ref)
4949 .with_message("FormulaPlane shift could not update span geometry")
4950 .into());
4951 }
4952 if plan.force_binding_residual_axes
4953 && let Some(binding_set_id) = plan.binding_set_id
4954 {
4955 authority.plane.force_binding_residual_axes(binding_set_id);
4964 }
4965 }
4966 authority.rebuild_indexes();
4967 self.formula_plane_indexes_epoch_seen = 0;
4968 }
4969
4970 let mut span_plans = Vec::new();
4971 for span_ref in demote_refs {
4972 let authority = self.graph.formula_authority();
4973 let Some(span) = authority.plane.spans.get(span_ref) else {
4974 continue;
4975 };
4976 let Some(template) = authority.plane.templates.get(span.template_id) else {
4977 return Err(ExcelError::new(ExcelErrorKind::Ref)
4978 .with_message("FormulaPlane demotion found a span with a missing template")
4979 .into());
4980 };
4981 let ast = self
4982 .graph
4983 .data_store()
4984 .retrieve_ast(template.ast_id, self.graph.sheet_reg())
4985 .ok_or_else(|| {
4986 ExcelError::new(ExcelErrorKind::Ref)
4987 .with_message("FormulaPlane demotion could not retrieve the template AST")
4988 })?;
4989 let placements = span
4990 .domain
4991 .iter()
4992 .map(|placement| (placement.row + 1, placement.col + 1))
4993 .collect();
4994 span_plans.push(SpanPlan {
4995 span_ref,
4996 sheet_id: span.sheet_id,
4997 ast,
4998 origin_row: template.origin_row,
4999 origin_col: template.origin_col,
5000 binding_set_id: span.binding_set_id,
5001 placements,
5002 });
5003 }
5004 if span_plans.is_empty() {
5005 return Ok(());
5006 }
5007
5008 let mut relocated = Vec::new();
5009 let mut placement_cells = Vec::new();
5010 for plan in &span_plans {
5011 for &(row, col) in &plan.placements {
5012 let row_delta = i64::from(row) - i64::from(plan.origin_row);
5013 let col_delta = i64::from(col) - i64::from(plan.origin_col);
5014 let bound_ast = if let Some(binding_set_id) = plan.binding_set_id {
5015 let authority = self.graph.formula_authority();
5016 if let Some(binding_set) = authority.plane.binding_sets.get(binding_set_id) {
5017 if binding_set.is_single_literal_binding() {
5018 plan.ast.clone()
5019 } else {
5020 let placement = crate::formula_plane::runtime::PlacementCoord::new(
5021 plan.sheet_id,
5022 row.saturating_sub(1),
5023 col.saturating_sub(1),
5024 );
5025 let binding =
5026 authority.plane.spans.get(plan.span_ref).and_then(|span| {
5027 binding_set
5028 .literal_bindings_for_placement(&span.domain, placement)
5029 });
5030 if let Some(binding) = binding {
5031 substitute_literal_slots_for_template_placement(
5032 &plan.ast,
5033 binding.as_ref(),
5034 )
5035 } else {
5036 plan.ast.clone()
5037 }
5038 }
5039 } else {
5040 plan.ast.clone()
5041 }
5042 } else {
5043 plan.ast.clone()
5044 };
5045 let ast = relocate_ast_for_template_placement(&bound_ast, row_delta, col_delta)?;
5046 relocated.push((plan.sheet_id, row, col, ast));
5047 placement_cells.push((plan.sheet_id, row, col));
5048 }
5049 }
5050 let planned_by_sheet = {
5051 let mut pipeline = self.ingest_pipeline();
5052 let mut planned_by_sheet: BTreeMap<
5053 SheetId,
5054 Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
5055 > = BTreeMap::new();
5056 for (formula_sheet_id, row, col, ast) in relocated {
5057 let placement =
5058 CellRef::new(formula_sheet_id, Coord::from_excel(row, col, true, true));
5059 let ingested =
5060 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?;
5061 planned_by_sheet.entry(formula_sheet_id).or_default().push((
5062 row,
5063 col,
5064 ingested.ast_id,
5065 ingested.dep_plan,
5066 ));
5067 }
5068 planned_by_sheet
5069 };
5070 {
5071 let authority = self.graph.formula_authority_mut();
5072 for plan in &span_plans {
5073 authority
5074 .plane
5075 .remove_overlays_for_source_span(plan.span_ref);
5076 authority.plane.remove_span(plan.span_ref);
5077 }
5078 authority.rebuild_indexes();
5079 }
5080 if clear_computed_overlays {
5081 self.clear_computed_overlay_cells_in_region(&placement_cells, &affected_region);
5088 }
5089 for (formula_sheet_id, planned) in planned_by_sheet {
5090 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
5091 self.graph
5092 .bulk_set_formulas_with_plans(&sheet_name, planned)?;
5093 }
5094 if !clear_computed_overlays {
5095 for (formula_sheet_id, row, col) in &placement_cells {
5096 let row0 = row.saturating_sub(1);
5097 let col0 = col.saturating_sub(1);
5098 if dirty_span_coords.contains(&(*formula_sheet_id, row0, col0)) {
5099 continue;
5100 }
5101 let cell =
5102 CellRef::new(*formula_sheet_id, Coord::from_excel(*row, *col, true, true));
5103 if let Some(&vertex_id) = self.graph.get_vertex_id_for_address(&cell) {
5104 self.graph.set_dirty(vertex_id, false);
5105 }
5106 }
5107 }
5108 self.formula_plane_indexes_epoch_seen = 0;
5109 Ok(())
5110 }
5111
5112 fn collect_cyclic_span_refs(
5117 &self,
5118 schedule: &MixedSchedule,
5119 span_refs_by_id: &BTreeMap<FormulaSpanId, FormulaSpanRef>,
5120 ) -> Vec<FormulaSpanRef> {
5121 let mut refs = Vec::new();
5122 for fallback in &schedule.fallbacks {
5123 if fallback.reason != MixedScheduleFallbackReason::CycleDetected {
5124 continue;
5125 }
5126 if let FormulaProducerId::Span(span_id) = fallback.producer
5127 && let Some(span_ref) = span_refs_by_id.get(&span_id)
5128 && !refs.contains(span_ref)
5129 {
5130 refs.push(*span_ref);
5131 }
5132 }
5133 refs
5134 }
5135
5136 fn demote_cyclic_spans(&mut self, span_refs: &[FormulaSpanRef]) -> Result<(), ExcelError> {
5144 let mut regions: Vec<Region> = Vec::new();
5145 {
5146 let authority = self.graph.formula_authority();
5147 for span_ref in span_refs {
5148 if let Some(span) = authority.plane.spans.get(*span_ref) {
5149 regions.push(Region::from_domain(&span.domain));
5150 }
5151 }
5152 }
5153 for region in regions {
5154 self.demote_spans_preserving_computed_overlays(region.sheet_id(), region)
5155 .map_err(|err| {
5156 ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
5157 "FormulaPlane cycle-member span demotion failed: {err:?}"
5158 ))
5159 })?;
5160 }
5161 self.formula_plane_cycle_member_span_demotions = self
5162 .formula_plane_cycle_member_span_demotions
5163 .saturating_add(span_refs.len() as u64);
5164 Self::record_shadow_fallback_reason(
5168 &mut self.formula_ingest_report_total,
5169 PlacementFallbackReason::CycleMember,
5170 span_refs.len() as u64,
5171 );
5172 Ok(())
5173 }
5174
5175 fn evaluate_legacy_cycle_prepass(&mut self) -> Result<usize, ExcelError> {
5190 let dirty = self.graph.get_evaluation_vertices();
5191 if dirty.is_empty() {
5192 return Ok(0);
5193 }
5194 let (schedule, _vdeps, _meta) = self.create_evaluation_schedule(&dirty)?;
5195 let dirty_set: FxHashSet<VertexId> = dirty.iter().copied().collect();
5196 let mut cycle_errors = 0usize;
5197 let mut stamped_vertices: Vec<VertexId> = Vec::new();
5198 for &unit in &schedule.units {
5199 let ScheduleUnit::Cycle(i) = unit else {
5200 continue;
5201 };
5202 let members = schedule.unit_cycle(i);
5203 let stamped = self.handle_cycle_unit(members, None, Some(&dirty_set), None)?;
5204 if stamped > 0 {
5205 cycle_errors += 1;
5206 }
5207 stamped_vertices.extend(members.iter().copied());
5208 }
5209 if !stamped_vertices.is_empty() {
5214 self.graph.clear_dirty_flags(&stamped_vertices);
5215 }
5216 Ok(cycle_errors)
5217 }
5218
5219 pub fn insert_rows(
5221 &mut self,
5222 sheet: &str,
5223 before: u32,
5224 count: u32,
5225 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5226 {
5227 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5228 let sheet_id = self.ensure_known_sheet_id(sheet)?;
5229 let before0 = before.saturating_sub(1);
5230 let affected_region = Self::structural_row_region(sheet_id, before0);
5231 let op = StructuralOp::InsertRows {
5232 sheet_id,
5233 before: before0,
5234 count,
5235 };
5236 self.demote_spans_for_structural_op(op, affected_region)?;
5237 let summary = {
5238 let mut editor = VertexEditor::new(&mut self.graph);
5239 editor.insert_rows(sheet_id, before0, count)?
5240 };
5241 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5242 let before0 = before0 as usize;
5243 asheet.insert_rows(before0, count as usize);
5244 }
5245 self.mark_moved_formula_vertices_dirty(&summary);
5246 self.clear_computed_overlay_after_row(sheet, before0 as usize);
5247 self.shift_row_visibility_insert(sheet_id, before0, count);
5248 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5249 self.mark_topology_edited();
5250 Ok(summary)
5251 }
5252
5253 pub fn delete_rows(
5255 &mut self,
5256 sheet: &str,
5257 start: u32,
5258 count: u32,
5259 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5260 {
5261 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5262 let sheet_id = self.ensure_known_sheet_id(sheet)?;
5263 let start0 = start.saturating_sub(1);
5264 let affected_region = Self::structural_row_region(sheet_id, start0);
5265 let op = StructuralOp::DeleteRows {
5266 sheet_id,
5267 start: start0,
5268 count,
5269 };
5270 self.demote_spans_for_structural_op(op, affected_region)?;
5271 let summary = {
5272 let mut editor = VertexEditor::new(&mut self.graph);
5273 editor.delete_rows(sheet_id, start0, count)?
5274 };
5275 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5276 let start0 = start0 as usize;
5277 asheet.delete_rows(start0, count as usize);
5278 }
5279 self.mark_moved_formula_vertices_dirty(&summary);
5280 self.clear_computed_overlay_after_row(sheet, start0 as usize);
5281 self.shift_row_visibility_delete(sheet_id, start0, count);
5282 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5283 self.mark_topology_edited();
5284 Ok(summary)
5285 }
5286
5287 pub fn insert_columns(
5289 &mut self,
5290 sheet: &str,
5291 before: u32,
5292 count: u32,
5293 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5294 {
5295 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5296 let sheet_id = self.graph.sheet_id(sheet).ok_or(
5297 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
5298 name: sheet.to_string(),
5299 reason: "Unknown sheet".to_string(),
5300 },
5301 )?;
5302 let before0 = before.saturating_sub(1);
5303 let affected_region = Self::structural_col_region(sheet_id, before0);
5304 let op = StructuralOp::InsertColumns {
5305 sheet_id,
5306 before: before0,
5307 count,
5308 };
5309 self.demote_spans_for_structural_op(op, affected_region)?;
5310 let summary = {
5311 let mut editor = VertexEditor::new(&mut self.graph);
5312 editor.insert_columns(sheet_id, before0, count)?
5313 };
5314 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5315 let before0 = before0 as usize;
5316 asheet.insert_columns(before0, count as usize);
5317 }
5318 self.mark_moved_formula_vertices_dirty(&summary);
5319 self.clear_computed_overlay_after_col(sheet, before0 as usize);
5320 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5321 self.mark_topology_edited();
5322 Ok(summary)
5323 }
5324
5325 pub fn delete_columns(
5327 &mut self,
5328 sheet: &str,
5329 start: u32,
5330 count: u32,
5331 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5332 {
5333 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5334 let sheet_id = self.graph.sheet_id(sheet).ok_or(
5335 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
5336 name: sheet.to_string(),
5337 reason: "Unknown sheet".to_string(),
5338 },
5339 )?;
5340 let start0 = start.saturating_sub(1);
5341 let affected_region = Self::structural_col_region(sheet_id, start0);
5342 let op = StructuralOp::DeleteColumns {
5343 sheet_id,
5344 start: start0,
5345 count,
5346 };
5347 self.demote_spans_for_structural_op(op, affected_region)?;
5348 let summary = {
5349 let mut editor = VertexEditor::new(&mut self.graph);
5350 editor.delete_columns(sheet_id, start0, count)?
5351 };
5352 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5353 let start0 = start0 as usize;
5354 asheet.delete_columns(start0, count as usize);
5355 }
5356 self.mark_moved_formula_vertices_dirty(&summary);
5357 self.clear_computed_overlay_after_col(sheet, start0 as usize);
5358 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5359 self.mark_topology_edited();
5360 Ok(summary)
5361 }
5362 fn arrow_used_row_bounds(
5364 &self,
5365 sheet: &str,
5366 start_col: u32,
5367 end_col: u32,
5368 ) -> Option<(u32, u32)> {
5369 let a = self.sheet_store().sheet(sheet)?;
5370 if a.columns.is_empty() {
5371 return None;
5372 }
5373 let sc0 = start_col.saturating_sub(1) as usize;
5374 let ec0 = end_col.saturating_sub(1) as usize;
5375 let col_hi = a.columns.len().saturating_sub(1);
5376 if sc0 > col_hi {
5377 return None;
5378 }
5379 let ec0 = ec0.min(col_hi);
5380 let snap = self.data_snapshot_id();
5382 let mut min_r0: Option<usize> = None;
5383 for ci in sc0..=ec0 {
5384 let sheet_id = self.graph.sheet_id(sheet)?;
5385 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
5386 g.as_ref()
5387 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
5388 }) {
5389 let mv = mv as usize;
5390 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
5391 continue;
5392 }
5393 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
5395 if let Ok(mut g) = self.row_bounds_cache.write() {
5396 g.get_or_insert_with(|| RowBoundsCache::new(snap))
5397 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
5398 }
5399 if let Some(m) = min_c {
5400 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
5401 }
5402 }
5403 min_r0?;
5404 let mut max_r0: Option<usize> = None;
5405 for ci in sc0..=ec0 {
5406 let sheet_id = self.graph.sheet_id(sheet)?;
5407 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
5408 g.as_ref()
5409 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
5410 }) {
5411 let mv = mv as usize;
5412 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
5413 continue;
5414 }
5415 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
5416 if let Ok(mut g) = self.row_bounds_cache.write() {
5417 g.get_or_insert_with(|| RowBoundsCache::new(snap))
5418 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
5419 }
5420 if let Some(m) = max_c {
5421 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
5422 }
5423 }
5424 match (min_r0, max_r0) {
5425 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
5426 _ => None,
5427 }
5428 }
5429
5430 fn scan_column_used_bounds(
5431 a: &crate::arrow_store::ArrowSheet,
5432 ci: usize,
5433 ) -> (Option<u32>, Option<u32>) {
5434 let col = &a.columns[ci];
5435
5436 let mut min_r0: Option<u32> = None;
5438 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5439 let tags = chunk.type_tag.values();
5440 for (off, &t) in tags.iter().enumerate() {
5441 let overlay_non_empty = chunk
5442 .overlay
5443 .get(off)
5444 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5445 .unwrap_or(false)
5446 || chunk
5447 .computed_overlay
5448 .get(off)
5449 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5450 .unwrap_or(false);
5451 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5452 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5453 break;
5454 };
5455 let row0 = chunk_start + off;
5456 min_r0 = Some(row0 as u32);
5457 break;
5458 }
5459 }
5460 if min_r0.is_some() {
5461 break;
5462 }
5463 }
5464 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
5465 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5466 sparse_idxs.sort_unstable();
5467 for chunk_idx in sparse_idxs {
5468 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5469 continue;
5470 };
5471 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5472 continue;
5473 };
5474 let tags = chunk.type_tag.values();
5475 for (off, &t) in tags.iter().enumerate() {
5476 let overlay_non_empty = chunk
5477 .overlay
5478 .get(off)
5479 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5480 .unwrap_or(false)
5481 || chunk
5482 .computed_overlay
5483 .get(off)
5484 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5485 .unwrap_or(false);
5486 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5487 let row0 = chunk_start + off;
5488 min_r0 = Some(row0 as u32);
5489 break;
5490 }
5491 }
5492 if min_r0.is_some() {
5493 break;
5494 }
5495 }
5496 }
5497
5498 let mut max_r0: Option<u32> = None;
5500 if !col.sparse_chunks.is_empty() {
5501 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5502 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
5503 for chunk_idx in sparse_idxs {
5504 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5505 continue;
5506 };
5507 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5508 continue;
5509 };
5510 let tags = chunk.type_tag.values();
5511 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5512 let overlay_non_empty = chunk
5513 .overlay
5514 .get(rev_idx)
5515 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5516 .unwrap_or(false)
5517 || chunk
5518 .computed_overlay
5519 .get(rev_idx)
5520 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5521 .unwrap_or(false);
5522 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5523 let row0 = chunk_start + rev_idx;
5524 max_r0 = Some(row0 as u32);
5525 break;
5526 }
5527 }
5528 if max_r0.is_some() {
5529 break;
5530 }
5531 }
5532 }
5533 if max_r0.is_none() {
5534 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
5535 let tags = chunk.type_tag.values();
5536 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5537 let overlay_non_empty = chunk
5538 .overlay
5539 .get(rev_idx)
5540 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5541 .unwrap_or(false)
5542 || chunk
5543 .computed_overlay
5544 .get(rev_idx)
5545 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5546 .unwrap_or(false);
5547 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5548 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5549 break;
5550 };
5551 let row0 = chunk_start + rev_idx;
5552 max_r0 = Some(row0 as u32);
5553 break;
5554 }
5555 }
5556 if max_r0.is_some() {
5557 break;
5558 }
5559 }
5560 }
5561
5562 (min_r0, max_r0)
5563 }
5564
5565 fn arrow_used_col_bounds(
5567 &self,
5568 sheet: &str,
5569 start_row: u32,
5570 end_row: u32,
5571 ) -> Option<(u32, u32)> {
5572 let a = self.sheet_store().sheet(sheet)?;
5573 if a.columns.is_empty() {
5574 return None;
5575 }
5576 let sr0 = start_row.saturating_sub(1) as usize;
5577 let er0 = end_row.saturating_sub(1) as usize;
5578 if sr0 > er0 {
5579 return None;
5580 }
5581 let mut min_c0: Option<usize> = None;
5584 let mut max_c0: Option<usize> = None;
5585 for (ci, col) in a.columns.iter().enumerate() {
5587 let mut any_in_range = false;
5588
5589 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
5590 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5591 return false;
5592 };
5593 let chunk_len = chunk.type_tag.len();
5594 if chunk_len == 0 {
5595 return false;
5596 }
5597 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
5598 if sr0 > chunk_end || er0 < chunk_start {
5600 return false;
5601 }
5602 let start_off = sr0.max(chunk_start) - chunk_start;
5603 let end_off = er0.min(chunk_end) - chunk_start;
5604 let tags = chunk.type_tag.values();
5605 for off in start_off..=end_off {
5606 let overlay_non_empty = chunk
5607 .overlay
5608 .get(off)
5609 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5610 .unwrap_or(false)
5611 || chunk
5612 .computed_overlay
5613 .get(off)
5614 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5615 .unwrap_or(false);
5616 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
5617 return true;
5618 }
5619 }
5620 false
5621 };
5622
5623 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5624 if scan_chunk(chunk_idx, chunk) {
5625 any_in_range = true;
5626 break;
5627 }
5628 }
5629
5630 if !any_in_range && !col.sparse_chunks.is_empty() {
5631 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
5632 if scan_chunk(chunk_idx, chunk) {
5633 any_in_range = true;
5634 break;
5635 }
5636 }
5637 }
5638
5639 if any_in_range {
5640 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
5641 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
5642 }
5643 }
5644 match (min_c0, max_c0) {
5645 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
5646 _ => None,
5647 }
5648 }
5649
5650 fn formula_row_bounds_for_columns(
5651 &self,
5652 sheet: &str,
5653 start_col: u32,
5654 end_col: u32,
5655 ) -> Option<(u32, u32)> {
5656 let sheet_id = self.graph.sheet_id(sheet)?;
5657 let sc0 = start_col.saturating_sub(1);
5658 let ec0 = end_col.saturating_sub(1);
5659 let mut min_r0: Option<u32> = None;
5660 let mut max_r0: Option<u32> = None;
5661
5662 if let Some(index) = self.graph.sheet_index(sheet_id) {
5663 for vid in index.vertices_in_col_range(sc0, ec0) {
5664 if !matches!(
5665 self.graph.get_vertex_kind(vid),
5666 VertexKind::FormulaScalar | VertexKind::FormulaArray
5667 ) {
5668 continue;
5669 }
5670 let row0 = self.graph.vertex_coord(vid).row();
5671 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5672 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5673 }
5674 } else {
5675 for vid in self.graph.vertices_in_sheet(sheet_id) {
5676 if !matches!(
5677 self.graph.get_vertex_kind(vid),
5678 VertexKind::FormulaScalar | VertexKind::FormulaArray
5679 ) {
5680 continue;
5681 }
5682 let coord = self.graph.vertex_coord(vid);
5683 let col0 = coord.col();
5684 if col0 < sc0 || col0 > ec0 {
5685 continue;
5686 }
5687 let row0 = coord.row();
5688 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5689 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5690 }
5691 }
5692
5693 match (min_r0, max_r0) {
5694 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5695 _ => None,
5696 }
5697 }
5698
5699 fn formula_col_bounds_for_rows(
5700 &self,
5701 sheet: &str,
5702 start_row: u32,
5703 end_row: u32,
5704 ) -> Option<(u32, u32)> {
5705 let sheet_id = self.graph.sheet_id(sheet)?;
5706 let sr0 = start_row.saturating_sub(1);
5707 let er0 = end_row.saturating_sub(1);
5708 let mut min_c0: Option<u32> = None;
5709 let mut max_c0: Option<u32> = None;
5710
5711 if let Some(index) = self.graph.sheet_index(sheet_id) {
5712 for vid in index.vertices_in_row_range(sr0, er0) {
5713 if !matches!(
5714 self.graph.get_vertex_kind(vid),
5715 VertexKind::FormulaScalar | VertexKind::FormulaArray
5716 ) {
5717 continue;
5718 }
5719 let col0 = self.graph.vertex_coord(vid).col();
5720 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5721 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5722 }
5723 } else {
5724 for vid in self.graph.vertices_in_sheet(sheet_id) {
5725 if !matches!(
5726 self.graph.get_vertex_kind(vid),
5727 VertexKind::FormulaScalar | VertexKind::FormulaArray
5728 ) {
5729 continue;
5730 }
5731 let coord = self.graph.vertex_coord(vid);
5732 let row0 = coord.row();
5733 if row0 < sr0 || row0 > er0 {
5734 continue;
5735 }
5736 let col0 = coord.col();
5737 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5738 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5739 }
5740 }
5741
5742 match (min_c0, max_c0) {
5743 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5744 _ => None,
5745 }
5746 }
5747
5748 fn union_used_bounds(
5749 first: Option<(u32, u32)>,
5750 second: Option<(u32, u32)>,
5751 ) -> Option<(u32, u32)> {
5752 match (first, second) {
5753 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
5754 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
5755 (None, None) => None,
5756 }
5757 }
5758
5759 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
5762 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5763 return;
5764 }
5765 if self.arrow_sheets.sheet(sheet).is_none() {
5766 self.arrow_sheets
5767 .sheets
5768 .push(crate::arrow_store::ArrowSheet {
5769 name: std::sync::Arc::<str>::from(sheet),
5770 columns: Vec::new(),
5771 nrows: 0,
5772 chunk_starts: Vec::new(),
5773 chunk_rows: 32 * 1024,
5774 });
5775 }
5776
5777 let row0 = row.saturating_sub(1) as usize;
5778 let col0 = col.saturating_sub(1) as usize;
5779
5780 let asheet = self
5781 .arrow_sheets
5782 .sheet_mut(sheet)
5783 .expect("ArrowSheet must exist");
5784
5785 let cur_cols = asheet.columns.len();
5786 if col0 >= cur_cols {
5787 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
5788 }
5789
5790 if row0 >= asheet.nrows as usize {
5791 if asheet.columns.is_empty() {
5792 asheet.insert_columns(0, 1);
5793 }
5794 asheet.ensure_row_capacity(row0 + 1);
5795 }
5796 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
5797 use crate::arrow_store::OverlayValue;
5798 let ov = match value {
5799 LiteralValue::Empty => OverlayValue::Empty,
5800 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
5801 LiteralValue::Number(n) => OverlayValue::Number(*n),
5802 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
5803 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
5804 LiteralValue::Error(e) => {
5805 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
5806 }
5807 LiteralValue::Date(d) => {
5808 let dt = d.and_hms_opt(0, 0, 0).unwrap();
5809 let serial = crate::builtins::datetime::datetime_to_serial_for(
5810 self.config.date_system,
5811 &dt,
5812 );
5813 OverlayValue::DateTime(serial)
5814 }
5815 LiteralValue::DateTime(dt) => {
5816 let serial = crate::builtins::datetime::datetime_to_serial_for(
5817 self.config.date_system,
5818 dt,
5819 );
5820 OverlayValue::DateTime(serial)
5821 }
5822 LiteralValue::Time(t) => {
5823 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
5824 OverlayValue::DateTime(serial)
5825 }
5826 LiteralValue::Duration(d) => {
5827 let serial = d.num_seconds() as f64 / 86_400.0;
5828 OverlayValue::Duration(serial)
5829 }
5830 LiteralValue::Pending => OverlayValue::Pending,
5831 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
5832 formualizer_common::ExcelErrorKind::Value,
5833 )),
5834 };
5835 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
5836 let _ = ch.overlay.set(in_off, ov);
5837 ch.computed_overlay.remove(in_off)
5842 } else {
5843 return;
5844 };
5845 let abs_threshold = 1024usize;
5847 let frac_den = 50usize;
5848 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
5849 if freed > 0 {
5850 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
5851 }
5852 self.adjust_computed_overlay_bytes(computed_delta);
5853 }
5854 }
5855
5856 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
5861 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5862 return;
5863 }
5864 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5865 return;
5866 };
5867 let row0 = row.saturating_sub(1) as usize;
5868 let col0 = col.saturating_sub(1) as usize;
5869 if row0 >= asheet.nrows as usize {
5870 return;
5871 }
5872 if col0 >= asheet.columns.len() {
5873 return;
5874 }
5875 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
5876 return;
5877 };
5878 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
5879 let _ = ch.overlay.remove(in_off);
5880 }
5881 }
5882
5883 fn clear_computed_overlay_col_row_range(
5884 &mut self,
5885 sheet: &str,
5886 col0: usize,
5887 start_row0: usize,
5888 end_row0_exclusive: usize,
5889 ) {
5890 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
5891 return;
5892 }
5893 if start_row0 >= end_row0_exclusive {
5894 return;
5895 }
5896
5897 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5898 return;
5899 };
5900 if col0 >= asheet.columns.len() || start_row0 >= asheet.nrows as usize {
5901 return;
5902 }
5903 let end_row0_exclusive = end_row0_exclusive.min(asheet.nrows as usize);
5904 if start_row0 >= end_row0_exclusive {
5905 return;
5906 }
5907
5908 let starts = asheet.chunk_starts.clone();
5909 let nrows = asheet.nrows as usize;
5910 let mut delta = 0isize;
5911 let Some(col) = asheet.columns.get_mut(col0) else {
5912 return;
5913 };
5914 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
5915 let Some(&chunk_start) = starts.get(chunk_idx) else {
5916 continue;
5917 };
5918 let chunk_end = starts
5919 .get(chunk_idx + 1)
5920 .copied()
5921 .unwrap_or(nrows)
5922 .min(chunk_start.saturating_add(ch.len()));
5923 let clear_start = start_row0.max(chunk_start);
5924 let clear_end = end_row0_exclusive.min(chunk_end);
5925 if clear_start >= clear_end {
5926 continue;
5927 }
5928 if clear_start == chunk_start && clear_end == chunk_end {
5929 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5930 } else {
5931 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5932 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5933 delta = delta.saturating_add(
5934 ch.computed_overlay
5935 .remove_range(start_in_chunk..end_in_chunk),
5936 );
5937 }
5938 }
5939 for (chunk_idx, ch) in &mut col.sparse_chunks {
5940 let Some(&chunk_start) = starts.get(*chunk_idx) else {
5941 continue;
5942 };
5943 let chunk_end = starts
5944 .get(*chunk_idx + 1)
5945 .copied()
5946 .unwrap_or(nrows)
5947 .min(chunk_start.saturating_add(ch.len()));
5948 let clear_start = start_row0.max(chunk_start);
5949 let clear_end = end_row0_exclusive.min(chunk_end);
5950 if clear_start >= clear_end {
5951 continue;
5952 }
5953 if clear_start == chunk_start && clear_end == chunk_end {
5954 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5955 } else {
5956 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5957 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5958 delta = delta.saturating_add(
5959 ch.computed_overlay
5960 .remove_range(start_in_chunk..end_in_chunk),
5961 );
5962 }
5963 }
5964 self.adjust_computed_overlay_bytes(delta);
5965 }
5966
5967 fn clear_computed_overlay_cells_in_region(
5968 &mut self,
5969 cells: &[(SheetId, u32, u32)],
5970 affected_region: &Region,
5971 ) {
5972 let mut by_col: BTreeMap<(SheetId, u32), Vec<u32>> = BTreeMap::new();
5973 for (formula_sheet_id, row, col) in cells {
5974 let row0 = row.saturating_sub(1);
5975 let col0 = col.saturating_sub(1);
5976 let placement_region = Region::point(*formula_sheet_id, row0, col0);
5977 if placement_region.intersects(affected_region) {
5978 by_col
5979 .entry((*formula_sheet_id, col0))
5980 .or_default()
5981 .push(row0);
5982 }
5983 }
5984
5985 for ((formula_sheet_id, col0), mut rows) in by_col {
5986 rows.sort_unstable();
5987 rows.dedup();
5988 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
5989 let mut start = rows[0];
5990 let mut prev = rows[0];
5991 for row in rows.into_iter().skip(1) {
5992 if row == prev.saturating_add(1) {
5993 prev = row;
5994 continue;
5995 }
5996 self.clear_computed_overlay_col_row_range(
5997 &sheet_name,
5998 col0 as usize,
5999 start as usize,
6000 prev.saturating_add(1) as usize,
6001 );
6002 start = row;
6003 prev = row;
6004 }
6005 self.clear_computed_overlay_col_row_range(
6006 &sheet_name,
6007 col0 as usize,
6008 start as usize,
6009 prev.saturating_add(1) as usize,
6010 );
6011 }
6012 }
6013
6014 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
6015 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
6016 return;
6017 }
6018
6019 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
6020 return;
6021 };
6022 if start_row0 >= asheet.nrows as usize {
6023 return;
6024 }
6025
6026 let starts = asheet.chunk_starts.clone();
6027 let nrows = asheet.nrows as usize;
6028 let mut delta = 0isize;
6029 for col in &mut asheet.columns {
6030 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
6031 let Some(&chunk_start) = starts.get(chunk_idx) else {
6032 continue;
6033 };
6034 let chunk_end = starts
6035 .get(chunk_idx + 1)
6036 .copied()
6037 .unwrap_or(nrows)
6038 .min(chunk_start.saturating_add(ch.len()));
6039 if chunk_end <= start_row0 {
6040 continue;
6041 }
6042 if chunk_start >= start_row0 {
6043 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6044 } else {
6045 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
6046 delta = delta
6047 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
6048 }
6049 }
6050
6051 for (chunk_idx, ch) in &mut col.sparse_chunks {
6052 let Some(&chunk_start) = starts.get(*chunk_idx) else {
6053 continue;
6054 };
6055 let chunk_end = starts
6056 .get(*chunk_idx + 1)
6057 .copied()
6058 .unwrap_or(nrows)
6059 .min(chunk_start.saturating_add(ch.len()));
6060 if chunk_end <= start_row0 {
6061 continue;
6062 }
6063 if chunk_start >= start_row0 {
6064 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6065 } else {
6066 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
6067 delta = delta
6068 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
6069 }
6070 }
6071 }
6072 self.adjust_computed_overlay_bytes(delta);
6073 }
6074
6075 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
6076 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
6077 return;
6078 }
6079
6080 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
6081 return;
6082 };
6083 if start_col0 >= asheet.columns.len() {
6084 return;
6085 }
6086
6087 let mut delta = 0isize;
6088 for col in asheet.columns.iter_mut().skip(start_col0) {
6089 for ch in &mut col.chunks {
6090 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6091 }
6092 for ch in col.sparse_chunks.values_mut() {
6093 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6094 }
6095 }
6096 self.adjust_computed_overlay_bytes(delta);
6097 }
6098
6099 #[inline]
6100 fn literal_to_overlay_value(&self, value: &LiteralValue) -> crate::arrow_store::OverlayValue {
6101 use crate::arrow_store::OverlayValue;
6102 match value {
6103 LiteralValue::Empty => OverlayValue::Empty,
6104 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
6105 LiteralValue::Number(n) => OverlayValue::Number(*n),
6106 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
6107 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
6108 LiteralValue::Error(e) => {
6109 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
6110 }
6111 LiteralValue::Date(d) => {
6112 let dt = d.and_hms_opt(0, 0, 0).unwrap();
6113 let serial =
6114 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, &dt);
6115 OverlayValue::DateTime(serial)
6116 }
6117 LiteralValue::DateTime(dt) => {
6118 let serial =
6119 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, dt);
6120 OverlayValue::DateTime(serial)
6121 }
6122 LiteralValue::Time(t) => {
6123 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
6124 OverlayValue::DateTime(serial)
6125 }
6126 LiteralValue::Duration(d) => {
6127 let serial = d.num_seconds() as f64 / 86_400.0;
6128 OverlayValue::Duration(serial)
6129 }
6130 LiteralValue::Pending => OverlayValue::Pending,
6131 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
6132 formualizer_common::ExcelErrorKind::Value,
6133 )),
6134 }
6135 }
6136
6137 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6140 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
6141 return None;
6142 }
6143 let asheet = self.arrow_sheets.sheet(sheet)?;
6144 let row0 = row.saturating_sub(1) as usize;
6145 let col0 = col.saturating_sub(1) as usize;
6146 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
6147 return None;
6148 }
6149 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
6150 let ch = asheet.columns[col0].chunk(ch_idx)?;
6151 ch.overlay.get_scalar(in_off).map(|ov| ov.to_literal())
6152 }
6153
6154 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6157 if !(self.config.arrow_storage_enabled
6158 && self.config.delta_overlay_enabled
6159 && self.config.write_formula_overlay_enabled)
6160 {
6161 return None;
6162 }
6163 let asheet = self.arrow_sheets.sheet(sheet)?;
6164 let row0 = row.saturating_sub(1) as usize;
6165 let col0 = col.saturating_sub(1) as usize;
6166 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
6167 return None;
6168 }
6169 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
6170 let ch = asheet.columns[col0].chunk(ch_idx)?;
6171 ch.computed_overlay
6172 .get_scalar(in_off)
6173 .map(|ov| ov.to_literal())
6174 }
6175
6176 fn set_delta_overlay_cell_raw(
6177 &mut self,
6178 sheet: &str,
6179 row: u32,
6180 col: u32,
6181 value: Option<LiteralValue>,
6182 ) {
6183 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
6184 return;
6185 }
6186
6187 self.ensure_arrow_sheet(sheet);
6188 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
6189 let row0 = row.saturating_sub(1) as usize;
6190 let col0 = col.saturating_sub(1) as usize;
6191 let asheet = self
6192 .arrow_sheets
6193 .sheet_mut(sheet)
6194 .expect("ArrowSheet must exist");
6195
6196 let cur_cols = asheet.columns.len();
6197 if col0 >= cur_cols {
6198 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6199 }
6200 if row0 >= asheet.nrows as usize {
6201 if asheet.columns.is_empty() {
6202 asheet.insert_columns(0, 1);
6203 }
6204 asheet.ensure_row_capacity(row0 + 1);
6205 }
6206
6207 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6208 return;
6209 };
6210 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6211 return;
6212 };
6213
6214 if let Some(ov) = ov_opt {
6215 let _ = ch.overlay.set(in_off, ov);
6216 } else {
6217 let _ = ch.overlay.remove(in_off);
6218 }
6219 }
6220
6221 fn set_computed_overlay_cell_raw(
6222 &mut self,
6223 sheet: &str,
6224 row: u32,
6225 col: u32,
6226 value: Option<LiteralValue>,
6227 ) {
6228 if !(self.config.arrow_storage_enabled
6229 && self.config.delta_overlay_enabled
6230 && self.config.write_formula_overlay_enabled)
6231 {
6232 return;
6233 }
6234
6235 self.ensure_arrow_sheet(sheet);
6236 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
6237 let row0 = row.saturating_sub(1) as usize;
6238 let col0 = col.saturating_sub(1) as usize;
6239 let asheet = self
6240 .arrow_sheets
6241 .sheet_mut(sheet)
6242 .expect("ArrowSheet must exist");
6243
6244 let cur_cols = asheet.columns.len();
6245 if col0 >= cur_cols {
6246 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6247 }
6248 if row0 >= asheet.nrows as usize {
6249 if asheet.columns.is_empty() {
6250 asheet.insert_columns(0, 1);
6251 }
6252 asheet.ensure_row_capacity(row0 + 1);
6253 }
6254
6255 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6256 return;
6257 };
6258 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6259 return;
6260 };
6261
6262 let delta = if let Some(ov) = ov_opt {
6263 ch.computed_overlay.set(in_off, ov)
6264 } else {
6265 ch.computed_overlay.remove(in_off)
6266 };
6267 self.adjust_computed_overlay_bytes(delta);
6268 }
6269
6270 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
6271 use crate::engine::ArrowOp;
6272
6273 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
6274 Box::new(batch.ops.iter().rev())
6275 } else {
6276 Box::new(batch.ops.iter())
6277 };
6278
6279 for op in iter {
6280 match op {
6281 ArrowOp::SetDeltaCell {
6282 sheet_id,
6283 row0,
6284 col0,
6285 old,
6286 new,
6287 } => {
6288 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6289 let v = if undo { old.clone() } else { new.clone() };
6290 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
6291 }
6292 ArrowOp::SetComputedCell {
6293 sheet_id,
6294 row0,
6295 col0,
6296 old,
6297 new,
6298 } => {
6299 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6300 let v = if undo { old.clone() } else { new.clone() };
6301 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
6302 }
6303 ArrowOp::RestoreComputedRect {
6304 sheet_id,
6305 sr0,
6306 sc0,
6307 er0,
6308 ec0,
6309 old,
6310 new,
6311 } => {
6312 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6313 let vals = if undo { old } else { new };
6314 let height = (*er0).saturating_sub(*sr0) as usize + 1;
6315 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
6316 for r in 0..height {
6317 for c in 0..width {
6318 let v = vals
6319 .get(r)
6320 .and_then(|row| row.get(c))
6321 .cloned()
6322 .unwrap_or(LiteralValue::Empty);
6323 self.set_computed_overlay_cell_raw(
6324 &sheet,
6325 *sr0 + 1 + r as u32,
6326 *sc0 + 1 + c as u32,
6327 Some(v),
6328 );
6329 }
6330 }
6331 }
6332 ArrowOp::InsertRows {
6333 sheet_id,
6334 before0,
6335 count,
6336 } => {
6337 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6338 self.ensure_arrow_sheet(&sheet);
6339 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
6340 if undo {
6341 asheet.delete_rows(*before0 as usize, *count as usize);
6342 } else {
6343 asheet.insert_rows(*before0 as usize, *count as usize);
6344 }
6345 }
6346 }
6347 ArrowOp::InsertCols {
6348 sheet_id,
6349 before0,
6350 count,
6351 } => {
6352 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6353 self.ensure_arrow_sheet(&sheet);
6354 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
6355 if undo {
6356 asheet.delete_columns(*before0 as usize, *count as usize);
6357 } else {
6358 asheet.insert_columns(*before0 as usize, *count as usize);
6359 }
6360 }
6361 }
6362 }
6363 }
6364 }
6365
6366 fn record_spill_ops_into_arrow_undo(
6367 &mut self,
6368 undo: &mut crate::engine::ArrowUndoBatch,
6369 events: &[crate::engine::ChangeEvent],
6370 ) {
6371 use crate::engine::ChangeEvent;
6372 use formualizer_common::LiteralValue;
6373
6374 #[allow(clippy::type_complexity)]
6375 let rect_from_snapshot =
6376 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
6377 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
6378 if snap.target_cells.is_empty() {
6379 return None;
6380 }
6381 let sheet_id = snap.target_cells[0].sheet_id;
6382 let sr0 = snap.target_cells[0].coord.row();
6383 let sc0 = snap.target_cells[0].coord.col();
6384 if snap.values.is_empty() || snap.values[0].is_empty() {
6385 return None;
6386 }
6387 let h = snap.values.len() as u32;
6388 let w = snap.values[0].len() as u32;
6389 let er0 = sr0.saturating_add(h.saturating_sub(1));
6390 let ec0 = sc0.saturating_add(w.saturating_sub(1));
6391 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
6392 };
6393
6394 for ev in events {
6395 match ev {
6396 ChangeEvent::SpillCommitted { old, new, .. } => {
6397 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
6398 let old_vals = if let Some(old_snap) = old {
6399 rect_from_snapshot(old_snap)
6400 .map(|(_, _, _, _, _, v)| v)
6401 .unwrap_or_else(|| {
6402 vec![
6403 vec![LiteralValue::Empty; new_vals[0].len()];
6404 new_vals.len()
6405 ]
6406 })
6407 } else {
6408 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
6409 };
6410 undo.record_restore_computed_rect(
6411 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
6412 );
6413 }
6414 }
6415 ChangeEvent::SpillCleared { old, .. } => {
6416 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
6417 let new_vals =
6418 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
6419 undo.record_restore_computed_rect(
6420 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
6421 );
6422 }
6423 }
6424 _ => {}
6425 }
6426 }
6427 }
6428
6429 fn mirror_value_to_computed_overlay(
6434 &mut self,
6435 sheet: &str,
6436 row: u32,
6437 col: u32,
6438 value: &LiteralValue,
6439 ) {
6440 if !(self.config.arrow_storage_enabled
6441 && self.config.delta_overlay_enabled
6442 && self.config.write_formula_overlay_enabled)
6443 {
6444 return;
6445 }
6446 if self.computed_overlay_mirroring_disabled {
6447 return;
6448 }
6449
6450 let ov = self.literal_to_overlay_value(value);
6451 self.write_computed_overlay_value_0based(
6452 sheet,
6453 row.saturating_sub(1),
6454 col.saturating_sub(1),
6455 ov,
6456 );
6457 }
6458
6459 fn write_computed_overlay_value_0based(
6460 &mut self,
6461 sheet: &str,
6462 row0: u32,
6463 col0: u32,
6464 value: OverlayValue,
6465 ) {
6466 if !(self.config.arrow_storage_enabled
6467 && self.config.delta_overlay_enabled
6468 && self.config.write_formula_overlay_enabled)
6469 {
6470 return;
6471 }
6472 if self.computed_overlay_mirroring_disabled {
6473 return;
6474 }
6475
6476 self.ensure_arrow_sheet(sheet);
6477
6478 let row0 = row0 as usize;
6479 let col0 = col0 as usize;
6480 let asheet = self
6481 .arrow_sheets
6482 .sheet_mut(sheet)
6483 .expect("ArrowSheet must exist");
6484
6485 let cur_cols = asheet.columns.len();
6486 if col0 >= cur_cols {
6487 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6488 }
6489
6490 if row0 >= asheet.nrows as usize {
6491 if asheet.columns.is_empty() {
6492 asheet.insert_columns(0, 1);
6493 }
6494 asheet.ensure_row_capacity(row0 + 1);
6495 }
6496
6497 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6498 return;
6499 };
6500 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6501 return;
6502 };
6503
6504 let delta = ch.computed_overlay.set_scalar(in_off, value);
6505 self.adjust_computed_overlay_bytes(delta);
6506
6507 if let Some(cap) = self.config.max_overlay_memory_bytes
6508 && self.computed_overlay_bytes_estimate > cap
6509 {
6510 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6511 }
6512 }
6513
6514 pub(crate) fn plan_computed_write_coalescing(
6515 &self,
6516 buffer: &ComputedWriteBuffer,
6517 ) -> ComputedWriteCoalescingPlan {
6518 self.plan_computed_write_coalescing_from_writes(buffer.writes().iter().cloned())
6519 }
6520
6521 fn plan_owned_computed_write_coalescing(
6522 &self,
6523 writes: Vec<ComputedWrite>,
6524 ) -> ComputedWriteCoalescingPlan {
6525 self.plan_computed_write_coalescing_from_writes(writes)
6526 }
6527
6528 fn plan_computed_write_coalescing_from_writes(
6529 &self,
6530 writes: impl IntoIterator<Item = ComputedWrite>,
6531 ) -> ComputedWriteCoalescingPlan {
6532 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
6533 BTreeMap::new();
6534 let mut input_cells = 0usize;
6535
6536 for write in writes {
6537 match write {
6538 ComputedWrite::Cell {
6539 seq,
6540 sheet_id,
6541 row0,
6542 col0,
6543 value,
6544 } => {
6545 input_cells = input_cells.saturating_add(1);
6546 self.push_computed_write_plan_entry(
6547 &mut groups,
6548 seq,
6549 sheet_id,
6550 row0,
6551 col0,
6552 value,
6553 );
6554 }
6555 ComputedWrite::Rect {
6556 seq,
6557 sheet_id,
6558 sr0,
6559 sc0,
6560 values,
6561 } => {
6562 for (r_off, row) in values.into_iter().enumerate() {
6563 for (c_off, value) in row.into_iter().enumerate() {
6564 input_cells = input_cells.saturating_add(1);
6565 self.push_computed_write_plan_entry(
6566 &mut groups,
6567 seq,
6568 sheet_id,
6569 sr0.saturating_add(r_off as u32),
6570 sc0.saturating_add(c_off as u32),
6571 value,
6572 );
6573 }
6574 }
6575 }
6576 }
6577 }
6578
6579 let mut plan = ComputedWriteCoalescingPlan {
6580 chunks: Vec::with_capacity(groups.len()),
6581 input_cells,
6582 coalesced_cells: 0,
6583 overwritten_cells: 0,
6584 };
6585 for (key, entries) in groups {
6586 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(key, entries);
6587 plan.coalesced_cells = plan
6588 .coalesced_cells
6589 .saturating_add(chunk_plan.entries.len());
6590 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
6591 plan.chunks.push(chunk_plan);
6592 }
6593 debug_assert_eq!(
6594 plan.input_cells,
6595 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
6596 );
6597 plan
6598 }
6599
6600 fn push_computed_write_plan_entry(
6601 &self,
6602 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
6603 seq: u64,
6604 sheet_id: SheetId,
6605 row0: u32,
6606 col0: u32,
6607 value: OverlayValue,
6608 ) {
6609 let (chunk_idx, chunk_start_row0, row_in_chunk) =
6610 self.locate_computed_write_chunk(sheet_id, row0);
6611 let key = ComputedWriteChunkKey {
6612 sheet_id,
6613 col0,
6614 chunk_idx,
6615 chunk_start_row0,
6616 };
6617 groups
6618 .entry(key)
6619 .or_default()
6620 .push(ComputedWriteChunkEntryPlan {
6621 row_in_chunk,
6622 seq,
6623 value,
6624 });
6625 }
6626
6627 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
6628 let sheet_name = self.graph.sheet_name(sheet_id);
6629 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
6630 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
6631 }
6632 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
6633 }
6634
6635 fn locate_row_in_sheet_for_computed_write_plan(
6636 sheet: &crate::arrow_store::ArrowSheet,
6637 row0: usize,
6638 ) -> (usize, u32, usize) {
6639 if row0 < sheet.nrows as usize
6640 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
6641 {
6642 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
6643 return (chunk_idx, chunk_start as u32, row_in_chunk);
6644 }
6645
6646 let chunk_rows = sheet.chunk_rows.max(1);
6647 if sheet.chunk_starts.is_empty() {
6648 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
6649 }
6650
6651 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
6652 let mut chunk_start = sheet.chunk_starts[chunk_idx];
6653 while chunk_start.saturating_add(chunk_rows) <= row0 {
6654 chunk_idx = chunk_idx.saturating_add(1);
6655 chunk_start = chunk_start.saturating_add(chunk_rows);
6656 }
6657 (
6658 chunk_idx,
6659 chunk_start as u32,
6660 row0.saturating_sub(chunk_start),
6661 )
6662 }
6663
6664 fn locate_row_in_empty_sheet_for_computed_write_plan(
6665 row0: usize,
6666 chunk_rows: usize,
6667 ) -> (usize, u32, usize) {
6668 let chunk_rows = chunk_rows.max(1);
6669 let chunk_idx = row0 / chunk_rows;
6670 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
6671 (
6672 chunk_idx,
6673 chunk_start as u32,
6674 row0.saturating_sub(chunk_start),
6675 )
6676 }
6677
6678 #[cfg(test)]
6679 pub(crate) fn debug_plan_computed_write_coalescing(
6680 &self,
6681 buffer: &ComputedWriteBuffer,
6682 ) -> ComputedWriteCoalescingPlan {
6683 self.plan_computed_write_coalescing(buffer)
6684 }
6685
6686 pub(crate) fn flush_computed_write_buffer(
6687 &mut self,
6688 buffer: &mut ComputedWriteBuffer,
6689 ) -> Result<(), ExcelError> {
6690 if buffer.is_empty() {
6691 return Ok(());
6692 }
6693
6694 let plan = self.plan_owned_computed_write_coalescing(buffer.take_writes());
6695 self.flush_computed_write_plan(plan);
6696
6697 Ok(())
6698 }
6699
6700 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
6701 for chunk in plan.chunks {
6702 self.flush_computed_write_chunk_plan(chunk);
6703 }
6704 }
6705
6706 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
6707 match &chunk.shape {
6708 ComputedWriteChunkPlanShape::Point => {
6709 self.flush_computed_write_chunk_plan_as_points(chunk);
6710 }
6711 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
6712 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
6713 }
6714 ComputedWriteChunkPlanShape::DenseRange { .. } => {
6715 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6716 }
6717 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
6718 if Self::should_emit_computed_run_fragment(*len, *runs) {
6719 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
6720 } else {
6721 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6722 }
6723 }
6724 }
6725 }
6726
6727 #[inline]
6728 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
6729 runs <= len / 2
6730 }
6731
6732 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
6733 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
6734 for entry in chunk.entries {
6735 let row0 = chunk
6736 .chunk_start_row0
6737 .saturating_add(entry.row_in_chunk as u32);
6738 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
6739 }
6740 }
6741
6742 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
6743 &mut self,
6744 chunk: ComputedWriteChunkPlan,
6745 ) {
6746 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
6747 let sheet_id = chunk.sheet_id;
6748 let col0 = chunk.col0;
6749 let chunk_idx = chunk.chunk_idx;
6750 let chunk_start_row0 = chunk.chunk_start_row0;
6751 let items: Vec<(usize, OverlayValue)> = chunk
6752 .entries
6753 .into_iter()
6754 .map(|entry| (entry.row_in_chunk, entry.value))
6755 .collect();
6756 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
6757 items,
6758 point_estimate,
6759 ) {
6760 Some(Ok(fragment)) => {
6761 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
6762 }
6763 Some(Err(cells)) => {
6764 self.flush_computed_overlay_cells_as_points(
6765 sheet_id,
6766 col0,
6767 chunk_start_row0,
6768 cells,
6769 );
6770 }
6771 None => {}
6772 }
6773 }
6774
6775 #[inline]
6776 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
6777 chunk
6778 .entries
6779 .iter()
6780 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
6781 .fold(0usize, usize::saturating_add)
6782 }
6783
6784 fn flush_computed_overlay_cells_as_points(
6785 &mut self,
6786 sheet_id: SheetId,
6787 col0: u32,
6788 chunk_start_row0: u32,
6789 cells: Vec<(usize, OverlayValue)>,
6790 ) {
6791 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6792 for (row_in_chunk, value) in cells {
6793 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
6794 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
6795 }
6796 }
6797
6798 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6799 if chunk.entries.is_empty() {
6800 return;
6801 }
6802 let start = chunk.entries[0].row_in_chunk;
6803 let values: Vec<OverlayValue> =
6804 chunk.entries.into_iter().map(|entry| entry.value).collect();
6805 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
6806 self.apply_computed_overlay_fragment(
6807 chunk.sheet_id,
6808 chunk.col0,
6809 chunk.chunk_idx,
6810 fragment,
6811 );
6812 }
6813 }
6814
6815 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6816 if chunk.entries.is_empty() {
6817 return;
6818 }
6819 let start = chunk.entries[0].row_in_chunk;
6820 let values: Vec<OverlayValue> =
6821 chunk.entries.into_iter().map(|entry| entry.value).collect();
6822 if let Some(fragment) = OverlayFragment::run_range(start, values) {
6823 self.apply_computed_overlay_fragment(
6824 chunk.sheet_id,
6825 chunk.col0,
6826 chunk.chunk_idx,
6827 fragment,
6828 );
6829 }
6830 }
6831
6832 fn apply_computed_overlay_fragment(
6833 &mut self,
6834 sheet_id: SheetId,
6835 col0: u32,
6836 chunk_idx: usize,
6837 fragment: OverlayFragment,
6838 ) {
6839 if !(self.config.arrow_storage_enabled
6840 && self.config.delta_overlay_enabled
6841 && self.config.write_formula_overlay_enabled)
6842 {
6843 return;
6844 }
6845 if self.computed_overlay_mirroring_disabled {
6846 return;
6847 }
6848
6849 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6850 self.ensure_arrow_sheet(&sheet_name);
6851
6852 let col0 = col0 as usize;
6853 let asheet = self
6854 .arrow_sheets
6855 .sheet_mut(&sheet_name)
6856 .expect("ArrowSheet must exist");
6857
6858 let cur_cols = asheet.columns.len();
6859 if col0 >= cur_cols {
6860 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6861 }
6862
6863 let start_row0 = asheet
6864 .chunk_starts
6865 .get(chunk_idx)
6866 .copied()
6867 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
6868 let required_rows =
6869 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
6870 if required_rows > asheet.nrows as usize {
6871 if asheet.columns.is_empty() {
6872 asheet.insert_columns(0, 1);
6873 }
6874 asheet.ensure_row_capacity(required_rows);
6875 }
6876
6877 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
6878 return;
6879 };
6880 let delta = ch.computed_overlay.apply_fragment(fragment);
6881 self.adjust_computed_overlay_bytes(delta);
6882
6883 if let Some(cap) = self.config.max_overlay_memory_bytes
6884 && self.computed_overlay_bytes_estimate > cap
6885 {
6886 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6887 }
6888 }
6889
6890 #[inline]
6891 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
6892 if delta >= 0 {
6893 self.computed_overlay_bytes_estimate = self
6894 .computed_overlay_bytes_estimate
6895 .saturating_add(delta as usize);
6896 } else {
6897 self.computed_overlay_bytes_estimate = self
6898 .computed_overlay_bytes_estimate
6899 .saturating_sub((-delta) as usize);
6900 }
6901 }
6902
6903 fn clear_all_computed_overlays(&mut self) {
6904 let mut freed_total = 0usize;
6905 for sh in self.arrow_sheets.sheets.iter_mut() {
6906 for col in sh.columns.iter_mut() {
6907 for ch in col.chunks.iter_mut() {
6908 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6909 }
6910 for ch in col.sparse_chunks.values_mut() {
6911 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6912 }
6913 }
6914 }
6915 self.computed_overlay_bytes_estimate = self
6916 .computed_overlay_bytes_estimate
6917 .saturating_sub(freed_total);
6918 }
6919
6920 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
6921 self.compact_all_computed_overlays();
6924 }
6925
6926 fn compact_all_computed_overlays(&mut self) {
6929 let mut freed_total = 0usize;
6930 for sheet in self.arrow_sheets.sheets.iter_mut() {
6931 for col_idx in 0..sheet.columns.len() {
6932 let num_dense = sheet.columns[col_idx].chunks.len();
6934 for ch_idx in 0..num_dense {
6935 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
6936 }
6937 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
6939 .sparse_chunks
6940 .keys()
6941 .copied()
6942 .collect();
6943 for ch_idx in sparse_keys {
6944 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
6945 }
6946 }
6947 }
6948 self.computed_overlay_bytes_estimate = self
6949 .computed_overlay_bytes_estimate
6950 .saturating_sub(freed_total);
6951 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
6952 }
6953
6954 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
6955 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
6956 }
6957
6958 fn record_vertex_value_to_overlay(
6959 &mut self,
6960 vertex_id: VertexId,
6961 value: &LiteralValue,
6962 computed_writes: Option<&mut ComputedWriteBuffer>,
6963 ) -> Result<(), ExcelError> {
6964 if !(self.config.arrow_storage_enabled
6965 && self.config.delta_overlay_enabled
6966 && self.config.write_formula_overlay_enabled)
6967 {
6968 return Ok(());
6969 }
6970 if self.computed_overlay_mirroring_disabled {
6971 return Ok(());
6972 }
6973 if !matches!(
6974 self.graph.get_vertex_kind(vertex_id),
6975 VertexKind::FormulaScalar | VertexKind::FormulaArray
6976 ) {
6977 return Ok(());
6978 }
6979 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
6980 return Ok(());
6981 };
6982 let ov = self.literal_to_overlay_value(value);
6983 if let Some(buffer) = computed_writes {
6984 buffer.push_cell(cell.sheet_id, cell.coord.row(), cell.coord.col(), ov);
6985 if self.should_flush_computed_write_buffer(buffer) {
6986 self.flush_computed_write_buffer(buffer)?;
6987 }
6988 } else {
6989 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
6990 self.write_computed_overlay_value_0based(
6991 &sheet_name,
6992 cell.coord.row(),
6993 cell.coord.col(),
6994 ov,
6995 );
6996 }
6997 Ok(())
6998 }
6999
7000 #[inline]
7001 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
7002 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
7003 if cap == 0 {
7004 return false;
7005 }
7006 self.computed_overlay_bytes_estimate
7007 .saturating_add(buffer.estimated_bytes())
7008 > cap
7009 })
7010 }
7011
7012 pub fn overlay_memory_usage(&self) -> usize {
7014 self.computed_overlay_bytes_estimate
7015 }
7016
7017 #[cfg(test)]
7018 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
7019 self.overlay_compactions
7020 }
7021
7022 #[cfg(test)]
7023 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
7024 let mut total = 0usize;
7025 for sheet in &self.arrow_sheets.sheets {
7026 for column in &sheet.columns {
7027 for chunk in &column.chunks {
7028 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
7029 }
7030 for chunk in column.sparse_chunks.values() {
7031 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
7032 }
7033 }
7034 }
7035 self.computed_overlay_bytes_estimate = total;
7036 total
7037 }
7038
7039 fn resolve_sheet_locator_for_write(
7040 &mut self,
7041 loc: formualizer_common::SheetLocator<'_>,
7042 current_sheet: &str,
7043 ) -> Result<SheetId, ExcelError> {
7044 Ok(match loc {
7045 formualizer_common::SheetLocator::Id(id) => id,
7046 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
7047 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
7048 })
7049 }
7050
7051 fn resolve_sheet_locator_for_read(
7052 &self,
7053 loc: formualizer_common::SheetLocator<'_>,
7054 current_sheet: &str,
7055 ) -> Result<SheetId, ExcelError> {
7056 match loc {
7057 formualizer_common::SheetLocator::Id(id) => Ok(id),
7058 formualizer_common::SheetLocator::Name(name) => self
7059 .graph
7060 .sheet_id(name.as_ref())
7061 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
7062 formualizer_common::SheetLocator::Current => self
7063 .graph
7064 .sheet_id(current_sheet)
7065 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
7066 }
7067 }
7068
7069 pub fn set_cell_value(
7071 &mut self,
7072 sheet: &str,
7073 row: u32,
7074 col: u32,
7075 value: LiteralValue,
7076 ) -> Result<(), ExcelError> {
7077 let sheet_id = self.graph.sheet_id_mut(sheet);
7078 self.demote_span_containing_cell_for_write(
7079 sheet_id,
7080 row.saturating_sub(1),
7081 col.saturating_sub(1),
7082 )
7083 .map_err(Self::editor_error_to_excel)?;
7084 self.graph.set_cell_value(sheet, row, col, value.clone())?;
7085 self.record_formula_plane_changed_cell(sheet, row, col);
7086 self.mirror_value_to_overlay(sheet, row, col, &value);
7088 self.snapshot_id
7090 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7091 self.has_edited = true;
7092 Ok(())
7093 }
7094
7095 fn record_formula_plane_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
7100 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7101 return;
7102 }
7103 let sheet_id = self.graph.sheet_id_mut(sheet);
7104 self.record_formula_plane_structural_change(StructuralScope::Cell {
7105 sheet: sheet_id,
7106 row: row.saturating_sub(1),
7107 col: col.saturating_sub(1),
7108 });
7109 }
7110
7111 fn record_formula_plane_change_for_event(&mut self, event: &ChangeEvent) {
7112 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7113 return;
7114 }
7115
7116 match event {
7117 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
7118 self.record_formula_plane_structural_change(StructuralScope::Cell {
7119 sheet: addr.sheet_id,
7120 row: addr.coord.row(),
7121 col: addr.coord.col(),
7122 });
7123 }
7124 ChangeEvent::SpillCommitted { new, .. } => {
7125 if let Some(scope) = Self::formula_plane_region_from_cells(&new.target_cells) {
7126 self.record_formula_plane_structural_change(scope);
7127 }
7128 }
7129 ChangeEvent::SpillCleared { old, .. } => {
7130 if let Some(scope) = Self::formula_plane_region_from_cells(&old.target_cells) {
7131 self.record_formula_plane_structural_change(scope);
7132 }
7133 }
7134 ChangeEvent::DefineName { .. }
7135 | ChangeEvent::UpdateName { .. }
7136 | ChangeEvent::DeleteName { .. }
7137 | ChangeEvent::VertexMoved { .. }
7138 | ChangeEvent::FormulaAdjusted { .. }
7139 | ChangeEvent::NamedRangeAdjusted { .. } => {
7140 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
7141 }
7142 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
7143 self.record_formula_plane_structural_change(StructuralScope::Region(
7144 Region::whole_row(*sheet_id, *row0),
7145 ));
7146 }
7147 ChangeEvent::AddVertex { .. }
7148 | ChangeEvent::RemoveVertex { .. }
7149 | ChangeEvent::EdgeAdded { .. }
7150 | ChangeEvent::EdgeRemoved { .. }
7151 | ChangeEvent::CompoundStart { .. }
7152 | ChangeEvent::CompoundEnd { .. }
7153 | ChangeEvent::StagedFormulaCellChanged { .. } => {}
7154 }
7155 }
7156
7157 fn record_formula_plane_structural_change(&mut self, scope: StructuralScope) {
7158 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7159 return;
7160 }
7161
7162 match scope {
7163 StructuralScope::Cell { sheet, row, col } => {
7164 self.graph
7165 .formula_authority_mut()
7166 .record_changed_region(Region::point(sheet, row, col));
7167 }
7168 StructuralScope::Region(region) => {
7169 self.graph
7170 .formula_authority_mut()
7171 .record_changed_region(region);
7172 }
7173 StructuralScope::Sheet(sheet_id) => {
7174 self.graph
7175 .formula_authority_mut()
7176 .record_changed_region(Region::whole_sheet(sheet_id));
7177 }
7178 StructuralScope::RemovedSheet(sheet_id) => {
7179 let removed_refs = {
7180 let authority = self.graph.formula_authority();
7181 authority
7182 .active_span_refs()
7183 .into_iter()
7184 .filter(|span_ref| {
7185 authority
7186 .plane
7187 .spans
7188 .get(*span_ref)
7189 .map(|span| span.sheet_id == sheet_id)
7190 .unwrap_or(false)
7191 })
7192 .collect::<Vec<_>>()
7193 };
7194
7195 let authority = self.graph.formula_authority_mut();
7196 for span_ref in removed_refs {
7197 authority.plane.remove_span(span_ref);
7198 }
7199 authority.mark_all_active_spans_dirty();
7200 let _ = authority.rebuild_indexes();
7201 }
7202 StructuralScope::AllSheets => {
7203 let authority = self.graph.formula_authority_mut();
7204 authority.mark_all_active_spans_dirty();
7205 let _ = authority.rebuild_indexes();
7206 }
7207 }
7208 }
7209
7210 fn formula_plane_region_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
7211 let first = cells.first()?;
7212 let sheet_id = first.sheet_id;
7213 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
7214 return Some(StructuralScope::AllSheets);
7215 }
7216 let mut row_start = first.coord.row();
7217 let mut row_end = row_start;
7218 let mut col_start = first.coord.col();
7219 let mut col_end = col_start;
7220 for cell in cells.iter().skip(1) {
7221 row_start = row_start.min(cell.coord.row());
7222 row_end = row_end.max(cell.coord.row());
7223 col_start = col_start.min(cell.coord.col());
7224 col_end = col_end.max(cell.coord.col());
7225 }
7226 Some(StructuralScope::Region(Region::rect(
7227 sheet_id, row_start, row_end, col_start, col_end,
7228 )))
7229 }
7230
7231 pub fn set_cell_value_ref(
7232 &mut self,
7233 cell: formualizer_common::SheetCellRef<'_>,
7234 current_sheet: &str,
7235 value: LiteralValue,
7236 ) -> Result<(), ExcelError> {
7237 let owned = cell.into_owned();
7238 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
7239 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
7240 self.set_cell_value(
7241 &sheet_name,
7242 owned.coord.row() + 1,
7243 owned.coord.col() + 1,
7244 value,
7245 )
7246 }
7247
7248 pub fn set_cell_formula_ref(
7249 &mut self,
7250 cell: formualizer_common::SheetCellRef<'_>,
7251 current_sheet: &str,
7252 ast: ASTNode,
7253 ) -> Result<(), ExcelError> {
7254 let owned = cell.into_owned();
7255 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
7256 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
7257 self.set_cell_formula(
7258 &sheet_name,
7259 owned.coord.row() + 1,
7260 owned.coord.col() + 1,
7261 ast,
7262 )
7263 }
7264
7265 pub fn get_cell_value_ref(
7266 &self,
7267 cell: formualizer_common::SheetCellRef<'_>,
7268 current_sheet: &str,
7269 ) -> Result<Option<LiteralValue>, ExcelError> {
7270 let owned = cell.into_owned();
7271 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
7272 let sheet_name = self.graph.sheet_name(sheet_id);
7273 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
7274 }
7275
7276 pub fn resolve_range_view_sheet_ref<'c>(
7277 &'c self,
7278 r: &formualizer_common::SheetRef<'_>,
7279 current_sheet: &str,
7280 ) -> Result<RangeView<'c>, ExcelError> {
7281 use formualizer_common::SheetLocator;
7282
7283 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
7284 match loc {
7285 SheetLocator::Current => Ok(None),
7286 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
7287 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
7288 }
7289 };
7290
7291 let rt = match r {
7292 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
7293 sheet: sheet_to_opt_name(cell.sheet.clone())?,
7294 row: cell.coord.row() + 1,
7295 col: cell.coord.col() + 1,
7296 row_abs: cell.coord.row_abs(),
7297 col_abs: cell.coord.col_abs(),
7298 },
7299 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
7300 sheet: sheet_to_opt_name(range.sheet.clone())?,
7301 start_row: range.start_row.map(|b| b.index + 1),
7302 start_col: range.start_col.map(|b| b.index + 1),
7303 end_row: range.end_row.map(|b| b.index + 1),
7304 end_col: range.end_col.map(|b| b.index + 1),
7305 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
7306 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
7307 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
7308 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
7309 },
7310 };
7311
7312 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
7313 }
7314
7315 pub fn set_cell_formula(
7317 &mut self,
7318 sheet: &str,
7319 row: u32,
7320 col: u32,
7321 ast: ASTNode,
7322 ) -> Result<(), ExcelError> {
7323 let sheet_id = self.graph.sheet_id_mut(sheet);
7324 self.demote_span_containing_cell_for_write(
7325 sheet_id,
7326 row.saturating_sub(1),
7327 col.saturating_sub(1),
7328 )
7329 .map_err(Self::editor_error_to_excel)?;
7330 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
7331 let ingested = {
7332 let mut pipeline = self.ingest_pipeline();
7333 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
7334 };
7335 self.graph.set_cell_formula_with_plan(
7336 sheet,
7337 row,
7338 col,
7339 ingested.ast_id,
7340 &ingested.dep_plan,
7341 ingested.dep_plan.volatile,
7342 ingested.dep_plan.dynamic,
7343 )?;
7344 self.record_formula_plane_changed_cell(sheet, row, col);
7345
7346 self.clear_delta_overlay_cell(sheet, row, col);
7351
7352 self.mark_topology_edited();
7354 Ok(())
7355 }
7356
7357 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
7359 where
7360 I: IntoIterator<Item = (u32, u32, ASTNode)>,
7361 {
7362 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
7363 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
7364 let sheet_id = self.graph.sheet_id_mut(sheet);
7365 let writes_inside_active_span = edited_cells.iter().any(|(row, col)| {
7366 let placement =
7367 PlacementCoord::new(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
7368 self.graph
7369 .formula_authority()
7370 .plane
7371 .spans
7372 .find_at(placement)
7373 .is_some()
7374 });
7375 if writes_inside_active_span {
7376 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
7377 .map_err(Self::editor_error_to_excel)?;
7378 }
7379 let ingested = {
7380 let mut pipeline = self.ingest_pipeline();
7381 let inputs = collected.into_iter().map(|(row, col, ast)| {
7382 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
7383 (FormulaAstInput::Tree(ast), placement, None)
7384 });
7385 pipeline.ingest_batch(inputs)?
7386 };
7387 let planned = ingested
7388 .into_iter()
7389 .map(|formula| {
7390 (
7391 formula.placement.coord.row() + 1,
7392 formula.placement.coord.col() + 1,
7393 formula.ast_id,
7394 formula.dep_plan,
7395 )
7396 })
7397 .collect();
7398 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
7399 for (row, col) in edited_cells {
7400 self.record_formula_plane_changed_cell(sheet, row, col);
7401 }
7402 if n > 0 {
7404 self.mark_topology_edited();
7405 }
7406 Ok(n)
7407 }
7408
7409 #[inline]
7410 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
7411 match v {
7412 LiteralValue::Empty => None,
7413 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
7414 other => Some(other),
7415 }
7416 }
7417
7418 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
7420 self.read_cell_value(sheet, row, col)
7421 .and_then(Self::normalize_public_cell_read)
7422 }
7423
7424 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
7426 let asheet = self.sheet_store().sheet(sheet)?;
7427 let r0 = row.saturating_sub(1) as usize;
7428 let c0 = col.saturating_sub(1) as usize;
7429 let v = asheet.get_cell_value(r0, c0);
7430 if matches!(v, LiteralValue::Empty) {
7431 None
7432 } else {
7433 Some(v)
7434 }
7435 }
7436
7437 pub(crate) fn read_range_values(
7439 &self,
7440 sheet: &str,
7441 sr: u32,
7442 sc: u32,
7443 er: u32,
7444 ec: u32,
7445 ) -> RangeView<'_> {
7446 let Some(asheet) = self.sheet_store().sheet(sheet) else {
7447 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
7448 };
7449 if er < sr || ec < sc {
7450 return asheet.range_view(1, 1, 0, 0);
7451 }
7452 let sr0 = sr.saturating_sub(1) as usize;
7453 let sc0 = sc.saturating_sub(1) as usize;
7454 let er0 = er.saturating_sub(1) as usize;
7455 let ec0 = ec.saturating_sub(1) as usize;
7456 asheet.range_view(sr0, sc0, er0, ec0)
7457 }
7458
7459 pub fn get_cell(
7461 &self,
7462 sheet: &str,
7463 row: u32,
7464 col: u32,
7465 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
7466 let v = self.get_cell_value(sheet, row, col);
7467 let sheet_id = self.graph.sheet_id(sheet)?;
7468 let coord = Coord::from_excel(row, col, true, true);
7469 let cell = CellRef::new(sheet_id, coord);
7470 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
7471 let ast = self.graph.get_formula_id(vid).and_then(|ast_id| {
7472 self.graph
7473 .data_store()
7474 .retrieve_ast(ast_id, self.graph.sheet_reg())
7475 });
7476 return Some((ast, v));
7477 }
7478
7479 let placement =
7480 crate::formula_plane::runtime::PlacementCoord::new(sheet_id, coord.row(), coord.col());
7481 let handle = self
7482 .graph
7483 .formula_authority()
7484 .plane
7485 .resolve_formula_at(placement, None);
7486 let template_id = match handle.resolution {
7487 crate::formula_plane::runtime::FormulaResolution::SpanPlacement {
7488 template_id, ..
7489 } => Some(template_id),
7490 crate::formula_plane::runtime::FormulaResolution::Overlay(overlay_ref) => self
7491 .graph
7492 .formula_authority()
7493 .plane
7494 .formula_overlay
7495 .get(overlay_ref)
7496 .and_then(|overlay| match overlay.kind {
7497 crate::formula_plane::runtime::FormulaOverlayEntryKind::FormulaOverride(
7498 template_id,
7499 ) => Some(template_id),
7500 _ => None,
7501 }),
7502 _ => None,
7503 };
7504 let ast = template_id.and_then(|template_id| {
7505 let ast_id = self
7506 .graph
7507 .formula_authority()
7508 .plane
7509 .templates
7510 .get(template_id)?
7511 .ast_id;
7512 self.graph
7513 .data_store()
7514 .retrieve_ast(ast_id, self.graph.sheet_reg())
7515 });
7516 if let Some(ast) = ast {
7517 Some((Some(ast), v))
7518 } else if v.is_some() {
7519 Some((None, v))
7520 } else {
7521 None
7522 }
7523 }
7524
7525 pub fn begin_batch(&mut self) {
7527 self.graph.begin_batch();
7528 }
7529
7530 pub fn end_batch(&mut self) {
7532 self.graph.end_batch();
7533 }
7534
7535 pub fn begin_deferred_dirty(&mut self) {
7544 self.graph.begin_deferred_dirty();
7545 }
7546
7547 pub fn end_deferred_dirty(&mut self) {
7550 let _ = self.graph.end_deferred_dirty();
7551 }
7552
7553 pub fn dirty_propagation_visits(&self) -> u64 {
7557 self.graph.dirty_propagation_visits()
7558 }
7559
7560 #[inline]
7563 fn record_cell_if_changed(
7564 delta: &mut DeltaCollector,
7565 cell: &CellRef,
7566 old: &LiteralValue,
7567 new: &LiteralValue,
7568 ) {
7569 if old != new {
7570 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7571 }
7572 }
7573
7574 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
7575 if self.graph.formula_authority().active_span_count() > 0 {
7576 let _ = self.evaluate_authoritative_formula_plane_all()?;
7577 }
7578 self.evaluate_vertex_impl(vertex_id, None)
7579 }
7580
7581 fn evaluate_vertex_impl(
7582 &mut self,
7583 vertex_id: VertexId,
7584 delta: Option<&mut DeltaCollector>,
7585 ) -> Result<LiteralValue, ExcelError> {
7586 let mut delta = delta;
7587 if !self.graph.vertex_exists(vertex_id) {
7589 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
7590 .with_message(format!("Vertex not found: {vertex_id:?}")));
7591 }
7592
7593 let kind = self.graph.get_vertex_kind(vertex_id);
7595 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
7596
7597 let ast_id = match kind {
7598 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
7599 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
7600 ast_id
7601 } else {
7602 return Ok(LiteralValue::Number(0.0));
7603 }
7604 }
7605 VertexKind::Empty | VertexKind::Cell => {
7606 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
7607 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7608 let row = cell_ref.coord.row() + 1;
7609 let col = cell_ref.coord.col() + 1;
7610 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
7611 return Ok(v);
7612 }
7613 }
7614 return Ok(LiteralValue::Number(0.0));
7615 }
7616 VertexKind::NamedScalar => {
7617 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
7618 return Ok(value);
7619 }
7620 VertexKind::NamedArray => {
7621 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
7622 return Ok(value);
7623 }
7624 VertexKind::InfiniteRange
7625 | VertexKind::Range
7626 | VertexKind::External
7627 | VertexKind::Table => {
7628 return Ok(LiteralValue::Number(0.0));
7630 }
7631 };
7632
7633 let sheet_name = self.graph.sheet_name(sheet_id);
7635 let cell_ref = self
7636 .graph
7637 .get_cell_ref(vertex_id)
7638 .expect("cell ref for vertex");
7639 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7640
7641 let result =
7642 interpreter.evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg());
7643
7644 match result {
7646 Ok(cv) => {
7647 let result_literal = cv.into_literal();
7648 match result_literal {
7649 LiteralValue::Array(rows) => {
7650 self.graph
7652 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
7653 let anchor = self
7655 .graph
7656 .get_cell_ref(vertex_id)
7657 .expect("cell ref for vertex");
7658 let sheet_id = anchor.sheet_id;
7659 let h = rows.len() as u32;
7660 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
7661
7662 let spill_cells = (h as u64).saturating_mul(w as u64);
7664 if spill_cells > self.config.spill.max_spill_cells as u64 {
7665 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7666 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7667 .with_message("SpillTooLarge")
7668 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7669 expected_rows: h,
7670 expected_cols: w,
7671 });
7672 let spill_val = LiteralValue::Error(spill_err.clone());
7673 if let Some(d) = delta.as_deref_mut() {
7674 let old = self
7675 .read_cell_value(
7676 self.graph.sheet_name(anchor.sheet_id),
7677 anchor.coord.row() + 1,
7678 anchor.coord.col() + 1,
7679 )
7680 .unwrap_or(LiteralValue::Empty);
7681 if old != spill_val {
7682 d.record_cell(
7683 anchor.sheet_id,
7684 anchor.coord.row(),
7685 anchor.coord.col(),
7686 );
7687 }
7688 }
7689 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7690 if self.config.arrow_storage_enabled
7691 && self.config.delta_overlay_enabled
7692 && self.config.write_formula_overlay_enabled
7693 {
7694 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7695 self.mirror_value_to_computed_overlay(
7696 &sheet_name,
7697 anchor.coord.row() + 1,
7698 anchor.coord.col() + 1,
7699 &spill_val,
7700 );
7701 }
7702 return Ok(spill_val);
7703 }
7704 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);
7708 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
7709 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
7710 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7711 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7712 .with_message("Spill exceeds sheet bounds")
7713 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7714 expected_rows: h,
7715 expected_cols: w,
7716 });
7717 let spill_val = LiteralValue::Error(spill_err.clone());
7718 if let Some(d) = delta.as_deref_mut() {
7719 let old = self
7720 .read_cell_value(
7721 self.graph.sheet_name(anchor.sheet_id),
7722 anchor.coord.row() + 1,
7723 anchor.coord.col() + 1,
7724 )
7725 .unwrap_or(LiteralValue::Empty);
7726 if old != spill_val {
7727 d.record_cell(
7728 anchor.sheet_id,
7729 anchor.coord.row(),
7730 anchor.coord.col(),
7731 );
7732 }
7733 }
7734 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7735 if self.config.arrow_storage_enabled
7736 && self.config.delta_overlay_enabled
7737 && self.config.write_formula_overlay_enabled
7738 {
7739 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7740 self.mirror_value_to_computed_overlay(
7741 &sheet_name,
7742 anchor.coord.row() + 1,
7743 anchor.coord.col() + 1,
7744 &spill_val,
7745 );
7746 }
7747 return Ok(spill_val);
7748 }
7749 let mut targets = Vec::new();
7750 for r in 0..h {
7751 for c in 0..w {
7752 targets.push(self.graph.make_cell_ref_internal(
7753 sheet_id,
7754 anchor.coord.row() + r,
7755 anchor.coord.col() + c,
7756 ));
7757 }
7758 }
7759
7760 match self.spill_mgr.reserve(
7762 vertex_id,
7763 anchor,
7764 SpillShape { rows: h, cols: w },
7765 SpillMeta {
7766 epoch: self.recalc_epoch,
7767 config: self.config.spill,
7768 },
7769 ) {
7770 Ok(()) => {
7771 if let Err(e) = self.commit_spill_and_mirror(
7775 vertex_id,
7776 &targets,
7777 rows.clone(),
7778 delta.as_deref_mut(),
7779 None,
7780 ) {
7781 self.clear_spill_projection_and_mirror(
7783 vertex_id,
7784 delta.as_deref_mut(),
7785 );
7786 if let Some(d) = delta.as_deref_mut() {
7787 let old = self
7788 .read_cell_value(
7789 self.graph.sheet_name(anchor.sheet_id),
7790 anchor.coord.row() + 1,
7791 anchor.coord.col() + 1,
7792 )
7793 .unwrap_or(LiteralValue::Empty);
7794 let new = LiteralValue::Error(e.clone());
7795 if old != new {
7796 d.record_cell(
7797 anchor.sheet_id,
7798 anchor.coord.row(),
7799 anchor.coord.col(),
7800 );
7801 }
7802 }
7803 let err_val = LiteralValue::Error(e.clone());
7804 self.graph.update_vertex_value(vertex_id, err_val.clone());
7805 if self.config.arrow_storage_enabled
7806 && self.config.delta_overlay_enabled
7807 && self.config.write_formula_overlay_enabled
7808 {
7809 let sheet_name =
7810 self.graph.sheet_name(anchor.sheet_id).to_string();
7811 self.mirror_value_to_computed_overlay(
7812 &sheet_name,
7813 anchor.coord.row() + 1,
7814 anchor.coord.col() + 1,
7815 &err_val,
7816 );
7817 }
7818 return Ok(err_val);
7819 }
7820 let top_left = rows
7822 .first()
7823 .and_then(|r| r.first())
7824 .cloned()
7825 .unwrap_or(LiteralValue::Empty);
7826 self.graph.update_vertex_value(vertex_id, top_left.clone());
7827 Ok(top_left)
7828 }
7829 Err(e) => {
7830 self.clear_spill_projection_and_mirror(
7831 vertex_id,
7832 delta.as_deref_mut(),
7833 );
7834 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7835 .with_message(
7836 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
7837 )
7838 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7839 expected_rows: h,
7840 expected_cols: w,
7841 });
7842 let spill_val = LiteralValue::Error(spill_err.clone());
7843 if let Some(d) = delta.as_deref_mut() {
7844 let old = self
7845 .read_cell_value(
7846 self.graph.sheet_name(anchor.sheet_id),
7847 anchor.coord.row() + 1,
7848 anchor.coord.col() + 1,
7849 )
7850 .unwrap_or(LiteralValue::Empty);
7851 if old != spill_val {
7852 d.record_cell(
7853 anchor.sheet_id,
7854 anchor.coord.row(),
7855 anchor.coord.col(),
7856 );
7857 }
7858 }
7859 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7860 if self.config.arrow_storage_enabled
7861 && self.config.delta_overlay_enabled
7862 && self.config.write_formula_overlay_enabled
7863 {
7864 let sheet_name =
7865 self.graph.sheet_name(anchor.sheet_id).to_string();
7866 self.mirror_value_to_computed_overlay(
7867 &sheet_name,
7868 anchor.coord.row() + 1,
7869 anchor.coord.col() + 1,
7870 &spill_val,
7871 );
7872 }
7873 Ok(spill_val)
7874 }
7875 }
7876 }
7877 other => {
7878 let spill_cells = self
7880 .graph
7881 .spill_cells_for_anchor(vertex_id)
7882 .map(|cells| cells.to_vec())
7883 .unwrap_or_default();
7884 if let Some(d) = delta.as_deref_mut()
7885 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7886 {
7887 if spill_cells.is_empty() {
7888 let old = self
7889 .read_cell_value(
7890 self.graph.sheet_name(anchor.sheet_id),
7891 anchor.coord.row() + 1,
7892 anchor.coord.col() + 1,
7893 )
7894 .unwrap_or(LiteralValue::Empty);
7895 if old != other {
7896 d.record_cell(
7897 anchor.sheet_id,
7898 anchor.coord.row(),
7899 anchor.coord.col(),
7900 );
7901 }
7902 } else {
7903 for cell in spill_cells.iter() {
7904 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7905 let old = self
7906 .get_cell_value(
7907 sheet_name,
7908 cell.coord.row() + 1,
7909 cell.coord.col() + 1,
7910 )
7911 .unwrap_or(LiteralValue::Empty);
7912 let new = if cell.sheet_id == anchor.sheet_id
7913 && cell.coord.row() == anchor.coord.row()
7914 && cell.coord.col() == anchor.coord.col()
7915 {
7916 other.clone()
7917 } else {
7918 LiteralValue::Empty
7919 };
7920 Self::record_cell_if_changed(d, cell, &old, &new);
7921 }
7922 }
7923 }
7924 self.graph.clear_spill_region(vertex_id);
7925 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
7926 self.record_formula_plane_structural_change(scope);
7927 }
7928 if self.config.arrow_storage_enabled
7929 && self.config.delta_overlay_enabled
7930 && self.config.write_formula_overlay_enabled
7931 {
7932 let empty = LiteralValue::Empty;
7933 for cell in spill_cells.iter() {
7934 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7935 self.mirror_value_to_computed_overlay(
7936 &sheet_name,
7937 cell.coord.row() + 1,
7938 cell.coord.col() + 1,
7939 &empty,
7940 );
7941 }
7942 }
7943 self.graph.update_vertex_value(vertex_id, other.clone());
7944 if self.config.arrow_storage_enabled
7946 && self.config.delta_overlay_enabled
7947 && self.config.write_formula_overlay_enabled
7948 {
7949 let anchor = self
7950 .graph
7951 .get_cell_ref(vertex_id)
7952 .expect("cell ref for vertex");
7953 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7954 self.mirror_value_to_computed_overlay(
7955 &sheet_name,
7956 anchor.coord.row() + 1,
7957 anchor.coord.col() + 1,
7958 &other,
7959 );
7960 }
7961 Ok(other)
7962 }
7963 }
7964 }
7965 Err(e) => {
7966 let spill_cells = self
7969 .graph
7970 .spill_cells_for_anchor(vertex_id)
7971 .map(|cells| cells.to_vec())
7972 .unwrap_or_default();
7973 let err_val = LiteralValue::Error(e.clone());
7974 if let Some(d) = delta
7975 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7976 {
7977 if spill_cells.is_empty() {
7978 let old = self
7979 .read_cell_value(
7980 self.graph.sheet_name(anchor.sheet_id),
7981 anchor.coord.row() + 1,
7982 anchor.coord.col() + 1,
7983 )
7984 .unwrap_or(LiteralValue::Empty);
7985 if old != err_val {
7986 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7987 }
7988 } else {
7989 for cell in spill_cells.iter() {
7990 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7991 let old = self
7992 .get_cell_value(
7993 sheet_name,
7994 cell.coord.row() + 1,
7995 cell.coord.col() + 1,
7996 )
7997 .unwrap_or(LiteralValue::Empty);
7998 let new = if cell.sheet_id == anchor.sheet_id
7999 && cell.coord.row() == anchor.coord.row()
8000 && cell.coord.col() == anchor.coord.col()
8001 {
8002 err_val.clone()
8003 } else {
8004 LiteralValue::Empty
8005 };
8006 Self::record_cell_if_changed(d, cell, &old, &new);
8007 }
8008 }
8009 }
8010 self.graph.clear_spill_region(vertex_id);
8011 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
8012 self.record_formula_plane_structural_change(scope);
8013 }
8014 if self.config.arrow_storage_enabled
8015 && self.config.delta_overlay_enabled
8016 && self.config.write_formula_overlay_enabled
8017 {
8018 let empty = LiteralValue::Empty;
8019 for cell in spill_cells.iter() {
8020 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8021 self.mirror_value_to_computed_overlay(
8022 &sheet_name,
8023 cell.coord.row() + 1,
8024 cell.coord.col() + 1,
8025 &empty,
8026 );
8027 }
8028 }
8029 self.graph.update_vertex_value(vertex_id, err_val.clone());
8030 if self.config.arrow_storage_enabled
8031 && self.config.delta_overlay_enabled
8032 && self.config.write_formula_overlay_enabled
8033 {
8034 let anchor = self
8035 .graph
8036 .get_cell_ref(vertex_id)
8037 .expect("cell ref for vertex");
8038 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
8039 self.mirror_value_to_computed_overlay(
8040 &sheet_name,
8041 anchor.coord.row() + 1,
8042 anchor.coord.col() + 1,
8043 &err_val,
8044 );
8045 }
8046 Ok(err_val)
8047 }
8048 }
8049 }
8050
8051 fn evaluate_named_scalar(
8052 &mut self,
8053 vertex_id: VertexId,
8054 sheet_id: SheetId,
8055 ) -> Result<LiteralValue, ExcelError> {
8056 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
8057 ExcelError::new(ExcelErrorKind::Name)
8058 .with_message("Named range metadata missing".to_string())
8059 })?;
8060
8061 match &named_range.definition {
8062 NamedDefinition::Cell(cell_ref) => {
8063 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8064 let row = cell_ref.coord.row() + 1;
8065 let col = cell_ref.coord.col() + 1;
8066
8067 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
8068 && matches!(
8069 self.graph.get_vertex_kind(dep_vertex),
8070 VertexKind::FormulaScalar | VertexKind::FormulaArray
8071 )
8072 {
8073 let value = self.evaluate_vertex(dep_vertex)?;
8075 self.graph.update_vertex_value(vertex_id, value.clone());
8076 Ok(value)
8077 } else {
8078 let value = self
8079 .get_cell_value(sheet_name, row, col)
8080 .unwrap_or(LiteralValue::Empty);
8081 self.graph.update_vertex_value(vertex_id, value.clone());
8082 Ok(value)
8083 }
8084 }
8085 NamedDefinition::Literal(v) => {
8086 let out = v.clone();
8087 self.graph.update_vertex_value(vertex_id, out.clone());
8088 Ok(out)
8089 }
8090 NamedDefinition::Formula { ast, .. } => {
8091 let context_sheet = match named_range.scope {
8092 NameScope::Sheet(id) => id,
8093 NameScope::Workbook => sheet_id,
8094 };
8095 let sheet_name = self.graph.sheet_name(context_sheet);
8096 let cell_ref = self
8097 .graph
8098 .get_cell_ref(vertex_id)
8099 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
8100 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
8101 match interpreter.evaluate_ast(ast) {
8102 Ok(cv) => {
8103 let value = cv.into_literal();
8104 match value {
8105 LiteralValue::Array(_) => {
8106 let err = ExcelError::new(ExcelErrorKind::NImpl)
8107 .with_message("Array result in scalar named range".to_string());
8108 let err_val = LiteralValue::Error(err.clone());
8109 self.graph.update_vertex_value(vertex_id, err_val.clone());
8110 Ok(err_val)
8111 }
8112 other => {
8113 self.graph.update_vertex_value(vertex_id, other.clone());
8114 Ok(other)
8115 }
8116 }
8117 }
8118 Err(err) => {
8119 let err_val = LiteralValue::Error(err.clone());
8120 self.graph.update_vertex_value(vertex_id, err_val.clone());
8121 Ok(err_val)
8122 }
8123 }
8124 }
8125 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
8126 .with_message("Range-valued name evaluated as scalar".to_string())),
8127 }
8128 }
8129
8130 fn evaluate_named_array(
8131 &mut self,
8132 vertex_id: VertexId,
8133 sheet_id: SheetId,
8134 ) -> Result<LiteralValue, ExcelError> {
8135 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
8136 ExcelError::new(ExcelErrorKind::Name)
8137 .with_message("Named range metadata missing".to_string())
8138 })?;
8139
8140 let out = match &named_range.definition {
8141 NamedDefinition::Range(range_ref) => {
8142 if range_ref.start.sheet_id != range_ref.end.sheet_id {
8143 return Err(ExcelError::new(ExcelErrorKind::Ref)
8144 .with_message("Named range cannot span sheets".to_string()));
8145 }
8146
8147 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
8148 let sr0 = range_ref.start.coord.row();
8149 let sc0 = range_ref.start.coord.col();
8150 let er0 = range_ref.end.coord.row();
8151 let ec0 = range_ref.end.coord.col();
8152 if sr0 > er0 || sc0 > ec0 {
8153 return Err(ExcelError::new(ExcelErrorKind::Ref)
8154 .with_message("Invalid named range bounds".to_string()));
8155 }
8156
8157 let h = (er0 - sr0 + 1) as usize;
8158 let w = (ec0 - sc0 + 1) as usize;
8159 let cell_count = (h as u64).saturating_mul(w as u64);
8160 if cell_count > self.config.spill.max_spill_cells as u64 {
8161 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
8162 "Named range too large to materialize as an array".to_string(),
8163 ));
8164 }
8165
8166 let mut rows = Vec::with_capacity(h);
8167 for r0 in sr0..=er0 {
8168 let mut row = Vec::with_capacity(w);
8169 for c0 in sc0..=ec0 {
8170 let v = self
8171 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
8172 .unwrap_or(LiteralValue::Empty);
8173 row.push(v);
8174 }
8175 rows.push(row);
8176 }
8177 LiteralValue::Array(rows)
8178 }
8179 NamedDefinition::Cell(cell_ref) => {
8180 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8181 let row = cell_ref.coord.row() + 1;
8182 let col = cell_ref.coord.col() + 1;
8183 let v = self
8184 .get_cell_value(sheet_name, row, col)
8185 .unwrap_or(LiteralValue::Empty);
8186 LiteralValue::Array(vec![vec![v]])
8187 }
8188 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
8189 NamedDefinition::Formula { ast, .. } => {
8190 let context_sheet = match named_range.scope {
8191 NameScope::Sheet(id) => id,
8192 NameScope::Workbook => sheet_id,
8193 };
8194 let sheet_name = self.graph.sheet_name(context_sheet);
8195 let cell_ref = self
8196 .graph
8197 .get_cell_ref(vertex_id)
8198 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
8199 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
8200 match interpreter.evaluate_ast(ast) {
8201 Ok(cv) => {
8202 let v = cv.into_literal();
8203 match v {
8204 LiteralValue::Array(_) => v,
8205 other => LiteralValue::Array(vec![vec![other]]),
8206 }
8207 }
8208 Err(err) => LiteralValue::Error(err),
8209 }
8210 }
8211 };
8212
8213 self.graph.update_vertex_value(vertex_id, out.clone());
8214 Ok(out)
8215 }
8216
8217 pub fn evaluate_until(
8219 &mut self,
8220 targets: &[(&str, u32, u32)],
8221 ) -> Result<EvalResult, ExcelError> {
8222 #[cfg(feature = "tracing")]
8223 let _span_eval = tracing::info_span!("evaluate_until", targets = targets.len()).entered();
8224 let start = crate::instant::FzInstant::now();
8225 self.begin_evaluation_request();
8226 self.graph.flush_pending_edge_deltas();
8229 let _source_cache = self.source_cache_session();
8230 if self.graph.formula_authority().active_span_count() > 0 {
8231 return self.evaluate_authoritative_formula_plane_all();
8232 }
8233
8234 let mut target_addrs = Vec::new();
8236 for (sheet, row, col) in targets {
8237 let sheet_id = self.graph.sheet_id_mut(sheet);
8240 let coord = Coord::from_excel(*row, *col, true, true);
8241 target_addrs.push(CellRef::new(sheet_id, coord));
8242 }
8243
8244 let mut target_vertex_ids = Vec::new();
8246 for addr in &target_addrs {
8247 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
8248 target_vertex_ids.push(*vertex_id);
8249 }
8250 }
8251
8252 if target_vertex_ids.is_empty() {
8253 return Ok(EvalResult {
8254 computed_vertices: 0,
8255 cycle_errors: 0,
8256 elapsed: start.elapsed(),
8257 });
8258 }
8259
8260 #[cfg(feature = "tracing")]
8262 let _span_sub = tracing::info_span!("demand_subgraph_build").entered();
8263 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
8264 #[cfg(feature = "tracing")]
8265 drop(_span_sub);
8266
8267 if precedents_to_eval.is_empty() {
8268 return Ok(EvalResult {
8269 computed_vertices: 0,
8270 cycle_errors: 0,
8271 elapsed: start.elapsed(),
8272 });
8273 }
8274
8275 let scheduler = Scheduler::new(&self.graph);
8277 #[cfg(feature = "tracing")]
8278 let _span_sched =
8279 tracing::info_span!("schedule_build", vertices = precedents_to_eval.len()).entered();
8280 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
8281 #[cfg(feature = "tracing")]
8282 drop(_span_sched);
8283
8284 let mut cycle_errors = 0;
8288 let mut computed_vertices = 0;
8289 for &unit in &schedule.units {
8290 match unit {
8291 ScheduleUnit::Cycle(i) => {
8292 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
8293 cycle_errors += 1;
8294 }
8295 }
8296 ScheduleUnit::Layer(i) => {
8297 let layer = schedule.unit_layer(i);
8298 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8299 computed_vertices += self.evaluate_layer_parallel(layer)?;
8300 } else {
8301 computed_vertices += self.evaluate_layer_sequential(layer)?;
8302 }
8303 }
8304 }
8305 }
8306
8307 self.graph.clear_dirty_flags(&precedents_to_eval);
8311
8312 self.redirty_for_next_recalc();
8314
8315 Ok(EvalResult {
8316 computed_vertices,
8317 cycle_errors,
8318 elapsed: start.elapsed(),
8319 })
8320 }
8321
8322 fn evaluate_until_with_delta_collector(
8323 &mut self,
8324 targets: &[(&str, u32, u32)],
8325 delta: &mut DeltaCollector,
8326 ) -> Result<EvalResult, ExcelError> {
8327 #[cfg(feature = "tracing")]
8328 let _span_eval =
8329 tracing::info_span!("evaluate_until_with_delta", targets = targets.len()).entered();
8330 let start = crate::instant::FzInstant::now();
8331 self.begin_evaluation_request();
8332 self.graph.flush_pending_edge_deltas();
8333 let _source_cache = self.source_cache_session();
8334
8335 let mut target_addrs = Vec::new();
8336 for (sheet, row, col) in targets {
8337 let sheet_id = self.graph.sheet_id_mut(sheet);
8338 let coord = Coord::from_excel(*row, *col, true, true);
8339 target_addrs.push(CellRef::new(sheet_id, coord));
8340 }
8341
8342 let mut target_vertex_ids = Vec::new();
8343 for addr in &target_addrs {
8344 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
8345 target_vertex_ids.push(*vertex_id);
8346 }
8347 }
8348
8349 if target_vertex_ids.is_empty() {
8350 return Ok(EvalResult {
8351 computed_vertices: 0,
8352 cycle_errors: 0,
8353 elapsed: start.elapsed(),
8354 });
8355 }
8356
8357 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
8358
8359 if precedents_to_eval.is_empty() {
8360 return Ok(EvalResult {
8361 computed_vertices: 0,
8362 cycle_errors: 0,
8363 elapsed: start.elapsed(),
8364 });
8365 }
8366
8367 let scheduler = Scheduler::new(&self.graph);
8368 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
8369
8370 let mut cycle_errors = 0;
8371 let mut computed_vertices = 0;
8372 for &unit in &schedule.units {
8373 match unit {
8374 ScheduleUnit::Cycle(i) => {
8375 if self.handle_cycle_unit(schedule.unit_cycle(i), Some(delta), None, None)? > 0
8376 {
8377 cycle_errors += 1;
8378 }
8379 }
8380 ScheduleUnit::Layer(i) => {
8381 let layer = schedule.unit_layer(i);
8382 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8383 computed_vertices +=
8384 self.evaluate_layer_parallel_with_delta(layer, delta)?;
8385 } else {
8386 computed_vertices +=
8387 self.evaluate_layer_sequential_with_delta(layer, delta)?;
8388 }
8389 }
8390 }
8391 }
8392
8393 self.graph.clear_dirty_flags(&precedents_to_eval);
8394 self.redirty_for_next_recalc();
8395
8396 Ok(EvalResult {
8397 computed_vertices,
8398 cycle_errors,
8399 elapsed: start.elapsed(),
8400 })
8401 }
8402
8403 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
8405 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
8406 vertices.sort_unstable();
8407 if vertices.is_empty() {
8408 return Ok(RecalcPlan {
8409 schedule: crate::engine::Schedule {
8410 units: Vec::new(),
8411 layers: Vec::new(),
8412 cycles: Vec::new(),
8413 },
8414 has_dynamic_refs: false,
8415 });
8416 }
8417
8418 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
8419 let (schedule, _, _) = self.create_evaluation_schedule_uncached(&vertices)?;
8420 Ok(RecalcPlan {
8421 schedule,
8422 has_dynamic_refs,
8423 })
8424 }
8425
8426 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
8428 self.begin_evaluation_request();
8429 let _source_cache = self.source_cache_session();
8430 self.validate_deterministic_mode()?;
8431 if self.config.defer_graph_building {
8432 self.build_graph_all()?;
8433 }
8434 if self.graph.formula_authority().active_span_count() > 0 {
8435 return self.evaluate_authoritative_formula_plane_all();
8436 }
8437
8438 let start = crate::instant::FzInstant::now();
8439 let dirty_vertices = self.graph.get_evaluation_vertices();
8440 if dirty_vertices.is_empty() {
8441 return Ok(EvalResult {
8442 computed_vertices: 0,
8443 cycle_errors: 0,
8444 elapsed: start.elapsed(),
8445 });
8446 }
8447
8448 if plan.has_dynamic_refs {
8451 self.virtual_dep_fallback_activations =
8452 self.virtual_dep_fallback_activations.saturating_add(1);
8453 return self.evaluate_all();
8454 }
8455
8456 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
8457 let mut computed_vertices = 0;
8458 let mut cycle_errors = 0;
8459
8460 for &unit in &plan.schedule.units {
8461 match unit {
8462 ScheduleUnit::Cycle(i) => {
8463 let stamped = self.handle_cycle_unit(
8468 plan.schedule.unit_cycle(i),
8469 None,
8470 Some(&dirty_set),
8471 None,
8472 )?;
8473 if stamped > 0 {
8474 cycle_errors += 1;
8475 }
8476 }
8477 ScheduleUnit::Layer(i) => {
8478 let work: Vec<VertexId> = plan
8479 .schedule
8480 .unit_layer(i)
8481 .vertices
8482 .iter()
8483 .copied()
8484 .filter(|v| dirty_set.contains(v))
8485 .collect();
8486 if work.is_empty() {
8487 continue;
8488 }
8489 let temp_layer = crate::engine::scheduler::Layer { vertices: work };
8490 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
8491 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
8492 } else {
8493 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
8494 }
8495 }
8496 }
8497 }
8498
8499 self.graph.clear_dirty_flags(&dirty_vertices);
8500 self.redirty_for_next_recalc();
8501
8502 Ok(EvalResult {
8503 computed_vertices,
8504 cycle_errors,
8505 elapsed: start.elapsed(),
8506 })
8507 }
8508 fn evaluate_authoritative_formula_plane_all(&mut self) -> Result<EvalResult, ExcelError> {
8509 self.begin_evaluation_request();
8516 if self.graph.formula_authority().active_span_count() == 0 {
8521 #[cfg(test)]
8522 {
8523 self.last_formula_plane_span_eval_report = None;
8524 }
8525 return self.evaluate_all_legacy_impl();
8526 }
8527
8528 let current_indexes_epoch = self.graph.formula_authority().indexes_epoch();
8533 let span_seed_mode = if self.formula_plane_indexes_epoch_seen != current_indexes_epoch {
8534 SpanSeedMode::WholeAll
8535 } else {
8536 SpanSeedMode::DirtyClosure
8537 };
8538 let pending_changed_regions = self
8541 .graph
8542 .formula_authority_mut()
8543 .take_pending_changed_regions();
8544
8545 if matches!(span_seed_mode, SpanSeedMode::DirtyClosure)
8553 && pending_changed_regions.is_empty()
8554 {
8555 #[cfg(test)]
8556 {
8557 self.last_formula_plane_span_eval_report = None;
8558 }
8559 return self.evaluate_all_legacy_impl();
8560 }
8561
8562 let start = crate::instant::FzInstant::now();
8563 let mut span_seed_mode = span_seed_mode;
8564 let mut pending_changed_regions = pending_changed_regions;
8565 let mut prepass_cycle_errors = 0usize;
8568 const MAX_CYCLE_DEMOTE_ITERS: usize = 64;
8569 let mut cycle_demote_iters = 0usize;
8570 let (schedule, span_refs_by_id, plane_epoch, legacy_vertices) = loop {
8571 let (schedule, span_refs_by_id, plane_epoch, legacy_vertices) =
8572 self.build_formula_plane_mixed_schedule(span_seed_mode, &pending_changed_regions)?;
8573
8574 if schedule.is_authoritative_safe() {
8575 break (schedule, span_refs_by_id, plane_epoch, legacy_vertices);
8576 }
8577
8578 let has_cycle_fallback = schedule.stats.cycle_count > 0
8588 || schedule
8589 .fallbacks
8590 .iter()
8591 .any(|fb| fb.reason == MixedScheduleFallbackReason::CycleDetected);
8592 if !has_cycle_fallback {
8593 self.formula_plane_capacity_bailouts =
8594 self.formula_plane_capacity_bailouts.saturating_add(1);
8595 #[cfg(test)]
8596 {
8597 self.last_formula_plane_span_eval_report = None;
8598 }
8599 return self.evaluate_all_legacy_impl();
8600 }
8601
8602 let cyclic_spans = self.collect_cyclic_span_refs(&schedule, &span_refs_by_id);
8612 if !cyclic_spans.is_empty() {
8613 self.demote_cyclic_spans(&cyclic_spans)?;
8614 }
8615
8616 if self.graph.formula_authority().active_span_count() == 0 {
8617 return self.evaluate_all_legacy_impl();
8620 }
8621
8622 prepass_cycle_errors =
8626 prepass_cycle_errors.saturating_add(self.evaluate_legacy_cycle_prepass()?);
8627
8628 span_seed_mode = SpanSeedMode::WholeAll;
8632 pending_changed_regions = self
8633 .graph
8634 .formula_authority_mut()
8635 .take_pending_changed_regions();
8636
8637 cycle_demote_iters += 1;
8638 if cycle_demote_iters >= MAX_CYCLE_DEMOTE_ITERS {
8639 return self.evaluate_all_legacy_impl();
8644 }
8645 };
8646
8647 let mut computed_vertices = 0usize;
8648 #[cfg(test)]
8649 {
8650 self.last_formula_plane_span_eval_report = None;
8651 }
8652 for layer in schedule.layers {
8653 let mut buffer = ComputedWriteBuffer::default();
8654 let mut sink = SpanComputedWriteSink::new(&mut buffer);
8655 let work_items = layer.work;
8656 let mut work_index = 0usize;
8657 while work_index < work_items.len() {
8658 match work_items[work_index].producer {
8659 FormulaProducerId::Span(span_id) => {
8660 let span_ref = *span_refs_by_id.get(&span_id).ok_or_else(|| {
8661 ExcelError::new(ExcelErrorKind::NImpl)
8662 .with_message("FormulaPlane schedule referenced a stale span")
8663 })?;
8664 let sheet_id = {
8665 let authority = self.graph.formula_authority();
8666 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
8667 ExcelError::new(ExcelErrorKind::NImpl)
8668 .with_message("FormulaPlane schedule referenced a stale span")
8669 })?;
8670 span.sheet_id
8671 };
8672 let current_sheet = self.graph.sheet_name(sheet_id);
8673 let authority = self.graph.formula_authority();
8674 let evaluator = SpanEvaluator::new(
8675 &authority.plane,
8676 self,
8677 current_sheet,
8678 self.graph.data_store(),
8679 self.graph.sheet_reg(),
8680 );
8681 #[cfg(test)]
8682 let mut last_group_report = None;
8683 while work_index < work_items.len() {
8684 let FormulaProducerId::Span(group_span_id) =
8685 work_items[work_index].producer
8686 else {
8687 break;
8688 };
8689 let group_span_ref =
8690 *span_refs_by_id.get(&group_span_id).ok_or_else(|| {
8691 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8692 "FormulaPlane schedule referenced a stale span",
8693 )
8694 })?;
8695 let group_sheet_id = {
8696 let authority = self.graph.formula_authority();
8697 let span =
8698 authority.plane.spans.get(group_span_ref).ok_or_else(|| {
8699 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8700 "FormulaPlane schedule referenced a stale span",
8701 )
8702 })?;
8703 span.sheet_id
8704 };
8705 if group_sheet_id != sheet_id {
8706 break;
8707 }
8708
8709 let dirty = producer_dirty_to_span_dirty(
8710 work_items[work_index].dirty.clone(),
8711 group_span_ref,
8712 );
8713 let task = SpanEvalTask {
8714 span: group_span_ref,
8715 dirty,
8716 plane_epoch,
8717 };
8718 let report =
8719 evaluator.evaluate_task(&task, &mut sink).map_err(|err| {
8720 ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
8721 "FormulaPlane span evaluation failed: {err:?}"
8722 ))
8723 })?;
8724 #[cfg(test)]
8725 {
8726 last_group_report = Some(report.clone());
8727 }
8728 computed_vertices = computed_vertices
8729 .saturating_add(report.span_eval_placement_count as usize);
8730 work_index = work_index.saturating_add(1);
8731 }
8732 #[cfg(test)]
8733 {
8734 if let Some(report) = last_group_report {
8735 self.last_formula_plane_span_eval_report = Some(report);
8736 }
8737 }
8738 }
8739 FormulaProducerId::Legacy(_) => {
8740 let mut vertices = Vec::new();
8749 while work_index < work_items.len() {
8750 let FormulaProducerId::Legacy(vertex_id) =
8751 work_items[work_index].producer
8752 else {
8753 break;
8754 };
8755 vertices.push(vertex_id);
8756 work_index = work_index.saturating_add(1);
8757 }
8758 let legacy_layer = crate::engine::scheduler::Layer { vertices };
8759 let evaluated =
8760 if self.thread_pool.is_some() && legacy_layer.vertices.len() > 1 {
8761 self.evaluate_layer_parallel(&legacy_layer)?
8762 } else {
8763 self.evaluate_layer_sequential(&legacy_layer)?
8764 };
8765 computed_vertices = computed_vertices.saturating_add(evaluated);
8766 }
8767 }
8768 }
8769 self.flush_computed_write_buffer(&mut buffer)?;
8770 }
8771
8772 self.graph.clear_dirty_flags(&legacy_vertices);
8773 self.redirty_for_next_recalc();
8777 self.formula_plane_indexes_epoch_seen = self.graph.formula_authority().indexes_epoch();
8780 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8781 Ok(EvalResult {
8782 computed_vertices,
8783 cycle_errors: prepass_cycle_errors,
8784 elapsed: start.elapsed(),
8785 })
8786 }
8787
8788 fn build_formula_plane_mixed_schedule(
8789 &self,
8790 span_seed_mode: SpanSeedMode,
8791 pending_changed_regions: &[Region],
8792 ) -> Result<FormulaPlaneMixedScheduleBuild, ExcelError> {
8793 let authority = self.graph.formula_authority();
8794 let mut producer_results = FormulaProducerResultIndex::default();
8795 let mut consumer_reads = FormulaConsumerReadIndex::default();
8796 let mut work = Vec::new();
8797
8798 let dirty_legacy: rustc_hash::FxHashSet<VertexId> =
8804 self.graph.get_evaluation_vertices().into_iter().collect();
8805
8806 let span_refs = authority.active_span_refs();
8807 let span_refs_by_id = span_refs
8808 .iter()
8809 .copied()
8810 .map(|span_ref| (span_ref.id, span_ref))
8811 .collect::<BTreeMap<_, _>>();
8812 for span_ref in &span_refs {
8813 let span = authority.plane.spans.get(*span_ref).ok_or_else(|| {
8814 ExcelError::new(ExcelErrorKind::NImpl)
8815 .with_message("FormulaPlane active span ref is stale")
8816 })?;
8817 let result_region = Region::from_domain(span.result_region.domain());
8818 producer_results.insert_producer(FormulaProducerId::Span(span.id), result_region);
8819 let Some(read_summary_id) = span.read_summary_id else {
8820 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8821 .with_message("FormulaPlane active span is missing read summary"));
8822 };
8823 let Some(read_summary) = authority.plane.span_read_summaries.get(read_summary_id)
8824 else {
8825 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8826 .with_message("FormulaPlane active span has stale read summary"));
8827 };
8828 if read_summary.result_region != result_region {
8829 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8830 .with_message("FormulaPlane active span read summary is stale"));
8831 }
8832 for dependency in &read_summary.dependencies {
8833 consumer_reads.insert_read(
8834 FormulaProducerId::Span(span.id),
8835 dependency.read_region,
8836 read_summary.result_region,
8837 dependency.projection,
8838 );
8839 }
8840 if matches!(span_seed_mode, SpanSeedMode::WholeAll) {
8841 work.push(FormulaProducerWork {
8842 producer: FormulaProducerId::Span(span.id),
8843 dirty: ProducerDirtyDomain::Whole,
8844 });
8845 }
8846 }
8847
8848 let legacy_vertices = self.graph.formula_vertices();
8849 let mut scheduled_legacy_vertices = Vec::new();
8850 for vertex in &legacy_vertices {
8851 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8852 continue;
8853 };
8854 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8855 producer_results.insert_producer(FormulaProducerId::Legacy(*vertex), result_region);
8856 if dirty_legacy.contains(vertex) {
8857 scheduled_legacy_vertices.push(*vertex);
8858 work.push(FormulaProducerWork {
8859 producer: FormulaProducerId::Legacy(*vertex),
8860 dirty: ProducerDirtyDomain::Whole,
8861 });
8862 }
8863 }
8864
8865 for vertex in &legacy_vertices {
8866 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8867 continue;
8868 };
8869 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8870 let mut seen = rustc_hash::FxHashSet::default();
8871 for dep in self.graph.get_dependencies(*vertex) {
8872 let Some(dep_cell) = self.graph.get_cell_ref_for_vertex(dep) else {
8873 continue;
8874 };
8875 let read_region = Region::point(
8876 dep_cell.sheet_id,
8877 dep_cell.coord.row(),
8878 dep_cell.coord.col(),
8879 );
8880 if seen.insert(read_region) {
8881 consumer_reads.insert_read(
8882 FormulaProducerId::Legacy(*vertex),
8883 read_region,
8884 result_region,
8885 DirtyProjectionRule::WholeResult,
8886 );
8887 }
8888 }
8889 if let Some(ranges) = self.graph.get_range_dependencies(*vertex) {
8890 for range in ranges {
8891 let Some(read_region) = self.shared_range_to_region_pattern(range)? else {
8892 continue;
8893 };
8894 if seen.insert(read_region) {
8895 consumer_reads.insert_read(
8896 FormulaProducerId::Legacy(*vertex),
8897 read_region,
8898 result_region,
8899 DirtyProjectionRule::WholeResult,
8900 );
8901 }
8902 }
8903 }
8904 }
8905
8906 if matches!(span_seed_mode, SpanSeedMode::DirtyClosure)
8911 && !pending_changed_regions.is_empty()
8912 {
8913 use crate::formula_plane::producer::compute_dirty_closure;
8914 let producer_results_ref = &producer_results;
8915 let closure = compute_dirty_closure(
8916 &consumer_reads,
8917 pending_changed_regions.iter().copied(),
8918 |producer| producer_results_ref.producer_result_region(producer),
8919 );
8920 for fallback_work in closure.work {
8921 work.push(fallback_work);
8922 }
8923 if !closure.fallbacks.is_empty() {
8926 let mut already_whole: rustc_hash::FxHashSet<_> = work
8927 .iter()
8928 .filter_map(|w| match (w.producer, &w.dirty) {
8929 (FormulaProducerId::Span(id), ProducerDirtyDomain::Whole) => Some(id),
8930 _ => None,
8931 })
8932 .collect();
8933 for fb in &closure.fallbacks {
8934 if let FormulaProducerId::Span(id) = fb.consumer
8935 && already_whole.insert(id)
8936 {
8937 work.push(FormulaProducerWork {
8938 producer: FormulaProducerId::Span(id),
8939 dirty: ProducerDirtyDomain::Whole,
8940 });
8941 }
8942 }
8943 }
8944 }
8945
8946 let schedule = build_mixed_schedule(work, &producer_results, &consumer_reads);
8947 Ok((
8948 schedule,
8949 span_refs_by_id,
8950 authority.plane.epoch().0,
8951 scheduled_legacy_vertices,
8952 ))
8953 }
8954}
8955
8956#[derive(Clone, Copy, Debug)]
8960enum SpanSeedMode {
8961 WholeAll,
8962 DirtyClosure,
8963}
8964
8965impl<R> Engine<R>
8966where
8967 R: EvaluationContext,
8968{
8969 fn shared_range_to_region_pattern(
8970 &self,
8971 range: &crate::reference::SharedRangeRef<'static>,
8972 ) -> Result<Option<Region>, ExcelError> {
8973 use crate::reference::SharedSheetLocator;
8974 let sheet_id = match range.sheet {
8975 SharedSheetLocator::Id(id) => id,
8976 SharedSheetLocator::Current => self.graph.default_sheet_id(),
8977 SharedSheetLocator::Name(_) => return Ok(None),
8978 };
8979 match (
8980 range.start_row,
8981 range.end_row,
8982 range.start_col,
8983 range.end_col,
8984 ) {
8985 (Some(sr), Some(er), Some(sc), Some(ec)) => Ok(Some(Region::rect(
8986 sheet_id, sr.index, er.index, sc.index, ec.index,
8987 ))),
8988 (None, None, Some(sc), Some(ec)) if sc.index == ec.index => {
8989 Ok(Some(Region::whole_col(sheet_id, sc.index)))
8990 }
8991 (Some(sr), Some(er), None, None) if sr.index == er.index => {
8992 Ok(Some(Region::whole_row(sheet_id, sr.index)))
8993 }
8994 _ => Ok(None),
8995 }
8996 }
8997
8998 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
9000 debug_assert!(
9001 !self.graph.deferred_dirty_active(),
9002 "deferred-dirty scope leaked into evaluate_all: a begin_deferred_dirty \
9003 was not balanced by end_deferred_dirty"
9004 );
9005 self.lookup_index_cache.reset_counters();
9006 let _source_cache = self.source_cache_session();
9007 self.validate_deterministic_mode()?;
9008 if self.config.defer_graph_building {
9009 self.build_graph_all()?;
9011 }
9012 self.evaluate_all_coordinator()
9013 }
9014
9015 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
9020 self.begin_evaluation_request();
9021 if self.config.formula_plane_mode == FormulaPlaneMode::AuthoritativeExperimental {
9022 return self.evaluate_authoritative_formula_plane_all();
9023 }
9024 self.evaluate_all_legacy_impl()
9025 }
9026
9027 fn legacy_pass_run_units(
9033 &mut self,
9034 schedule: &crate::engine::scheduler::Schedule,
9035 ) -> Result<(usize, usize), ExcelError> {
9036 let mut computed_vertices = 0;
9037 let mut cycle_count = 0;
9038 for &unit in &schedule.units {
9039 match unit {
9040 ScheduleUnit::Cycle(i) => {
9041 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
9042 cycle_count += 1;
9043 }
9044 }
9045 ScheduleUnit::Layer(i) => {
9046 let layer = schedule.unit_layer(i);
9047 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9048 computed_vertices += self.evaluate_layer_parallel(layer)?;
9049 } else {
9050 computed_vertices += self.evaluate_layer_sequential(layer)?;
9051 }
9052 }
9053 }
9054 }
9055 Ok((computed_vertices, cycle_count))
9056 }
9057
9058 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
9070 self.reset_virtual_dep_telemetry_if_disabled();
9071 #[cfg(feature = "tracing")]
9072 let _span_eval = tracing::info_span!("evaluate_all").entered();
9073 let start = crate::instant::FzInstant::now();
9074 let mut computed_vertices = 0;
9075 let mut cycle_errors = 0;
9076 let mut replan_iterations = 0;
9077 const MAX_REPLAN: usize = 5;
9078 let mut telemetry = self
9079 .config
9080 .enable_virtual_dep_telemetry
9081 .then(|| self.start_virtual_dep_telemetry());
9082
9083 loop {
9084 let to_evaluate = self.graph.get_evaluation_vertices();
9085 if to_evaluate.is_empty() {
9086 if let Some(t) = telemetry.as_mut()
9087 && t.bailout_reason.is_none()
9088 {
9089 t.bailout_reason = Some("no_work");
9090 }
9091 break;
9092 }
9093
9094 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9095 if let Some(t) = telemetry.as_mut() {
9096 Self::accumulate_schedule_meta(t, &meta);
9097 }
9098
9099 let (pass_computed, pass_cycles) = self.legacy_pass_run_units(&schedule)?;
9100 computed_vertices += pass_computed;
9101 cycle_errors += pass_cycles;
9102
9103 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9105 if let Some(t) = telemetry.as_mut() {
9106 t.changed_vdeps_total += changed_vertices.len();
9107 }
9108
9109 self.graph.clear_dirty_flags(&to_evaluate);
9110 for v in &changed_vertices {
9111 self.graph.set_dirty(*v, true);
9112 }
9113
9114 if changed_vertices.is_empty() {
9115 if let Some(t) = telemetry.as_mut() {
9116 t.bailout_reason = Some("converged");
9117 }
9118 break;
9119 }
9120 if replan_iterations >= MAX_REPLAN {
9121 if let Some(t) = telemetry.as_mut() {
9122 t.bailout_reason = Some("max_replan");
9123 }
9124 break;
9125 }
9126
9127 replan_iterations += 1;
9128 }
9129
9130 if let Some(mut t) = telemetry {
9131 t.replan_iterations = replan_iterations;
9132 self.last_virtual_dep_telemetry = t;
9133 }
9134
9135 self.redirty_for_next_recalc();
9137
9138 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9140
9141 Ok(EvalResult {
9142 computed_vertices,
9143 cycle_errors,
9144 elapsed: start.elapsed(),
9145 })
9146 }
9147
9148 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
9149 let mut collector = DeltaCollector::new(DeltaMode::Cells);
9150 let result = self.evaluate_all_with_delta_collector(&mut collector)?;
9151 Ok((result, collector.finish()))
9152 }
9153
9154 fn evaluate_all_with_delta_collector(
9155 &mut self,
9156 delta: &mut DeltaCollector,
9157 ) -> Result<EvalResult, ExcelError> {
9158 self.begin_evaluation_request();
9159 let _source_cache = self.source_cache_session();
9160 if self.config.defer_graph_building {
9161 self.build_graph_all()?;
9162 }
9163 if self.graph.formula_authority().active_span_count() > 0 {
9164 let _ = delta;
9165 return self.evaluate_authoritative_formula_plane_all();
9166 }
9167 self.reset_virtual_dep_telemetry_if_disabled();
9168 #[cfg(feature = "tracing")]
9169 let _span_eval = tracing::info_span!("evaluate_all_with_delta").entered();
9170 let start = crate::instant::FzInstant::now();
9171 let mut computed_vertices = 0;
9172 let mut cycle_errors = 0;
9173
9174 let mut replan_iterations = 0;
9175 const MAX_REPLAN: usize = 5;
9176 let mut telemetry = self
9177 .config
9178 .enable_virtual_dep_telemetry
9179 .then(|| self.start_virtual_dep_telemetry());
9180
9181 loop {
9182 let to_evaluate = self.graph.get_evaluation_vertices();
9183 if to_evaluate.is_empty() {
9184 if let Some(t) = telemetry.as_mut()
9185 && t.bailout_reason.is_none()
9186 {
9187 t.bailout_reason = Some("no_work");
9188 }
9189 break;
9190 }
9191
9192 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9193 if let Some(t) = telemetry.as_mut() {
9194 Self::accumulate_schedule_meta(t, &meta);
9195 }
9196
9197 for &unit in &schedule.units {
9198 match unit {
9199 ScheduleUnit::Cycle(i) => {
9200 if self.handle_cycle_unit(
9201 schedule.unit_cycle(i),
9202 Some(delta),
9203 None,
9204 None,
9205 )? > 0
9206 {
9207 cycle_errors += 1;
9208 }
9209 }
9210 ScheduleUnit::Layer(i) => {
9211 let layer = schedule.unit_layer(i);
9212 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9213 computed_vertices +=
9214 self.evaluate_layer_parallel_with_delta(layer, delta)?;
9215 } else {
9216 computed_vertices +=
9217 self.evaluate_layer_sequential_with_delta(layer, delta)?;
9218 }
9219 }
9220 }
9221 }
9222
9223 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9224 if let Some(t) = telemetry.as_mut() {
9225 t.changed_vdeps_total += changed_vertices.len();
9226 }
9227 self.graph.clear_dirty_flags(&to_evaluate);
9228 for v in &changed_vertices {
9229 self.graph.set_dirty(*v, true);
9230 }
9231
9232 if changed_vertices.is_empty() {
9233 if let Some(t) = telemetry.as_mut() {
9234 t.bailout_reason = Some("converged");
9235 }
9236 break;
9237 }
9238 if replan_iterations >= MAX_REPLAN {
9239 if let Some(t) = telemetry.as_mut() {
9240 t.bailout_reason = Some("max_replan");
9241 }
9242 break;
9243 }
9244 replan_iterations += 1;
9245 }
9246
9247 if let Some(mut t) = telemetry {
9248 t.replan_iterations = replan_iterations;
9249 self.last_virtual_dep_telemetry = t;
9250 }
9251
9252 self.redirty_for_next_recalc();
9253 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9254
9255 Ok(EvalResult {
9256 computed_vertices,
9257 cycle_errors,
9258 elapsed: start.elapsed(),
9259 })
9260 }
9261
9262 pub fn evaluate_cell(
9272 &mut self,
9273 sheet: &str,
9274 row: u32,
9275 col: u32,
9276 ) -> Result<Option<LiteralValue>, ExcelError> {
9277 if row == 0 || col == 0 {
9278 return Err(ExcelError::new(ExcelErrorKind::Ref)
9279 .with_message("Row and column must be >= 1".to_string()));
9280 }
9281
9282 if self.config.defer_graph_building {
9290 self.build_graph_all()?;
9291 }
9292
9293 let result = self.evaluate_cells(&[(sheet, row, col)])?;
9294
9295 match result.len() {
9296 0 => Ok(None),
9297 1 => {
9298 let v = result.into_iter().next().unwrap();
9299 Ok(v)
9300 }
9301 _ => unreachable!("evaluate_cells returned unexpected length"),
9302 }
9303 }
9304
9305 pub fn evaluate_cells(
9312 &mut self,
9313 targets: &[(&str, u32, u32)],
9314 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9315 debug_assert!(
9316 !self.graph.deferred_dirty_active(),
9317 "deferred-dirty scope leaked into evaluate_cells: a begin_deferred_dirty \
9318 was not balanced by end_deferred_dirty"
9319 );
9320 self.validate_deterministic_mode()?;
9321 if targets.is_empty() {
9322 return Ok(Vec::new());
9323 }
9324 if self.config.defer_graph_building {
9328 self.build_graph_all()?;
9329 }
9330 if self.graph.formula_authority().active_span_count() > 0 {
9331 let _ = self.evaluate_authoritative_formula_plane_all()?;
9332 } else {
9333 self.evaluate_until(targets)?;
9334 }
9335 Ok(targets
9336 .iter()
9337 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9338 .collect())
9339 }
9340
9341 pub fn evaluate_cells_cancellable(
9342 &mut self,
9343 targets: &[(&str, u32, u32)],
9344 cancel_flag: Arc<AtomicBool>,
9345 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9346 self.active_cancel_flag = Some(cancel_flag.clone());
9347 let res = self.evaluate_cells_cancellable_impl(targets, &cancel_flag);
9348 self.active_cancel_flag = None;
9349 res
9350 }
9351
9352 fn evaluate_cells_cancellable_impl(
9353 &mut self,
9354 targets: &[(&str, u32, u32)],
9355 cancel_flag: &AtomicBool,
9356 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9357 self.validate_deterministic_mode()?;
9358 if targets.is_empty() {
9359 return Ok(Vec::new());
9360 }
9361 if self.config.defer_graph_building {
9365 self.build_graph_all()?;
9366 }
9367 if self.graph.formula_authority().active_span_count() > 0 {
9368 if cancel_flag.load(Ordering::Relaxed) {
9369 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9370 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9371 ));
9372 }
9373 let _ = self.evaluate_authoritative_formula_plane_all()?;
9374 return Ok(targets
9375 .iter()
9376 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9377 .collect());
9378 }
9379
9380 let a1_targets: Vec<String> = targets
9383 .iter()
9384 .map(|(s, r, c)| {
9385 format!("{}!{}", s, col_letters_from_1based(*c).unwrap()) + &r.to_string()
9386 })
9387 .collect();
9388 let a1_refs: Vec<&str> = a1_targets.iter().map(|s| s.as_str()).collect();
9389
9390 self.evaluate_until_cancellable_impl(&a1_refs, cancel_flag)?;
9391
9392 Ok(targets
9393 .iter()
9394 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9395 .collect())
9396 }
9397
9398 pub fn evaluate_cells_with_delta(
9399 &mut self,
9400 targets: &[(&str, u32, u32)],
9401 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
9402 self.validate_deterministic_mode()?;
9403 if targets.is_empty() {
9404 return Ok((Vec::new(), EvalDelta::default()));
9405 }
9406 if self.config.defer_graph_building {
9407 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
9408 for (s, _, _) in targets.iter() {
9409 sheets.insert(*s);
9410 }
9411 self.build_graph_for_sheets(sheets.iter().cloned())?;
9412 }
9413 if self.graph.formula_authority().active_span_count() > 0 {
9414 let _ = self.evaluate_authoritative_formula_plane_all()?;
9415 let values = targets
9416 .iter()
9417 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9418 .collect();
9419 return Ok((values, EvalDelta::default()));
9420 }
9421 let mut collector = DeltaCollector::new(DeltaMode::Cells);
9422 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
9423 let values = targets
9424 .iter()
9425 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9426 .collect();
9427 Ok((values, collector.finish()))
9428 }
9429
9430 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
9432 if targets.is_empty() {
9433 return Ok(EvalPlan {
9434 total_vertices_to_evaluate: 0,
9435 layers: Vec::new(),
9436 cycles_detected: 0,
9437 dirty_count: 0,
9438 volatile_count: 0,
9439 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9440 estimated_parallel_layers: 0,
9441 target_cells: Vec::new(),
9442 });
9443 }
9444 if self.config.defer_graph_building && self.has_staged_formulas() {
9445 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
9446 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
9447 ));
9448 }
9449
9450 let addresses: Vec<String> = targets
9452 .iter()
9453 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
9454 .collect();
9455
9456 let mut target_addrs = Vec::new();
9458 for (sheet, row, col) in targets {
9459 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
9460 let coord = Coord::from_excel(*row, *col, true, true);
9461 target_addrs.push(CellRef::new(sheet_id, coord));
9462 }
9463 }
9464
9465 let mut target_vertex_ids = Vec::new();
9467 for addr in &target_addrs {
9468 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
9469 target_vertex_ids.push(*vertex_id);
9470 }
9471 }
9472
9473 if target_vertex_ids.is_empty() {
9474 return Ok(EvalPlan {
9475 total_vertices_to_evaluate: 0,
9476 layers: Vec::new(),
9477 cycles_detected: 0,
9478 dirty_count: 0,
9479 volatile_count: 0,
9480 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9481 estimated_parallel_layers: 0,
9482 target_cells: addresses,
9483 });
9484 }
9485
9486 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
9488
9489 if precedents_to_eval.is_empty() {
9490 return Ok(EvalPlan {
9491 total_vertices_to_evaluate: 0,
9492 layers: Vec::new(),
9493 cycles_detected: 0,
9494 dirty_count: 0,
9495 volatile_count: 0,
9496 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9497 estimated_parallel_layers: 0,
9498 target_cells: addresses,
9499 });
9500 }
9501
9502 let mut dirty_count = 0;
9504 let mut volatile_count = 0;
9505 for &vertex_id in &precedents_to_eval {
9506 if self.graph.is_dirty(vertex_id) {
9507 dirty_count += 1;
9508 }
9509 if self.graph.is_volatile(vertex_id) {
9510 volatile_count += 1;
9511 }
9512 }
9513
9514 let scheduler = Scheduler::new(&self.graph);
9516 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
9517
9518 let mut layers = Vec::new();
9520 let mut estimated_parallel_layers = 0;
9521 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
9522
9523 for layer in &schedule.layers {
9524 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
9525 if parallel_eligible {
9526 estimated_parallel_layers += 1;
9527 }
9528
9529 let sample_cells: Vec<String> = layer
9531 .vertices
9532 .iter()
9533 .take(5)
9534 .filter_map(|&vertex_id| {
9535 self.graph
9536 .get_cell_ref_for_vertex(vertex_id)
9537 .map(|cell_ref| {
9538 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
9539 format!(
9540 "{}!{}{}",
9541 sheet_name,
9542 Self::col_to_letters(cell_ref.coord.col()),
9543 cell_ref.coord.row() + 1
9544 )
9545 })
9546 })
9547 .collect();
9548
9549 layers.push(LayerInfo {
9550 vertex_count: layer.vertices.len(),
9551 parallel_eligible,
9552 sample_cells,
9553 });
9554 }
9555
9556 Ok(EvalPlan {
9557 total_vertices_to_evaluate: precedents_to_eval.len(),
9558 layers,
9559 cycles_detected: schedule.cycles.len(),
9560 dirty_count,
9561 volatile_count,
9562 parallel_enabled,
9563 estimated_parallel_layers,
9564 target_cells: addresses,
9565 })
9566 }
9567 fn create_evaluation_schedule(
9569 &mut self,
9570 to_evaluate: &[VertexId],
9571 ) -> Result<ScheduleBuildOutput, ExcelError> {
9572 self.graph.flush_pending_edge_deltas();
9575 if self.can_use_static_schedule_cache(to_evaluate) {
9576 if let Some(cached) = self.cached_static_schedule.as_ref()
9577 && cached.topology_epoch == self.topology_epoch
9578 && cached.candidate_vertices.as_slice() == to_evaluate
9579 {
9580 let meta = ScheduleBuildMeta {
9581 candidate_vertices: to_evaluate.len(),
9582 vdeps_vertices: 0,
9583 vdeps_edges: 0,
9584 builder_elapsed_ms: 0,
9585 used_virtual_schedule: false,
9586 schedule_cache_hit: true,
9587 schedule_cache_eligible: true,
9588 };
9589 return Ok((cached.schedule.clone(), FxHashMap::default(), meta));
9590 }
9591
9592 let (schedule, vdeps, mut meta) =
9593 self.create_evaluation_schedule_uncached(to_evaluate)?;
9594 meta.schedule_cache_hit = false;
9595 meta.schedule_cache_eligible = true;
9596 if vdeps.is_empty() {
9597 self.cached_static_schedule = Some(CachedScheduleEntry {
9598 topology_epoch: self.topology_epoch,
9599 candidate_vertices: to_evaluate.to_vec(),
9600 schedule: schedule.clone(),
9601 });
9602 }
9603 return Ok((schedule, vdeps, meta));
9604 }
9605
9606 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_uncached(to_evaluate)?;
9607 meta.schedule_cache_hit = false;
9608 meta.schedule_cache_eligible = false;
9609 Ok((schedule, vdeps, meta))
9610 }
9611
9612 fn create_evaluation_schedule_uncached(
9613 &self,
9614 to_evaluate: &[VertexId],
9615 ) -> Result<ScheduleBuildOutput, ExcelError> {
9616 let builder = VirtualDepBuilder::new(self);
9617 let (vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
9618 if self.config.enable_virtual_dep_telemetry {
9619 let build_started = crate::instant::FzInstant::now();
9620 let (vdeps, augmented) = builder.build(to_evaluate);
9621 let builder_elapsed_ms = build_started.elapsed().as_millis();
9622 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
9623 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
9624 } else {
9625 let (vdeps, augmented) = builder.build(to_evaluate);
9626 (vdeps, augmented, 0, 0)
9627 };
9628
9629 let mut final_evaluate = to_evaluate.to_vec();
9630 if !augmented.is_empty() {
9631 final_evaluate.extend(augmented);
9632 final_evaluate.sort_unstable();
9633 final_evaluate.dedup();
9634 }
9635
9636 let use_virtual = !vdeps.is_empty();
9637
9638 let scheduler = Scheduler::new(&self.graph);
9639 let schedule = if use_virtual {
9640 scheduler.create_schedule_with_virtual(&final_evaluate, &vdeps)?
9641 } else {
9642 scheduler.create_schedule(&final_evaluate)?
9643 };
9644
9645 let meta = ScheduleBuildMeta {
9646 candidate_vertices: to_evaluate.len(),
9647 vdeps_vertices: vdeps.len(),
9648 vdeps_edges,
9649 builder_elapsed_ms,
9650 used_virtual_schedule: use_virtual,
9651 schedule_cache_hit: false,
9652 schedule_cache_eligible: false,
9653 };
9654
9655 Ok((schedule, vdeps, meta))
9656 }
9657
9658 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
9659 !to_evaluate.is_empty()
9660 && to_evaluate.iter().copied().all(|v| {
9661 !self.graph.is_dynamic(v) && self.graph.get_range_dependencies(v).is_none()
9662 })
9663 }
9664
9665 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
9666 VirtualDepTelemetry {
9667 fallback_mode_activations: self.virtual_dep_fallback_activations,
9668 ..VirtualDepTelemetry::default()
9669 }
9670 }
9671
9672 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
9673 telemetry.candidate_vertices_total += meta.candidate_vertices;
9674 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
9675 telemetry.vdeps_edges_total += meta.vdeps_edges;
9676 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
9677 if meta.schedule_cache_eligible {
9678 if meta.schedule_cache_hit {
9679 telemetry.schedule_cache_hits += 1;
9680 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
9681 } else {
9682 telemetry.schedule_cache_misses += 1;
9683 }
9684 }
9685 if meta.used_virtual_schedule {
9686 telemetry.schedule_virtual_passes += 1;
9687 } else {
9688 telemetry.schedule_static_passes += 1;
9689 }
9690 }
9691
9692 fn changed_virtual_dep_vertices(
9693 &self,
9694 to_evaluate: &[VertexId],
9695 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
9696 ) -> Vec<VertexId> {
9697 if !to_evaluate
9698 .iter()
9699 .copied()
9700 .any(|v| self.graph.is_dynamic(v))
9701 {
9702 return Vec::new();
9703 }
9704
9705 let builder = VirtualDepBuilder::new(self);
9706 let (new_vdeps, _) = builder.build(to_evaluate);
9707
9708 let mut candidates = FxHashSet::default();
9709 candidates.extend(old_vdeps.keys().copied());
9710 candidates.extend(new_vdeps.keys().copied());
9711
9712 let mut changed = Vec::new();
9713 for v in candidates {
9714 if old_vdeps.get(&v) != new_vdeps.get(&v) {
9715 changed.push(v);
9716 }
9717 }
9718 changed
9719 }
9720
9721 fn build_demand_subgraph(
9724 &self,
9725 target_vertices: &[VertexId],
9726 ) -> (
9727 Vec<VertexId>,
9728 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
9729 ) {
9730 #[cfg(feature = "tracing")]
9731 let _span =
9732 tracing::info_span!("demand_subgraph", targets = target_vertices.len()).entered();
9733 use rustc_hash::{FxHashMap, FxHashSet};
9734
9735 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
9736 let mut visited: FxHashSet<VertexId> = FxHashSet::default();
9737 let mut stack: Vec<VertexId> = Vec::new();
9738 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default(); for &t in target_vertices {
9741 stack.push(t);
9742 }
9743
9744 while let Some(v) = stack.pop() {
9745 if !visited.insert(v) {
9746 continue;
9747 }
9748 if !self.graph.vertex_exists(v) {
9749 continue;
9750 }
9751 match self.graph.get_vertex_kind(v) {
9759 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
9760 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
9761 to_evaluate.insert(v);
9762 }
9763 }
9764 VertexKind::NamedScalar
9765 | VertexKind::NamedArray
9766 | VertexKind::Range
9767 | VertexKind::InfiniteRange => {
9768 to_evaluate.insert(v);
9769 }
9770 _ => {}
9771 }
9772
9773 if let Some(dependencies) = self.graph.dependencies_slice(v) {
9784 for &dep in dependencies {
9785 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9786 stack.push(dep);
9787 }
9788 }
9789 } else {
9790 for dep in self.graph.get_dependencies(v) {
9791 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9792 stack.push(dep);
9793 }
9794 }
9795 } let builder = VirtualDepBuilder::new(self);
9797 let (vdeps_map, _) = builder.build(&[v]);
9798 if let Some(deps) = vdeps_map.get(&v) {
9799 for &u in deps {
9800 vdeps.entry(v).or_default().push(u);
9801 if !visited.contains(&u) {
9802 stack.push(u);
9803 }
9804 }
9805 }
9806 }
9807
9808 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
9809 result.sort_unstable();
9810 for deps in vdeps.values_mut() {
9812 deps.sort_unstable();
9813 deps.dedup();
9814 }
9815 (result, vdeps)
9816 }
9817
9818 fn col_to_letters(col: u32) -> String {
9820 col_letters_from_1based(col).expect("column index must be >= 1")
9821 }
9822
9823 pub fn evaluate_all_cancellable(
9825 &mut self,
9826 cancel_flag: Arc<AtomicBool>,
9827 ) -> Result<EvalResult, ExcelError> {
9828 self.active_cancel_flag = Some(cancel_flag.clone());
9829 let res = self.evaluate_all_cancellable_impl(&cancel_flag);
9830 self.active_cancel_flag = None;
9831 res
9832 }
9833
9834 fn evaluate_all_cancellable_impl(
9835 &mut self,
9836 cancel_flag: &AtomicBool,
9837 ) -> Result<EvalResult, ExcelError> {
9838 self.begin_evaluation_request();
9839 let _source_cache = self.source_cache_session();
9840 self.validate_deterministic_mode()?;
9841 if self.config.defer_graph_building {
9842 self.build_graph_all()?;
9843 }
9844 if self.graph.formula_authority().active_span_count() > 0 {
9845 if cancel_flag.load(Ordering::Relaxed) {
9846 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9847 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9848 ));
9849 }
9850 return self.evaluate_authoritative_formula_plane_all();
9851 }
9852 self.reset_virtual_dep_telemetry_if_disabled();
9853 let start = crate::instant::FzInstant::now();
9854 let mut computed_vertices = 0;
9855 let mut cycle_errors = 0;
9856
9857 let mut replan_iterations = 0;
9858 const MAX_REPLAN: usize = 5;
9859 let mut telemetry = self
9860 .config
9861 .enable_virtual_dep_telemetry
9862 .then(|| self.start_virtual_dep_telemetry());
9863
9864 loop {
9865 if cancel_flag.load(Ordering::Relaxed) {
9866 if let Some(mut t) = telemetry {
9867 t.bailout_reason = Some("cancelled");
9868 t.replan_iterations = replan_iterations;
9869 self.last_virtual_dep_telemetry = t;
9870 }
9871 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9872 .with_message("Evaluation cancelled before scheduling".to_string()));
9873 }
9874
9875 let to_evaluate = self.graph.get_evaluation_vertices();
9876 if to_evaluate.is_empty() {
9877 if let Some(t) = telemetry.as_mut()
9878 && t.bailout_reason.is_none()
9879 {
9880 t.bailout_reason = Some("no_work");
9881 }
9882 break;
9883 }
9884
9885 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9886 if let Some(t) = telemetry.as_mut() {
9887 Self::accumulate_schedule_meta(t, &meta);
9888 }
9889
9890 for &unit in &schedule.units {
9893 match unit {
9894 ScheduleUnit::Cycle(i) => {
9895 if cancel_flag.load(Ordering::Relaxed) {
9897 if let Some(mut t) = telemetry {
9898 t.bailout_reason = Some("cancelled");
9899 t.replan_iterations = replan_iterations;
9900 self.last_virtual_dep_telemetry = t;
9901 }
9902 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9903 "Evaluation cancelled during cycle handling".to_string(),
9904 ));
9905 }
9906
9907 if self.handle_cycle_unit(
9908 schedule.unit_cycle(i),
9909 None,
9910 None,
9911 Some(cancel_flag),
9912 )? > 0
9913 {
9914 cycle_errors += 1;
9915 }
9916 }
9917 ScheduleUnit::Layer(i) => {
9918 let layer = schedule.unit_layer(i);
9919 if cancel_flag.load(Ordering::Relaxed) {
9921 if let Some(mut t) = telemetry {
9922 t.bailout_reason = Some("cancelled");
9923 t.replan_iterations = replan_iterations;
9924 self.last_virtual_dep_telemetry = t;
9925 }
9926 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9927 .with_message("Evaluation cancelled between layers".to_string()));
9928 }
9929
9930 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9932 computed_vertices +=
9933 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
9934 } else {
9935 computed_vertices +=
9936 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
9937 }
9938 }
9939 }
9940 }
9941
9942 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9943 if let Some(t) = telemetry.as_mut() {
9944 t.changed_vdeps_total += changed_vertices.len();
9945 }
9946 self.graph.clear_dirty_flags(&to_evaluate);
9947 for v in &changed_vertices {
9948 self.graph.set_dirty(*v, true);
9949 }
9950
9951 if changed_vertices.is_empty() {
9952 if let Some(t) = telemetry.as_mut() {
9953 t.bailout_reason = Some("converged");
9954 }
9955 break;
9956 }
9957 if replan_iterations >= MAX_REPLAN {
9958 if let Some(t) = telemetry.as_mut() {
9959 t.bailout_reason = Some("max_replan");
9960 }
9961 break;
9962 }
9963 replan_iterations += 1;
9964 }
9965
9966 if let Some(mut t) = telemetry {
9967 t.replan_iterations = replan_iterations;
9968 self.last_virtual_dep_telemetry = t;
9969 }
9970
9971 self.redirty_for_next_recalc();
9973 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9974
9975 Ok(EvalResult {
9976 computed_vertices,
9977 cycle_errors,
9978 elapsed: start.elapsed(),
9979 })
9980 }
9981
9982 pub fn evaluate_until_cancellable(
9984 &mut self,
9985 targets: &[&str],
9986 cancel_flag: Arc<AtomicBool>,
9987 ) -> Result<EvalResult, ExcelError> {
9988 self.active_cancel_flag = Some(cancel_flag.clone());
9989 let res = self.evaluate_until_cancellable_impl(targets, &cancel_flag);
9990 self.active_cancel_flag = None;
9991 res
9992 }
9993
9994 fn evaluate_until_cancellable_impl(
9995 &mut self,
9996 targets: &[&str],
9997 cancel_flag: &AtomicBool,
9998 ) -> Result<EvalResult, ExcelError> {
9999 let start = crate::instant::FzInstant::now();
10000 self.begin_evaluation_request();
10001 self.graph.flush_pending_edge_deltas();
10002 if self.graph.formula_authority().active_span_count() > 0 {
10003 if cancel_flag.load(Ordering::Relaxed) {
10004 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
10005 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
10006 ));
10007 }
10008 return self.evaluate_authoritative_formula_plane_all();
10009 }
10010
10011 let mut target_addrs = Vec::new();
10013 for target in targets {
10014 let (sheet, row, col) = self.parse_a1_notation(target)?;
10015 let sheet_id = self.graph.sheet_id_mut(&sheet);
10016 let coord = Coord::from_excel(row, col, true, true);
10017 target_addrs.push(CellRef::new(sheet_id, coord));
10018 }
10019
10020 let mut target_vertex_ids = Vec::new();
10022 for addr in &target_addrs {
10023 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
10024 target_vertex_ids.push(*vertex_id);
10025 }
10026 }
10027
10028 if target_vertex_ids.is_empty() {
10029 return Ok(EvalResult {
10030 computed_vertices: 0,
10031 cycle_errors: 0,
10032 elapsed: start.elapsed(),
10033 });
10034 }
10035
10036 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
10038
10039 if precedents_to_eval.is_empty() {
10040 return Ok(EvalResult {
10041 computed_vertices: 0,
10042 cycle_errors: 0,
10043 elapsed: start.elapsed(),
10044 });
10045 }
10046
10047 let scheduler = Scheduler::new(&self.graph);
10049 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
10050
10051 let mut cycle_errors = 0;
10054 let mut computed_vertices = 0;
10055 for &unit in &schedule.units {
10056 match unit {
10057 ScheduleUnit::Cycle(i) => {
10058 if cancel_flag.load(Ordering::Relaxed) {
10060 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
10061 "Demand-driven evaluation cancelled during cycle handling".to_string(),
10062 ));
10063 }
10064
10065 if self.handle_cycle_unit(
10066 schedule.unit_cycle(i),
10067 None,
10068 None,
10069 Some(cancel_flag),
10070 )? > 0
10071 {
10072 cycle_errors += 1;
10073 }
10074 }
10075 ScheduleUnit::Layer(i) => {
10076 let layer = schedule.unit_layer(i);
10077 if cancel_flag.load(Ordering::Relaxed) {
10079 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
10080 "Demand-driven evaluation cancelled between layers".to_string(),
10081 ));
10082 }
10083
10084 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
10086 computed_vertices +=
10087 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
10088 } else {
10089 computed_vertices += self
10090 .evaluate_layer_sequential_cancellable_demand_driven(
10091 layer,
10092 cancel_flag,
10093 )?;
10094 }
10095 }
10096 }
10097 }
10098
10099 self.graph.clear_dirty_flags(&precedents_to_eval);
10101
10102 self.redirty_for_next_recalc();
10104
10105 Ok(EvalResult {
10106 computed_vertices,
10107 cycle_errors,
10108 elapsed: start.elapsed(),
10109 })
10110 }
10111
10112 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
10113 let mut parts = address.splitn(2, '!');
10114 let first = parts.next().unwrap_or_default();
10115 let remainder = parts.next();
10116
10117 let (sheet, cell_part) = match remainder {
10118 Some(cell) => (first.to_string(), cell),
10119 None => (self.default_sheet_name().to_string(), first),
10120 };
10121
10122 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
10123 ExcelError::new(ExcelErrorKind::Ref)
10124 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
10125 })?;
10126
10127 Ok((sheet, row, col))
10128 }
10129
10130 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
10132 use formualizer_parse::parser::ASTNodeType;
10133 match &ast.node_type {
10134 ASTNodeType::Function { name, args, .. } => {
10135 if let Some(func) = self
10136 .get_function("", name)
10137 .or_else(|| crate::function_registry::get("", name))
10138 && func.caps().contains(crate::function::FnCaps::VOLATILE)
10139 {
10140 return true;
10141 }
10142 args.iter()
10143 .any(|arg| self.is_ast_volatile_with_provider(arg))
10144 }
10145 ASTNodeType::BinaryOp { left, right, .. } => {
10146 self.is_ast_volatile_with_provider(left)
10147 || self.is_ast_volatile_with_provider(right)
10148 }
10149 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
10150 ASTNodeType::Array(rows) => rows.iter().any(|row| {
10151 row.iter()
10152 .any(|cell| self.is_ast_volatile_with_provider(cell))
10153 }),
10154 _ => false,
10155 }
10156 }
10157
10158 fn find_dirty_precedents(&self, target_vertices: &[VertexId]) -> Vec<VertexId> {
10160 let mut to_evaluate = FxHashSet::default();
10161 let mut visited = FxHashSet::default();
10162 let mut stack = Vec::new();
10163
10164 for &target in target_vertices {
10166 stack.push(target);
10167 }
10168
10169 while let Some(vertex_id) = stack.pop() {
10170 if !visited.insert(vertex_id) {
10171 continue; }
10173
10174 if self.graph.vertex_exists(vertex_id) {
10175 let kind = self.graph.get_vertex_kind(vertex_id);
10177 let needs_eval = match kind {
10178 super::vertex::VertexKind::FormulaScalar
10179 | super::vertex::VertexKind::FormulaArray => {
10180 self.graph.is_dirty(vertex_id) || self.graph.is_volatile(vertex_id)
10181 }
10182 _ => false, };
10184
10185 if needs_eval {
10186 to_evaluate.insert(vertex_id);
10187 }
10188
10189 if let Some(dependencies) = self.graph.dependencies_slice(vertex_id) {
10191 for &dep_id in dependencies {
10192 if !visited.contains(&dep_id) {
10193 stack.push(dep_id);
10194 }
10195 }
10196 } else {
10197 let dependencies = self.graph.get_dependencies(vertex_id);
10198 for dep_id in dependencies {
10199 if !visited.contains(&dep_id) {
10200 stack.push(dep_id);
10201 }
10202 }
10203 }
10204 }
10205 }
10206
10207 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
10208 result.sort_unstable();
10209 result
10210 }
10211
10212 fn evaluate_layer_sequential(
10214 &mut self,
10215 layer: &super::scheduler::Layer,
10216 ) -> Result<usize, ExcelError> {
10217 self.evaluate_layer_sequential_effects(layer)
10218 }
10219
10220 fn update_vertex_value_with_delta(
10221 &mut self,
10222 vertex_id: VertexId,
10223 new_value: LiteralValue,
10224 delta: &mut DeltaCollector,
10225 ) {
10226 if delta.mode != DeltaMode::Off
10227 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
10228 {
10229 let sheet_name = self.graph.sheet_name(cell.sheet_id);
10230 let old = self
10231 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
10232 .unwrap_or(LiteralValue::Empty);
10233 if old != new_value {
10234 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
10235 }
10236 }
10237 self.graph.update_vertex_value(vertex_id, new_value.clone());
10238 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
10239 }
10240
10241 fn evaluate_layer_sequential_with_delta(
10242 &mut self,
10243 layer: &super::scheduler::Layer,
10244 delta: &mut DeltaCollector,
10245 ) -> Result<usize, ExcelError> {
10246 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
10247 }
10248
10249 fn evaluate_layer_sequential_cancellable(
10251 &mut self,
10252 layer: &super::scheduler::Layer,
10253 cancel_flag: &AtomicBool,
10254 ) -> Result<usize, ExcelError> {
10255 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
10256 }
10257
10258 fn evaluate_layer_sequential_cancellable_demand_driven(
10260 &mut self,
10261 layer: &super::scheduler::Layer,
10262 cancel_flag: &AtomicBool,
10263 ) -> Result<usize, ExcelError> {
10264 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
10265 }
10266
10267 fn evaluate_layer_parallel(
10269 &mut self,
10270 layer: &super::scheduler::Layer,
10271 ) -> Result<usize, ExcelError> {
10272 self.evaluate_layer_parallel_effects(layer)
10273 }
10274
10275 fn evaluate_layer_parallel_with_delta(
10276 &mut self,
10277 layer: &super::scheduler::Layer,
10278 delta: &mut DeltaCollector,
10279 ) -> Result<usize, ExcelError> {
10280 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
10281 }
10282
10283 fn evaluate_layer_parallel_cancellable(
10285 &mut self,
10286 layer: &super::scheduler::Layer,
10287 cancel_flag: &AtomicBool,
10288 ) -> Result<usize, ExcelError> {
10289 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
10290 }
10291
10292 fn apply_parallel_vertex_result(
10297 &mut self,
10298 vertex_id: VertexId,
10299 result: LiteralValue,
10300 mut delta: Option<&mut DeltaCollector>,
10301 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10302 ) -> Result<(), ExcelError> {
10303 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
10306 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
10307 && owner != vertex_id
10308 {
10309 return Ok(());
10310 }
10311
10312 let kind = self.graph.get_vertex_kind(vertex_id);
10313
10314 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
10316 if is_formula {
10317 match result {
10318 LiteralValue::Array(rows) => {
10319 self.apply_array_result_from_parallel(
10320 vertex_id,
10321 rows,
10322 delta.as_deref_mut(),
10323 overwritable_formulas,
10324 )?;
10325 }
10326 other => {
10327 self.apply_non_array_result_from_parallel(
10328 vertex_id,
10329 other,
10330 delta.as_deref_mut(),
10331 );
10332 }
10333 }
10334 return Ok(());
10335 }
10336
10337 if let Some(d) = delta {
10339 self.update_vertex_value_with_delta(vertex_id, result, d);
10340 } else {
10341 self.graph.update_vertex_value(vertex_id, result.clone());
10342 self.mirror_vertex_value_to_overlay(vertex_id, &result);
10343 }
10344 Ok(())
10345 }
10346
10347 fn apply_non_array_result_from_parallel(
10348 &mut self,
10349 vertex_id: VertexId,
10350 value: LiteralValue,
10351 delta: Option<&mut DeltaCollector>,
10352 ) {
10353 let spill_cells = self
10356 .graph
10357 .spill_cells_for_anchor(vertex_id)
10358 .map(|cells| cells.to_vec())
10359 .unwrap_or_default();
10360
10361 if let Some(d) = delta
10362 && d.mode != DeltaMode::Off
10363 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
10364 {
10365 if spill_cells.is_empty() {
10366 let old = self
10367 .read_cell_value(
10368 self.graph.sheet_name(anchor.sheet_id),
10369 anchor.coord.row() + 1,
10370 anchor.coord.col() + 1,
10371 )
10372 .unwrap_or(LiteralValue::Empty);
10373 if old != value {
10374 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10375 }
10376 } else {
10377 for cell in spill_cells.iter() {
10378 let sheet_name = self.graph.sheet_name(cell.sheet_id);
10379 let old = self
10380 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
10381 .unwrap_or(LiteralValue::Empty);
10382 let new = if cell.sheet_id == anchor.sheet_id
10383 && cell.coord.row() == anchor.coord.row()
10384 && cell.coord.col() == anchor.coord.col()
10385 {
10386 value.clone()
10387 } else {
10388 LiteralValue::Empty
10389 };
10390 Self::record_cell_if_changed(d, cell, &old, &new);
10391 }
10392 }
10393 }
10394
10395 self.graph.clear_spill_region(vertex_id);
10396 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
10397 self.record_formula_plane_structural_change(scope);
10398 }
10399
10400 if self.config.arrow_storage_enabled
10401 && self.config.delta_overlay_enabled
10402 && self.config.write_formula_overlay_enabled
10403 {
10404 let empty = LiteralValue::Empty;
10405 for cell in spill_cells.iter() {
10406 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
10407 self.mirror_value_to_computed_overlay(
10408 &sheet_name,
10409 cell.coord.row() + 1,
10410 cell.coord.col() + 1,
10411 &empty,
10412 );
10413 }
10414 }
10415
10416 self.graph.update_vertex_value(vertex_id, value.clone());
10417 self.mirror_vertex_value_to_overlay(vertex_id, &value);
10418 }
10419
10420 fn apply_array_result_from_parallel(
10421 &mut self,
10422 vertex_id: VertexId,
10423 rows: Vec<Vec<LiteralValue>>,
10424 mut delta: Option<&mut DeltaCollector>,
10425 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10426 ) -> Result<(), ExcelError> {
10427 self.graph
10429 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
10430
10431 let anchor = self
10432 .graph
10433 .get_cell_ref(vertex_id)
10434 .expect("cell ref for vertex");
10435 let sheet_id = anchor.sheet_id;
10436 let h = rows.len() as u32;
10437 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
10438
10439 let spill_cells = (h as u64).saturating_mul(w as u64);
10441 if spill_cells > self.config.spill.max_spill_cells as u64 {
10442 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10443 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10444 .with_message("SpillTooLarge")
10445 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10446 expected_rows: h,
10447 expected_cols: w,
10448 });
10449 let spill_val = LiteralValue::Error(spill_err.clone());
10450 if let Some(d) = delta.as_deref_mut()
10451 && d.mode != DeltaMode::Off
10452 {
10453 let old = self
10454 .read_cell_value(
10455 self.graph.sheet_name(anchor.sheet_id),
10456 anchor.coord.row() + 1,
10457 anchor.coord.col() + 1,
10458 )
10459 .unwrap_or(LiteralValue::Empty);
10460 if old != spill_val {
10461 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10462 }
10463 }
10464 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10465 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10466 return Ok(());
10467 }
10468
10469 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);
10473 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
10474 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
10475 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10476 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10477 .with_message("Spill exceeds sheet bounds")
10478 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10479 expected_rows: h,
10480 expected_cols: w,
10481 });
10482 let spill_val = LiteralValue::Error(spill_err.clone());
10483 if let Some(d) = delta.as_deref_mut()
10484 && d.mode != DeltaMode::Off
10485 {
10486 let old = self
10487 .read_cell_value(
10488 self.graph.sheet_name(anchor.sheet_id),
10489 anchor.coord.row() + 1,
10490 anchor.coord.col() + 1,
10491 )
10492 .unwrap_or(LiteralValue::Empty);
10493 if old != spill_val {
10494 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10495 }
10496 }
10497 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10498 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10499 return Ok(());
10500 }
10501
10502 let mut targets = Vec::new();
10503 for r in 0..h {
10504 for c in 0..w {
10505 targets.push(self.graph.make_cell_ref_internal(
10506 sheet_id,
10507 anchor.coord.row() + r,
10508 anchor.coord.col() + c,
10509 ));
10510 }
10511 }
10512
10513 match self.spill_mgr.reserve(
10514 vertex_id,
10515 anchor,
10516 SpillShape { rows: h, cols: w },
10517 SpillMeta {
10518 epoch: self.recalc_epoch,
10519 config: self.config.spill,
10520 },
10521 ) {
10522 Ok(()) => {
10523 if let Err(e) = self.commit_spill_and_mirror(
10524 vertex_id,
10525 &targets,
10526 rows.clone(),
10527 delta.as_deref_mut(),
10528 overwritable_formulas,
10529 ) {
10530 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10531 let err_val = LiteralValue::Error(e.clone());
10532 if let Some(d) = delta.as_deref_mut()
10533 && d.mode != DeltaMode::Off
10534 {
10535 let old = self
10536 .read_cell_value(
10537 self.graph.sheet_name(anchor.sheet_id),
10538 anchor.coord.row() + 1,
10539 anchor.coord.col() + 1,
10540 )
10541 .unwrap_or(LiteralValue::Empty);
10542 if old != err_val {
10543 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10544 }
10545 }
10546 self.graph.update_vertex_value(vertex_id, err_val.clone());
10547 self.mirror_vertex_value_to_overlay(vertex_id, &err_val);
10548 return Ok(());
10549 }
10550
10551 let top_left = rows
10553 .first()
10554 .and_then(|r| r.first())
10555 .cloned()
10556 .unwrap_or(LiteralValue::Empty);
10557 self.graph.update_vertex_value(vertex_id, top_left.clone());
10558 self.mirror_vertex_value_to_overlay(vertex_id, &top_left);
10559 Ok(())
10560 }
10561 Err(e) => {
10562 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10563 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10564 .with_message(e.message.unwrap_or_else(|| "Spill blocked".to_string()))
10565 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10566 expected_rows: h,
10567 expected_cols: w,
10568 });
10569 let spill_val = LiteralValue::Error(spill_err.clone());
10570 if let Some(d) = delta
10571 && d.mode != DeltaMode::Off
10572 {
10573 let old = self
10574 .read_cell_value(
10575 self.graph.sheet_name(anchor.sheet_id),
10576 anchor.coord.row() + 1,
10577 anchor.coord.col() + 1,
10578 )
10579 .unwrap_or(LiteralValue::Empty);
10580 if old != spill_val {
10581 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10582 }
10583 }
10584 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10585 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10586 Ok(())
10587 }
10588 }
10589 }
10590
10591 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
10593 if !self.graph.vertex_exists(vertex_id) {
10595 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
10596 .with_message(format!("Vertex not found: {vertex_id:?}")));
10597 }
10598
10599 let kind = self.graph.get_vertex_kind(vertex_id);
10601 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
10602
10603 let ast_id = match kind {
10604 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
10605 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
10606 ast_id
10607 } else {
10608 return Ok(LiteralValue::Number(0.0));
10609 }
10610 }
10611 VertexKind::Empty | VertexKind::Cell => {
10612 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
10613 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10614 let row = cell_ref.coord.row() + 1;
10615 let col = cell_ref.coord.col() + 1;
10616 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
10617 return Ok(v);
10618 }
10619 }
10620 return Ok(LiteralValue::Number(0.0));
10621 }
10622 VertexKind::NamedScalar => {
10623 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10624 ExcelError::new(ExcelErrorKind::Name)
10625 .with_message("Named range metadata missing".to_string())
10626 })?;
10627
10628 return match &named_range.definition {
10629 NamedDefinition::Cell(cell_ref) => {
10630 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10631 Ok(self
10632 .get_cell_value(
10633 sheet_name,
10634 cell_ref.coord.row() + 1,
10635 cell_ref.coord.col() + 1,
10636 )
10637 .unwrap_or(LiteralValue::Empty))
10638 }
10639 NamedDefinition::Literal(v) => Ok(v.clone()),
10640 NamedDefinition::Formula { ast, .. } => {
10641 let context_sheet = match named_range.scope {
10642 NameScope::Sheet(id) => id,
10643 NameScope::Workbook => sheet_id,
10644 };
10645 let sheet_name = self.graph.sheet_name(context_sheet);
10646 let cell_ref = self
10647 .graph
10648 .get_cell_ref(vertex_id)
10649 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10650 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10651 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
10652 }
10653 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
10654 .with_message("Range-valued name evaluated as scalar".to_string())),
10655 };
10656 }
10657 VertexKind::NamedArray => {
10658 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10659 ExcelError::new(ExcelErrorKind::Name)
10660 .with_message("Named range metadata missing".to_string())
10661 })?;
10662
10663 return match &named_range.definition {
10664 NamedDefinition::Range(range_ref) => {
10665 if range_ref.start.sheet_id != range_ref.end.sheet_id {
10666 return Err(ExcelError::new(ExcelErrorKind::Ref)
10667 .with_message("Named range cannot span sheets".to_string()));
10668 }
10669 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
10670 let sr0 = range_ref.start.coord.row();
10671 let sc0 = range_ref.start.coord.col();
10672 let er0 = range_ref.end.coord.row();
10673 let ec0 = range_ref.end.coord.col();
10674 if sr0 > er0 || sc0 > ec0 {
10675 return Err(ExcelError::new(ExcelErrorKind::Ref)
10676 .with_message("Invalid named range bounds".to_string()));
10677 }
10678
10679 let h = (er0 - sr0 + 1) as usize;
10680 let w = (ec0 - sc0 + 1) as usize;
10681 let cell_count = (h as u64).saturating_mul(w as u64);
10682 if cell_count > self.config.spill.max_spill_cells as u64 {
10683 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
10684 "Named range too large to materialize as an array".to_string(),
10685 ));
10686 }
10687
10688 let mut rows = Vec::with_capacity(h);
10689 for r0 in sr0..=er0 {
10690 let mut row = Vec::with_capacity(w);
10691 for c0 in sc0..=ec0 {
10692 let v = self
10693 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
10694 .unwrap_or(LiteralValue::Empty);
10695 row.push(v);
10696 }
10697 rows.push(row);
10698 }
10699 Ok(LiteralValue::Array(rows))
10700 }
10701 NamedDefinition::Cell(cell_ref) => {
10702 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10703 let row = cell_ref.coord.row() + 1;
10704 let col = cell_ref.coord.col() + 1;
10705 let v = self
10706 .get_cell_value(sheet_name, row, col)
10707 .unwrap_or(LiteralValue::Empty);
10708 Ok(LiteralValue::Array(vec![vec![v]]))
10709 }
10710 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
10711 NamedDefinition::Formula { ast, .. } => {
10712 let context_sheet = match named_range.scope {
10713 NameScope::Sheet(id) => id,
10714 NameScope::Workbook => sheet_id,
10715 };
10716 let sheet_name = self.graph.sheet_name(context_sheet);
10717 let cell_ref = self
10718 .graph
10719 .get_cell_ref(vertex_id)
10720 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10721 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10722 match interpreter.evaluate_ast(ast) {
10723 Ok(cv) => {
10724 let v = cv.into_literal();
10725 match v {
10726 LiteralValue::Array(_) => Ok(v),
10727 other => Ok(LiteralValue::Array(vec![vec![other]])),
10728 }
10729 }
10730 Err(err) => Ok(LiteralValue::Error(err)),
10731 }
10732 }
10733 };
10734 }
10735 VertexKind::InfiniteRange
10736 | VertexKind::Range
10737 | VertexKind::External
10738 | VertexKind::Table => {
10739 return Ok(LiteralValue::Number(0.0));
10741 }
10742 };
10743
10744 let sheet_name = self.graph.sheet_name(sheet_id);
10746 let cell_ref = self
10747 .graph
10748 .get_cell_ref(vertex_id)
10749 .expect("cell ref for vertex");
10750 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10751
10752 interpreter
10753 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
10754 .map(|cv| cv.into_literal())
10755 }
10756
10757 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
10759 self.thread_pool.as_ref()
10760 }
10761}
10762
10763#[derive(Default)]
10764struct RowBoundsCache {
10765 snapshot: u64,
10766 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
10768}
10769
10770impl RowBoundsCache {
10771 fn new(snapshot: u64) -> Self {
10772 Self {
10773 snapshot,
10774 map: Default::default(),
10775 }
10776 }
10777 fn get_row_bounds(
10778 &self,
10779 sheet_id: SheetId,
10780 col_idx: usize,
10781 snapshot: u64,
10782 ) -> Option<(Option<u32>, Option<u32>)> {
10783 if self.snapshot != snapshot {
10784 return None;
10785 }
10786 self.map.get(&(sheet_id as u32, col_idx)).copied()
10787 }
10788 fn put_row_bounds(
10789 &mut self,
10790 sheet_id: SheetId,
10791 col_idx: usize,
10792 snapshot: u64,
10793 bounds: (Option<u32>, Option<u32>),
10794 ) {
10795 if self.snapshot != snapshot {
10796 self.snapshot = snapshot;
10797 self.map.clear();
10798 }
10799 self.map.insert((sheet_id as u32, col_idx), bounds);
10800 }
10801}
10802
10803struct UsedAxisBoundsCache {
10804 snapshot: u64,
10805 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10806 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10807 #[cfg(test)]
10808 row_hits: std::sync::atomic::AtomicUsize,
10809 #[cfg(test)]
10810 row_misses: std::sync::atomic::AtomicUsize,
10811 #[cfg(test)]
10812 col_hits: std::sync::atomic::AtomicUsize,
10813 #[cfg(test)]
10814 col_misses: std::sync::atomic::AtomicUsize,
10815}
10816
10817impl UsedAxisBoundsCache {
10818 fn new(snapshot: u64) -> Self {
10819 Self {
10820 snapshot,
10821 row_bounds_by_col_span: Default::default(),
10822 col_bounds_by_row_span: Default::default(),
10823 #[cfg(test)]
10824 row_hits: std::sync::atomic::AtomicUsize::new(0),
10825 #[cfg(test)]
10826 row_misses: std::sync::atomic::AtomicUsize::new(0),
10827 #[cfg(test)]
10828 col_hits: std::sync::atomic::AtomicUsize::new(0),
10829 #[cfg(test)]
10830 col_misses: std::sync::atomic::AtomicUsize::new(0),
10831 }
10832 }
10833
10834 fn reset_for_snapshot(&mut self, snapshot: u64) {
10835 if self.snapshot != snapshot {
10836 self.snapshot = snapshot;
10837 self.row_bounds_by_col_span.clear();
10838 self.col_bounds_by_row_span.clear();
10839 }
10840 }
10841
10842 fn get_row_bounds(
10843 &self,
10844 sheet_id: SheetId,
10845 start_col: u32,
10846 end_col: u32,
10847 snapshot: u64,
10848 ) -> Option<Option<(u32, u32)>> {
10849 if self.snapshot != snapshot {
10850 return None;
10851 }
10852 let cached = self
10853 .row_bounds_by_col_span
10854 .get(&(sheet_id, start_col, end_col))
10855 .copied();
10856 #[cfg(test)]
10857 if cached.is_some() {
10858 self.row_hits.fetch_add(1, Ordering::Relaxed);
10859 }
10860 cached
10861 }
10862
10863 fn put_row_bounds(
10864 &mut self,
10865 sheet_id: SheetId,
10866 start_col: u32,
10867 end_col: u32,
10868 snapshot: u64,
10869 bounds: Option<(u32, u32)>,
10870 ) {
10871 self.reset_for_snapshot(snapshot);
10872 self.row_bounds_by_col_span
10873 .insert((sheet_id, start_col, end_col), bounds);
10874 #[cfg(test)]
10875 self.row_misses.fetch_add(1, Ordering::Relaxed);
10876 }
10877
10878 fn get_col_bounds(
10879 &self,
10880 sheet_id: SheetId,
10881 start_row: u32,
10882 end_row: u32,
10883 snapshot: u64,
10884 ) -> Option<Option<(u32, u32)>> {
10885 if self.snapshot != snapshot {
10886 return None;
10887 }
10888 let cached = self
10889 .col_bounds_by_row_span
10890 .get(&(sheet_id, start_row, end_row))
10891 .copied();
10892 #[cfg(test)]
10893 if cached.is_some() {
10894 self.col_hits.fetch_add(1, Ordering::Relaxed);
10895 }
10896 cached
10897 }
10898
10899 fn put_col_bounds(
10900 &mut self,
10901 sheet_id: SheetId,
10902 start_row: u32,
10903 end_row: u32,
10904 snapshot: u64,
10905 bounds: Option<(u32, u32)>,
10906 ) {
10907 self.reset_for_snapshot(snapshot);
10908 self.col_bounds_by_row_span
10909 .insert((sheet_id, start_row, end_row), bounds);
10910 #[cfg(test)]
10911 self.col_misses.fetch_add(1, Ordering::Relaxed);
10912 }
10913}
10914
10915#[derive(Default)]
10917pub struct ShimSpillManager {
10918 region_locks: RegionLockManager,
10919 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
10920}
10921
10922impl ShimSpillManager {
10923 pub(crate) fn reserve(
10924 &mut self,
10925 owner: VertexId,
10926 anchor_cell: CellRef,
10927 shape: SpillShape,
10928 _meta: SpillMeta,
10929 ) -> Result<(), ExcelError> {
10930 let region = crate::engine::spill::Region {
10932 sheet_id: anchor_cell.sheet_id as u32,
10933 row_start: anchor_cell.coord.row(),
10934 row_end: anchor_cell
10935 .coord
10936 .row()
10937 .saturating_add(shape.rows)
10938 .saturating_sub(1),
10939 col_start: anchor_cell.coord.col(),
10940 col_end: anchor_cell
10941 .coord
10942 .col()
10943 .saturating_add(shape.cols)
10944 .saturating_sub(1),
10945 };
10946 match self.region_locks.reserve(region, owner) {
10947 Ok(id) => {
10948 if id != 0 {
10949 self.active_locks.insert(owner, id);
10950 }
10951 Ok(())
10952 }
10953 Err(e) => Err(e),
10954 }
10955 }
10956
10957 pub(crate) fn release_owner(&mut self, owner: VertexId) {
10963 if let Some(id) = self.active_locks.remove(&owner) {
10964 self.region_locks.release(id);
10965 }
10966 }
10967
10968 pub(crate) fn commit_array_with_value_probe<F>(
10969 &mut self,
10970 graph: &mut DependencyGraph,
10971 anchor_vertex: VertexId,
10972 targets: &[CellRef],
10973 rows: Vec<Vec<LiteralValue>>,
10974 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10975 mut value_probe: F,
10976 ) -> Result<(), ExcelError>
10977 where
10978 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
10979 {
10980 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
10981
10982 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
10986 anchor_vertex,
10987 targets,
10988 overwritable_formulas,
10989 );
10990 if let Err(e) = plan_res {
10991 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10992 self.region_locks.release(id);
10993 }
10994 return Err(e);
10995 }
10996
10997 if !graph.value_cache_enabled() {
10998 let (expected_rows, expected_cols) = if targets.is_empty() {
11000 (0u32, 0u32)
11001 } else {
11002 let mut min_r = u32::MAX;
11003 let mut max_r = 0u32;
11004 let mut min_c = u32::MAX;
11005 let mut max_c = 0u32;
11006 for cell in targets {
11007 let r = cell.coord.row();
11008 let c = cell.coord.col();
11009 min_r = min_r.min(r);
11010 max_r = max_r.max(r);
11011 min_c = min_c.min(c);
11012 max_c = max_c.max(c);
11013 }
11014 (
11015 max_r.saturating_sub(min_r).saturating_add(1),
11016 max_c.saturating_sub(min_c).saturating_add(1),
11017 )
11018 };
11019
11020 let anchor_cell = graph
11021 .get_cell_ref(anchor_vertex)
11022 .expect("anchor cell ref for spill commit");
11023
11024 for cell in targets {
11025 if *cell == anchor_cell {
11027 continue;
11028 }
11029 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
11031 continue;
11032 }
11033 if let Some(&vid) = graph.get_vertex_id_for_address(cell)
11035 && vid != anchor_vertex
11036 {
11037 match graph.get_vertex_kind(vid) {
11038 crate::engine::vertex::VertexKind::FormulaScalar
11039 | crate::engine::vertex::VertexKind::FormulaArray => {
11040 continue;
11042 }
11043 _ => {}
11044 }
11045 }
11046
11047 if let Some(v) = value_probe(graph, cell)
11048 && !matches!(v, LiteralValue::Empty)
11049 {
11050 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11051 self.region_locks.release(id);
11052 }
11053 return Err(ExcelError::new(ExcelErrorKind::Spill)
11054 .with_message("BlockedByValue")
11055 .with_extra(ExcelErrorExtra::Spill {
11056 expected_rows,
11057 expected_cols,
11058 }));
11059 }
11060 }
11061 }
11062
11063 let commit_res = graph.commit_spill_region_atomic_with_fault(
11064 anchor_vertex,
11065 targets.to_vec(),
11066 rows,
11067 None,
11068 );
11069 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11070 self.region_locks.release(id);
11071 }
11072 commit_res.map(|_| ())
11073 }
11074
11075 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
11077 &mut self,
11078 engine: &mut Engine<R>,
11079 anchor_vertex: VertexId,
11080 targets: &[CellRef],
11081 rows: Vec<Vec<LiteralValue>>,
11082 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
11083 ) -> Result<(), ExcelError> {
11084 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
11086 anchor_vertex,
11087 targets,
11088 overwritable_formulas,
11089 );
11090 if let Err(e) = plan_res {
11091 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11092 self.region_locks.release(id);
11093 }
11094 return Err(e);
11095 }
11096
11097 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
11098 anchor_vertex,
11099 targets.to_vec(),
11100 rows.clone(),
11101 None,
11102 );
11103 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11104 self.region_locks.release(id);
11105 }
11106 commit_res.map(|_| ())?;
11107
11108 if engine.config.arrow_storage_enabled
11110 && engine.config.delta_overlay_enabled
11111 && engine.config.write_formula_overlay_enabled
11112 {
11113 for (idx, cell) in targets.iter().enumerate() {
11115 let (r_off, c_off) = {
11116 if rows.is_empty() || rows[0].is_empty() {
11117 (0usize, 0usize)
11118 } else {
11119 let width = rows[0].len();
11120 (idx / width, idx % width)
11121 }
11122 };
11123 let v = rows
11124 .get(r_off)
11125 .and_then(|r| r.get(c_off))
11126 .cloned()
11127 .unwrap_or(LiteralValue::Empty);
11128 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
11129 engine.mirror_value_to_computed_overlay(
11130 &sheet_name,
11131 cell.coord.row() + 1,
11132 cell.coord.col() + 1,
11133 &v,
11134 );
11135 }
11136 }
11137 Ok(())
11138 }
11139}
11140
11141impl<R> Engine<R>
11142where
11143 R: EvaluationContext,
11144{
11145 fn resolve_shared_ref(
11146 &self,
11147 reference: &ReferenceType,
11148 current_sheet: &str,
11149 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
11150 use formualizer_common::{
11151 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
11152 SheetRef as SharedRef,
11153 };
11154
11155 let sr = match reference {
11157 ReferenceType::Cell {
11158 sheet,
11159 row,
11160 col,
11161 row_abs,
11162 col_abs,
11163 } => {
11164 let row0 = row
11165 .checked_sub(1)
11166 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11167 let col0 = col
11168 .checked_sub(1)
11169 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11170 let sheet_loc = match sheet.as_deref() {
11171 Some(name) => SheetLocator::from_name(name),
11172 None => SheetLocator::Current,
11173 };
11174 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
11175 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
11176 }
11177 ReferenceType::Range {
11178 sheet,
11179 start_row,
11180 start_col,
11181 end_row,
11182 end_col,
11183 start_row_abs,
11184 start_col_abs,
11185 end_row_abs,
11186 end_col_abs,
11187 } => {
11188 let sheet_loc = match sheet.as_deref() {
11189 Some(name) => SheetLocator::from_name(name),
11190 None => SheetLocator::Current,
11191 };
11192 let sr = start_row
11193 .map(|r| {
11194 r.checked_sub(1)
11195 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11196 })
11197 .transpose()?;
11198 let sc = start_col
11199 .map(|c| {
11200 c.checked_sub(1)
11201 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11202 })
11203 .transpose()?;
11204 let er = end_row
11205 .map(|r| {
11206 r.checked_sub(1)
11207 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11208 })
11209 .transpose()?;
11210 let ec = end_col
11211 .map(|c| {
11212 c.checked_sub(1)
11213 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11214 })
11215 .transpose()?;
11216 let range = SharedRangeRef::from_parts(
11217 sheet_loc,
11218 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
11219 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
11220 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
11221 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
11222 )
11223 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
11224 SharedRef::Range(range)
11225 }
11226 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11227 };
11228
11229 let current_id = self
11230 .graph
11231 .sheet_id(current_sheet)
11232 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11233
11234 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
11235 match loc {
11236 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
11237 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
11238 SheetLocator::Name(name) => {
11239 let n = name.as_ref();
11240 self.graph
11241 .sheet_id(n)
11242 .map(SheetLocator::Id)
11243 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11244 }
11245 }
11246 };
11247
11248 match sr {
11249 SharedRef::Cell(cell) => {
11250 let owned = cell.into_owned();
11251 let sheet = resolve_loc(owned.sheet)?;
11252 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
11253 }
11254 SharedRef::Range(range) => {
11255 let owned = range.into_owned();
11256 let sheet = resolve_loc(owned.sheet)?;
11257 Ok(SharedRef::Range(SharedRangeRef {
11258 sheet,
11259 start_row: owned.start_row,
11260 start_col: owned.start_col,
11261 end_row: owned.end_row,
11262 end_col: owned.end_col,
11263 }))
11264 }
11265 }
11266 }
11267}
11268
11269impl<R> crate::traits::ReferenceResolver for Engine<R>
11272where
11273 R: EvaluationContext,
11274{
11275 fn resolve_cell_reference(
11276 &self,
11277 sheet: Option<&str>,
11278 row: u32,
11279 col: u32,
11280 ) -> Result<LiteralValue, ExcelError> {
11281 let Some(sheet_name) = sheet else {
11292 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
11293 "Unqualified cell reference resolved without sheet context".to_string(),
11294 ));
11295 };
11296 if let Some(v) = self.get_cell_value(sheet_name, row, col) {
11299 Ok(v)
11300 } else {
11301 Ok(LiteralValue::Number(0.0))
11303 }
11304 }
11305}
11306
11307impl<R> crate::traits::RangeResolver for Engine<R>
11308where
11309 R: EvaluationContext,
11310{
11311 fn resolve_range_reference(
11312 &self,
11313 sheet: Option<&str>,
11314 sr: Option<u32>,
11315 sc: Option<u32>,
11316 er: Option<u32>,
11317 ec: Option<u32>,
11318 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
11319 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
11322 }
11323}
11324
11325impl<R> crate::traits::NamedRangeResolver for Engine<R>
11326where
11327 R: EvaluationContext,
11328{
11329 fn resolve_named_range_reference(
11330 &self,
11331 name: &str,
11332 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
11333 self.resolver.resolve_named_range_reference(name)
11334 }
11335}
11336
11337impl<R> crate::traits::TableResolver for Engine<R>
11338where
11339 R: EvaluationContext,
11340{
11341 fn resolve_table_reference(
11342 &self,
11343 tref: &formualizer_parse::parser::TableReference,
11344 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
11345 self.resolver.resolve_table_reference(tref)
11346 }
11347}
11348
11349impl<R> crate::traits::SourceResolver for Engine<R>
11350where
11351 R: EvaluationContext,
11352{
11353 fn source_scalar_version(&self, name: &str) -> Option<u64> {
11354 self.resolver.source_scalar_version(name)
11355 }
11356
11357 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
11358 self.resolver.resolve_source_scalar(name)
11359 }
11360
11361 fn source_table_version(&self, name: &str) -> Option<u64> {
11362 self.resolver.source_table_version(name)
11363 }
11364
11365 fn resolve_source_table(
11366 &self,
11367 name: &str,
11368 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
11369 self.resolver.resolve_source_table(name)
11370 }
11371}
11372
11373impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
11375
11376impl<R> crate::traits::FunctionProvider for Engine<R>
11378where
11379 R: EvaluationContext,
11380{
11381 fn get_function(
11382 &self,
11383 prefix: &str,
11384 name: &str,
11385 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
11386 self.resolver.get_function(prefix, name)
11387 }
11388}
11389
11390impl<R> crate::traits::EvaluationContext for Engine<R>
11392where
11393 R: EvaluationContext,
11394{
11395 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
11396 &self.clock
11397 }
11398
11399 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
11400 self.thread_pool.as_ref()
11401 }
11402
11403 fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
11404 self.active_cancel_flag.clone()
11405 }
11406
11407 fn chunk_hint(&self) -> Option<usize> {
11408 let hint =
11410 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
11411 Some(hint.clamp(1024, 1 << 20)) }
11413
11414 fn volatile_level(&self) -> crate::traits::VolatileLevel {
11415 self.config.volatile_level
11416 }
11417
11418 fn workbook_seed(&self) -> u64 {
11419 self.config.workbook_seed
11420 }
11421
11422 fn recalc_epoch(&self) -> u64 {
11423 self.recalc_epoch
11424 }
11425
11426 fn workbook_sheet_count(&self) -> Option<usize> {
11427 Some(self.graph.sheet_reg().active_len())
11428 }
11429
11430 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
11431 self.graph.sheet_reg().active_position(sheet)
11432 }
11433
11434 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
11435 self.sheet_index_by_name(current_sheet)
11436 }
11437
11438 fn inspect_reference(
11439 &self,
11440 reference: &ReferenceType,
11441 current_sheet: &str,
11442 ) -> Result<Option<ReferenceInfo>, ExcelError> {
11443 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
11444 let sheet_id = self
11445 .graph
11446 .sheet_id(sheet_name)
11447 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11448 let sheet_index = self
11449 .graph
11450 .sheet_reg()
11451 .active_position_by_id(sheet_id)
11452 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11453 Ok((sheet_id, sheet_index))
11454 };
11455
11456 let cell_info =
11457 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
11458 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
11459 let row0 = row
11460 .checked_sub(1)
11461 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11462 let col0 = col
11463 .checked_sub(1)
11464 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11465 Ok(ReferenceInfo {
11466 first_sheet_index: Some(sheet_index),
11467 sheet_count: Some(1),
11468 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
11469 })
11470 };
11471
11472 let range_info = |sheet_name: &str,
11473 start_row: Option<u32>,
11474 start_col: Option<u32>|
11475 -> Result<ReferenceInfo, ExcelError> {
11476 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
11477 let row = start_row.unwrap_or(1);
11478 let col = start_col.unwrap_or(1);
11479 let row0 = row
11480 .checked_sub(1)
11481 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11482 let col0 = col
11483 .checked_sub(1)
11484 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11485 Ok(ReferenceInfo {
11486 first_sheet_index: Some(sheet_index),
11487 sheet_count: Some(1),
11488 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
11489 })
11490 };
11491
11492 let info = match reference {
11493 ReferenceType::Cell {
11494 sheet, row, col, ..
11495 } => {
11496 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
11497 cell_info(sheet_name, *row, *col)?
11498 }
11499 ReferenceType::Range {
11500 sheet,
11501 start_row,
11502 start_col,
11503 ..
11504 } => {
11505 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
11506 range_info(sheet_name, *start_row, *start_col)?
11507 }
11508 ReferenceType::Cell3D {
11509 sheet_first,
11510 sheet_last,
11511 row,
11512 col,
11513 ..
11514 } => {
11515 let first = cell_info(sheet_first, *row, *col)?;
11516 ReferenceInfo {
11517 first_sheet_index: first.first_sheet_index,
11518 sheet_count: self
11519 .graph
11520 .sheet_reg()
11521 .active_span_len(sheet_first, sheet_last),
11522 first_cell: first.first_cell,
11523 }
11524 }
11525 ReferenceType::Range3D {
11526 sheet_first,
11527 sheet_last,
11528 start_row,
11529 start_col,
11530 ..
11531 } => {
11532 let first = range_info(sheet_first, *start_row, *start_col)?;
11533 ReferenceInfo {
11534 first_sheet_index: first.first_sheet_index,
11535 sheet_count: self
11536 .graph
11537 .sheet_reg()
11538 .active_span_len(sheet_first, sheet_last),
11539 first_cell: first.first_cell,
11540 }
11541 }
11542 ReferenceType::NamedRange(name) => {
11543 let current_id = self
11544 .graph
11545 .sheet_id(current_sheet)
11546 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11547 let named = self
11548 .graph
11549 .resolve_name_entry(name, current_id)
11550 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11551 match &named.definition {
11552 NamedDefinition::Cell(cell) => ReferenceInfo {
11553 first_sheet_index: self
11554 .graph
11555 .sheet_reg()
11556 .active_position_by_id(cell.sheet_id),
11557 sheet_count: Some(1),
11558 first_cell: Some(*cell),
11559 },
11560 NamedDefinition::Range(range) => ReferenceInfo {
11561 first_sheet_index: self
11562 .graph
11563 .sheet_reg()
11564 .active_position_by_id(range.start.sheet_id),
11565 sheet_count: Some(1),
11566 first_cell: Some(range.start),
11567 },
11568 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
11569 ReferenceInfo {
11570 first_sheet_index: None,
11571 sheet_count: None,
11572 first_cell: None,
11573 }
11574 }
11575 }
11576 }
11577 ReferenceType::Table(tref) => {
11578 let table = self
11579 .graph
11580 .resolve_table_entry(&tref.name)
11581 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11582 ReferenceInfo {
11583 first_sheet_index: self
11584 .graph
11585 .sheet_reg()
11586 .active_position_by_id(table.range.start.sheet_id),
11587 sheet_count: Some(1),
11588 first_cell: Some(table.range.start),
11589 }
11590 }
11591 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11592 };
11593
11594 Ok(Some(info))
11595 }
11596
11597 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
11598 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11599 if sheet_name.is_empty() {
11600 return Err(ExcelError::new(ExcelErrorKind::Ref));
11601 }
11602 let row = cell.coord.row() + 1;
11603 let col = cell.coord.col() + 1;
11604
11605 if let Some(entries) = self.staged_formulas.get(sheet_name)
11606 && let Some(text) = entries.get(row, col)
11607 {
11608 return Ok(Some(if text.starts_with('=') {
11609 text.to_owned()
11610 } else {
11611 format!("={text}")
11612 }));
11613 }
11614
11615 let Some(vertex) = self.graph.get_vertex_for_cell(&cell) else {
11616 return Ok(None);
11617 };
11618 let Some(ast) = self.graph.get_formula(vertex) else {
11619 return Ok(None);
11620 };
11621 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
11622 }
11623
11624 fn used_rows_for_columns(
11625 &self,
11626 sheet: &str,
11627 start_col: u32,
11628 end_col: u32,
11629 ) -> Option<(u32, u32)> {
11630 let sheet_id = self.graph.sheet_id(sheet)?;
11632 let snap = self.data_snapshot_id();
11633 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11634 guard
11635 .as_ref()
11636 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
11637 }) {
11638 return cached;
11639 }
11640
11641 let arrow_bounds = self
11642 .sheet_store()
11643 .sheet(sheet)
11644 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
11645 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
11646 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11647 Some(bounds)
11648 } else {
11649 let sc0 = start_col.saturating_sub(1);
11650 let ec0 = end_col.saturating_sub(1);
11651 self.graph
11652 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
11653 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11654 };
11655
11656 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11657 guard
11658 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11659 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
11660 }
11661
11662 computed
11663 }
11664
11665 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
11666 let sheet_id = self.graph.sheet_id(sheet)?;
11668 let snap = self.data_snapshot_id();
11669 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11670 guard
11671 .as_ref()
11672 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
11673 }) {
11674 return cached;
11675 }
11676
11677 let arrow_bounds = self
11678 .sheet_store()
11679 .sheet(sheet)
11680 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
11681 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
11682 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11683 Some(bounds)
11684 } else {
11685 let sr0 = start_row.saturating_sub(1);
11686 let er0 = end_row.saturating_sub(1);
11687 self.graph
11688 .used_col_bounds_for_rows(sheet_id, sr0, er0)
11689 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11690 };
11691
11692 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11693 guard
11694 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11695 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
11696 }
11697
11698 computed
11699 }
11700
11701 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
11702 let _ = self.graph.sheet_id(sheet)?;
11703 Some((1_048_576, 16_384)) }
11707
11708 fn data_snapshot_id(&self) -> u64 {
11709 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
11710 }
11711
11712 fn backend_caps(&self) -> crate::traits::BackendCaps {
11713 crate::traits::BackendCaps {
11714 streaming: true,
11715 used_region: true,
11716 write: false,
11717 tables: false,
11718 async_stream: false,
11719 }
11720 }
11721
11722 fn build_lookup_index(
11723 &self,
11724 view: &RangeView<'_>,
11725 axis: LookupAxis,
11726 ) -> Option<Arc<LookupIndex>> {
11727 self.build_lookup_index_impl(view, axis)
11728 }
11729
11730 fn date_system(&self) -> crate::engine::DateSystem {
11733 self.config.date_system
11734 }
11735 fn resolve_range_view<'c>(
11737 &'c self,
11738 reference: &ReferenceType,
11739 current_sheet: &str,
11740 ) -> Result<RangeView<'c>, ExcelError> {
11741 match reference {
11742 ReferenceType::External(ext) => {
11743 let name = ext.raw.as_str();
11744 match ext.kind {
11745 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
11746 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
11747 return Err(ExcelError::new(ExcelErrorKind::Name)
11748 .with_message(format!("Undefined name: {name}")));
11749 };
11750 let version = source
11751 .version
11752 .or_else(|| self.resolver.source_scalar_version(name));
11753 let v = self.resolve_source_scalar_cached(name, version)?;
11754 Ok(RangeView::from_owned_rows(
11755 vec![vec![v]],
11756 self.config.date_system,
11757 ))
11758 }
11759 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
11760 let Some(source) = self.graph.resolve_source_table_entry(name) else {
11761 return Err(ExcelError::new(ExcelErrorKind::Name)
11762 .with_message(format!("Undefined table: {name}")));
11763 };
11764 let version = source
11765 .version
11766 .or_else(|| self.resolver.source_table_version(name));
11767 let table = self.resolve_source_table_cached(name, version)?;
11768 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
11769 self.source_table_to_range_view(table.as_ref(), &spec)
11770 }
11771 }
11772 }
11773 ReferenceType::Range { .. } => {
11774 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11775 let formualizer_common::SheetRef::Range(range) = shared else {
11776 return Err(ExcelError::new(ExcelErrorKind::Ref));
11777 };
11778 let sheet_id = match range.sheet {
11779 formualizer_common::SheetLocator::Id(id) => id,
11780 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11781 };
11782 let sheet_name = self.graph.sheet_name(sheet_id);
11783
11784 let bounded_range = if range.start_row.is_some()
11785 && range.start_col.is_some()
11786 && range.end_row.is_some()
11787 && range.end_col.is_some()
11788 {
11789 Some(RangeRef::try_from_shared(range.as_ref())?)
11790 } else {
11791 None
11792 };
11793
11794 let mut sr = bounded_range
11795 .as_ref()
11796 .map(|r| r.start.coord.row() + 1)
11797 .or_else(|| range.start_row.map(|b| b.index + 1));
11798 let mut sc = bounded_range
11799 .as_ref()
11800 .map(|r| r.start.coord.col() + 1)
11801 .or_else(|| range.start_col.map(|b| b.index + 1));
11802 let mut er = bounded_range
11803 .as_ref()
11804 .map(|r| r.end.coord.row() + 1)
11805 .or_else(|| range.end_row.map(|b| b.index + 1));
11806 let mut ec = bounded_range
11807 .as_ref()
11808 .map(|r| r.end.coord.col() + 1)
11809 .or_else(|| range.end_col.map(|b| b.index + 1));
11810
11811 if sr.is_none() && er.is_none() {
11812 let scv = sc.unwrap_or(1);
11814 let ecv = ec.unwrap_or(scv);
11815 sr = Some(1);
11816 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11817 er = Some(max_r);
11818 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11819 er = Some(self.config.max_open_ended_rows);
11820 }
11821 }
11822 if sc.is_none() && ec.is_none() {
11823 let srv = sr.unwrap_or(1);
11825 let erv = er.unwrap_or(srv);
11826 sc = Some(1);
11827 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11828 ec = Some(max_c);
11829 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11830 ec = Some(self.config.max_open_ended_cols);
11831 }
11832 }
11833 if sr.is_some() && er.is_none() {
11834 let scv = sc.unwrap_or(1);
11835 let ecv = ec.unwrap_or(scv);
11836 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11837 er = Some(max_r);
11838 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11839 er = Some(self.config.max_open_ended_rows);
11840 }
11841 }
11842 if er.is_some() && sr.is_none() {
11843 sr = Some(1);
11845 }
11846 if sc.is_some() && ec.is_none() {
11847 let srv = sr.unwrap_or(1);
11848 let erv = er.unwrap_or(srv);
11849 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11850 ec = Some(max_c);
11851 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11852 ec = Some(self.config.max_open_ended_cols);
11853 }
11854 }
11855 if ec.is_some() && sc.is_none() {
11856 sc = Some(1);
11858 }
11859
11860 let sr = sr.unwrap_or(1);
11861 let sc = sc.unwrap_or(1);
11862 let er = er.unwrap_or(sr.saturating_sub(1));
11863 let ec = ec.unwrap_or(sc.saturating_sub(1));
11864
11865 if self.force_materialize_range_views {
11866 if er < sr || ec < sc {
11867 return Ok(RangeView::from_owned_rows(
11868 Vec::new(),
11869 self.config.date_system,
11870 ));
11871 }
11872 let h = (er - sr + 1) as u64;
11873 let w = (ec - sc + 1) as u64;
11874 let cell_count = h.saturating_mul(w);
11875 if cell_count <= self.config.spill.max_spill_cells as u64 {
11876 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
11877 for r in sr..=er {
11878 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
11879 for c in sc..=ec {
11880 rowv.push(
11881 self.get_cell_value(sheet_name, r, c)
11882 .unwrap_or(LiteralValue::Empty),
11883 );
11884 }
11885 rows.push(rowv);
11886 }
11887 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
11888 }
11889 }
11890
11891 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
11892 return Ok(RangeView::from_owned_rows(
11893 Vec::new(),
11894 self.config.date_system,
11895 ));
11896 };
11897
11898 let rv = if er < sr || ec < sc {
11899 asheet.range_view(1, 1, 0, 0)
11900 } else {
11901 let sr0 = sr.saturating_sub(1) as usize;
11902 let sc0 = sc.saturating_sub(1) as usize;
11903 let er0 = er.saturating_sub(1) as usize;
11904 let ec0 = ec.saturating_sub(1) as usize;
11905 asheet.range_view(sr0, sc0, er0, ec0)
11906 };
11907
11908 Ok(rv)
11909 }
11910 ReferenceType::Cell { .. } => {
11911 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11912 let formualizer_common::SheetRef::Cell(cell) = shared else {
11913 return Err(ExcelError::new(ExcelErrorKind::Ref));
11914 };
11915 let addr = CellRef::try_from_shared(cell)?;
11916 let sheet_id = addr.sheet_id;
11917 let sheet_name = self.graph.sheet_name(sheet_id);
11918 let row = addr.coord.row() + 1;
11919 let col = addr.coord.col() + 1;
11920
11921 if self.force_materialize_range_views {
11922 let v = self
11923 .get_cell_value(sheet_name, row, col)
11924 .unwrap_or(LiteralValue::Empty);
11925 return Ok(RangeView::from_owned_rows(
11926 vec![vec![v]],
11927 self.config.date_system,
11928 ));
11929 }
11930
11931 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
11932 let r0 = row.saturating_sub(1) as usize;
11933 let c0 = col.saturating_sub(1) as usize;
11934 let rv = asheet.range_view(r0, c0, r0, c0);
11935 Ok(rv)
11936 } else {
11937 let v = self
11938 .get_cell_value(sheet_name, row, col)
11939 .unwrap_or(LiteralValue::Empty);
11940 Ok(RangeView::from_owned_rows(
11941 vec![vec![v]],
11942 self.config.date_system,
11943 ))
11944 }
11945 }
11946 ReferenceType::NamedRange(name) => {
11947 if let Some(current_id) = self.graph.sheet_id(current_sheet)
11948 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
11949 {
11950 match &named.definition {
11951 NamedDefinition::Cell(cell_ref) => {
11952 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
11953 if self.force_materialize_range_views {
11954 let v = self
11955 .get_cell_value(
11956 sheet_name,
11957 cell_ref.coord.row() + 1,
11958 cell_ref.coord.col() + 1,
11959 )
11960 .unwrap_or(LiteralValue::Empty);
11961 return Ok(RangeView::from_owned_rows(
11962 vec![vec![v]],
11963 self.config.date_system,
11964 ));
11965 } else {
11966 let asheet = self
11967 .sheet_store()
11968 .sheet(sheet_name)
11969 .expect("Arrow sheet missing for named cell");
11970 let r0 = cell_ref.coord.row() as usize;
11971 let c0 = cell_ref.coord.col() as usize;
11972 let rv = asheet.range_view(r0, c0, r0, c0);
11973 return Ok(rv);
11974 }
11975 }
11976 NamedDefinition::Range(range_ref) => {
11977 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
11978 let sr = range_ref.start.coord.row() + 1;
11979 let sc = range_ref.start.coord.col() + 1;
11980 let er = range_ref.end.coord.row() + 1;
11981 let ec = range_ref.end.coord.col() + 1;
11982 if self.force_materialize_range_views {
11983 let h = (er.saturating_sub(sr) + 1) as u64;
11984 let w = (ec.saturating_sub(sc) + 1) as u64;
11985 let cell_count = h.saturating_mul(w);
11986 if cell_count <= self.config.spill.max_spill_cells as u64 {
11987 let mut rows: Vec<Vec<LiteralValue>> =
11988 Vec::with_capacity(h as usize);
11989 for r in sr..=er {
11990 let mut rowv: Vec<LiteralValue> =
11991 Vec::with_capacity(w as usize);
11992 for c in sc..=ec {
11993 rowv.push(
11994 self.get_cell_value(sheet_name, r, c)
11995 .unwrap_or(LiteralValue::Empty),
11996 );
11997 }
11998 rows.push(rowv);
11999 }
12000 return Ok(RangeView::from_owned_rows(
12001 rows,
12002 self.config.date_system,
12003 ));
12004 }
12005 }
12006 let asheet = self
12007 .sheet_store()
12008 .sheet(sheet_name)
12009 .expect("Arrow sheet missing for named range");
12010 let sr0 = range_ref.start.coord.row() as usize;
12011 let sc0 = range_ref.start.coord.col() as usize;
12012 let er0 = range_ref.end.coord.row() as usize;
12013 let ec0 = range_ref.end.coord.col() as usize;
12014 let rv = asheet.range_view(sr0, sc0, er0, ec0);
12015 return Ok(rv);
12016 }
12017 NamedDefinition::Literal(v) => {
12018 return Ok(RangeView::from_owned_rows(
12019 vec![vec![v.clone()]],
12020 self.config.date_system,
12021 ));
12022 }
12023 NamedDefinition::Formula { .. } => {
12024 if let Some(value) = self.graph.get_value(named.vertex) {
12025 return Ok(RangeView::from_owned_rows(
12026 vec![vec![value]],
12027 self.config.date_system,
12028 ));
12029 }
12030 }
12031 }
12032 }
12033
12034 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
12035 let version = source
12036 .version
12037 .or_else(|| self.resolver.source_scalar_version(name));
12038 let v = self.resolve_source_scalar_cached(name, version)?;
12039 return Ok(RangeView::from_owned_rows(
12040 vec![vec![v]],
12041 self.config.date_system,
12042 ));
12043 }
12044
12045 let data = self.resolver.resolve_named_range_reference(name)?;
12046 Ok(RangeView::from_owned_rows(data, self.config.date_system))
12047 }
12048 ReferenceType::Table(tref) => {
12049 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
12050 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
12051 let asheet = self
12052 .sheet_store()
12053 .sheet(sheet_name)
12054 .expect("Arrow sheet missing for table reference");
12055
12056 let sr0 = table.range.start.coord.row() as usize;
12057 let sc0 = table.range.start.coord.col() as usize;
12058 let er0 = table.range.end.coord.row() as usize;
12059 let ec0 = table.range.end.coord.col() as usize;
12060
12061 let has_totals = table.totals_row;
12062 let has_headers = table.header_row;
12063 let data_sr = if has_headers {
12064 sr0.saturating_add(1)
12065 } else {
12066 sr0
12067 };
12068 let data_er = if has_totals {
12069 er0.saturating_sub(1)
12070 } else {
12071 er0
12072 };
12073
12074 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
12075 if sr > er || sc > ec {
12076 asheet.range_view(1, 1, 0, 0)
12077 } else {
12078 asheet.range_view(sr, sc, er, ec)
12079 }
12080 };
12081
12082 let av = match &tref.specifier {
12083 None => {
12084 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12085 "Table reference without specifier is unsupported".to_string(),
12086 ));
12087 }
12088 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
12089 let Some(idx) = table.col_index(col) else {
12090 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12091 "Column refers to unknown table column".to_string(),
12092 ));
12093 };
12094 let c0 = sc0 + idx;
12095 select(data_sr, c0, data_er, c0)
12096 }
12097 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
12098 start,
12099 end,
12100 )) => {
12101 let Some(si) = table.col_index(start) else {
12102 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12103 "Column range refers to unknown column(s)".to_string(),
12104 ));
12105 };
12106 let Some(ei) = table.col_index(end) else {
12107 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12108 "Column range refers to unknown column(s)".to_string(),
12109 ));
12110 };
12111 let (mut a, mut b) = (si, ei);
12112 if a > b {
12113 std::mem::swap(&mut a, &mut b);
12114 }
12115 let c_start = sc0 + a;
12116 let c_end = sc0 + b;
12117 select(data_sr, c_start, data_er, c_end)
12118 }
12119 Some(formualizer_parse::parser::TableSpecifier::All)
12120 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12121 formualizer_parse::parser::SpecialItem::All,
12122 )) => select(sr0, sc0, er0, ec0),
12123 Some(formualizer_parse::parser::TableSpecifier::Data)
12124 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12125 formualizer_parse::parser::SpecialItem::Data,
12126 )) => select(data_sr, sc0, data_er, ec0),
12127 Some(formualizer_parse::parser::TableSpecifier::Headers)
12128 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12129 formualizer_parse::parser::SpecialItem::Headers,
12130 )) => {
12131 if !has_headers {
12132 asheet.range_view(1, 1, 0, 0)
12133 } else {
12134 select(sr0, sc0, sr0, ec0)
12135 }
12136 }
12137 Some(formualizer_parse::parser::TableSpecifier::Totals)
12138 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12139 formualizer_parse::parser::SpecialItem::Totals,
12140 )) => {
12141 if !has_totals {
12142 asheet.range_view(1, 1, 0, 0)
12143 } else {
12144 select(er0, sc0, er0, ec0)
12145 }
12146 }
12147 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12148 formualizer_parse::parser::SpecialItem::ThisRow,
12149 )) => {
12150 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12151 "@ (This Row) requires table-aware context; not yet supported"
12152 .to_string(),
12153 ));
12154 }
12155 Some(formualizer_parse::parser::TableSpecifier::Row(_))
12156 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
12157 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12158 "Complex structured references not yet supported".to_string(),
12159 ));
12160 }
12161 };
12162
12163 return Ok(av);
12164 }
12165
12166 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
12167 let version = source
12168 .version
12169 .or_else(|| self.resolver.source_table_version(&tref.name));
12170 let table = self.resolve_source_table_cached(&tref.name, version)?;
12171 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
12172 }
12173
12174 let boxed = self.resolve_range_like(&ReferenceType::Table(tref.clone()))?;
12176 let owned = boxed.materialise().into_owned();
12177 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
12178 }
12179 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
12180 Err(ExcelError::new(ExcelErrorKind::NImpl)
12181 .with_message("3D references are not yet supported".to_string()))
12182 }
12183 }
12184 }
12185
12186 fn resolve_cell_reference_value(
12187 &self,
12188 sheet: Option<&str>,
12189 row: u32,
12190 col: u32,
12191 current_sheet: &str,
12192 ) -> Result<LiteralValue, ExcelError> {
12193 let sheet_name = sheet.unwrap_or(current_sheet);
12194 if self.graph.sheet_id(sheet_name).is_none() {
12195 return Err(ExcelError::new(ExcelErrorKind::Ref));
12196 }
12197 Ok(self
12198 .get_cell_value(sheet_name, row, col)
12199 .unwrap_or(LiteralValue::Empty))
12200 }
12201
12202 fn build_criteria_mask(
12203 &self,
12204 view: &RangeView<'_>,
12205 col_in_view: usize,
12206 pred: &crate::args::CriteriaPredicate,
12207 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
12208 if view.dims().1 == 0 {
12209 return None;
12210 }
12211 let sheet_rows = view.sheet().nrows as usize;
12214 if sheet_rows == 0 || view.start_row() >= sheet_rows {
12215 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
12216 }
12217 compute_criteria_mask(view, col_in_view, pred)
12218 }
12219
12220 fn build_row_visibility_mask(
12221 &self,
12222 view: &RangeView<'_>,
12223 mode: VisibilityMaskMode,
12224 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
12225 self.build_row_visibility_mask_for_view(view, mode)
12226 }
12227}
12228
12229impl<R> Engine<R>
12230where
12231 R: EvaluationContext,
12232{
12233 fn clear_spill_projection_and_mirror(
12234 &mut self,
12235 anchor_vertex: VertexId,
12236 delta: Option<&mut DeltaCollector>,
12237 ) {
12238 let spill_cells = self
12239 .graph
12240 .spill_cells_for_anchor(anchor_vertex)
12241 .map(|cells| cells.to_vec())
12242 .unwrap_or_default();
12243 if spill_cells.is_empty() {
12244 return;
12245 }
12246
12247 if let Some(delta) = delta
12248 && delta.mode != DeltaMode::Off
12249 {
12250 let empty = LiteralValue::Empty;
12251 for cell in spill_cells.iter() {
12252 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12253 let old = self
12254 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12255 .unwrap_or(LiteralValue::Empty);
12256 if old != empty {
12257 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12258 }
12259 }
12260 }
12261
12262 self.graph.clear_spill_region(anchor_vertex);
12263 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
12264 self.record_formula_plane_structural_change(scope);
12265 }
12266
12267 if self.config.arrow_storage_enabled
12268 && self.config.delta_overlay_enabled
12269 && self.config.write_formula_overlay_enabled
12270 {
12271 let empty = LiteralValue::Empty;
12272 for cell in spill_cells.iter() {
12273 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12274 self.mirror_value_to_computed_overlay(
12275 &sheet_name,
12276 cell.coord.row() + 1,
12277 cell.coord.col() + 1,
12278 &empty,
12279 );
12280 }
12281 }
12282 }
12283
12284 fn apply_cycle_outcome(
12298 &mut self,
12299 cycle: &[VertexId],
12300 mut delta: Option<&mut DeltaCollector>,
12301 dirty_filter: Option<&FxHashSet<VertexId>>,
12302 ) -> usize {
12303 let circ_error = LiteralValue::Error(
12304 ExcelError::new(ExcelErrorKind::Circ)
12305 .with_message("Circular dependency detected".to_string()),
12306 );
12307 let mut stamped = 0usize;
12308 for &vertex_id in cycle {
12309 if let Some(filter) = dirty_filter
12310 && !filter.contains(&vertex_id)
12311 {
12312 continue;
12313 }
12314 self.stamp_cycle_error(vertex_id, &circ_error, delta.as_deref_mut());
12315 stamped += 1;
12316 }
12317 stamped
12318 }
12319
12320 fn stamp_cycle_error(
12335 &mut self,
12336 vertex_id: VertexId,
12337 circ_error: &LiteralValue,
12338 mut delta: Option<&mut DeltaCollector>,
12339 ) {
12340 if self.graph.spill_registry_has_anchor(vertex_id) {
12342 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
12343 }
12344 self.spill_mgr.release_owner(vertex_id);
12347
12348 if let Some(d) = delta
12350 && d.mode != DeltaMode::Off
12351 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
12352 {
12353 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12354 let old = self
12355 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12356 .unwrap_or(LiteralValue::Empty);
12357 if old != *circ_error {
12358 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12359 }
12360 }
12361
12362 self.graph
12363 .update_vertex_value(vertex_id, circ_error.clone());
12364 self.mirror_vertex_value_to_overlay(vertex_id, circ_error);
12365 }
12366
12367 fn handle_cycle_unit(
12380 &mut self,
12381 cycle: &[VertexId],
12382 mut delta: Option<&mut DeltaCollector>,
12383 dirty_filter: Option<&FxHashSet<VertexId>>,
12384 cancel_flag: Option<&AtomicBool>,
12385 ) -> Result<usize, ExcelError> {
12386 match self.config.cycle.detection {
12387 CycleDetection::Static => {
12388 Ok(self.apply_cycle_outcome(cycle, delta.as_deref_mut(), dirty_filter))
12389 }
12390 CycleDetection::Runtime => {
12391 if let Some(filter) = dirty_filter
12392 && !cycle.iter().any(|v| filter.contains(v))
12393 {
12394 return Ok(0);
12395 }
12396 self.evaluate_scc_unit(cycle, delta, cancel_flag)
12400 }
12401 }
12402 }
12403
12404 pub(crate) fn evaluate_scc_unit(
12424 &mut self,
12425 cycle: &[VertexId],
12426 mut delta: Option<&mut DeltaCollector>,
12427 cancel_flag: Option<&AtomicBool>,
12428 ) -> Result<usize, ExcelError> {
12429 struct SccMember {
12430 vertex: VertexId,
12431 cell: Option<CellRef>,
12432 }
12433
12434 let task_start = crate::instant::FzInstant::now();
12435
12436 let mut cell_members: Vec<(VertexId, CellRef)> = Vec::new();
12441 let mut name_members: Vec<(VertexId, String)> = Vec::new();
12442 let mut other_members: Vec<VertexId> = Vec::new();
12443 for &v in cycle {
12444 match self.graph.get_vertex_kind(v) {
12445 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
12446 match self.graph.get_cell_ref(v) {
12447 Some(cell) => cell_members.push((v, cell)),
12448 None => other_members.push(v),
12449 }
12450 }
12451 VertexKind::NamedScalar | VertexKind::NamedArray => {
12452 match self.graph.name_key_for_vertex(v) {
12453 Some(key) => name_members.push((v, key)),
12454 None => other_members.push(v),
12455 }
12456 }
12457 _ => other_members.push(v),
12458 }
12459 }
12460 cell_members.sort_unstable_by_key(|(_, c)| (c.sheet_id, c.coord.row(), c.coord.col()));
12461 name_members.sort_unstable_by(|(av, ak), (bv, bk)| ak.cmp(bk).then(av.cmp(bv)));
12462 other_members.sort_unstable();
12463
12464 let cell_refs: Vec<CellRef> = cell_members.iter().map(|(_, c)| *c).collect();
12465 let name_keys: Vec<String> = name_members.iter().map(|(_, k)| k.clone()).collect();
12466 let mut members: Vec<SccMember> = Vec::with_capacity(cycle.len());
12467 for (v, c) in &cell_members {
12468 members.push(SccMember {
12469 vertex: *v,
12470 cell: Some(*c),
12471 });
12472 }
12473 for (v, _) in &name_members {
12474 members.push(SccMember {
12475 vertex: *v,
12476 cell: None,
12477 });
12478 }
12479 for v in &other_members {
12480 members.push(SccMember {
12481 vertex: *v,
12482 cell: None,
12483 });
12484 }
12485 let n = members.len();
12486 let recordable = cell_refs.len() + name_keys.len();
12489
12490 let circ_error = LiteralValue::Error(
12491 ExcelError::new(ExcelErrorKind::Circ)
12492 .with_message("Circular dependency detected".to_string()),
12493 );
12494
12495 if !self.iterative_state_values.is_empty() {
12506 let restore: Vec<(VertexId, LiteralValue)> = members
12507 .iter()
12508 .filter_map(|m| {
12509 let cell = m.cell?;
12510 let persisted = self.iterative_state_values.get(&m.vertex)?;
12511 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12512 let overlay = self
12513 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12514 .unwrap_or(LiteralValue::Empty);
12515 if matches!(overlay, LiteralValue::Empty) {
12516 Some((m.vertex, persisted.clone()))
12517 } else {
12518 None
12519 }
12520 })
12521 .collect();
12522 for (vertex, value) in restore {
12523 self.mirror_vertex_value_to_overlay(vertex, &value);
12524 }
12525 }
12526
12527 let snapshot: Vec<LiteralValue> = members
12530 .iter()
12531 .map(|m| match m.cell {
12532 Some(cell) => {
12533 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12534 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12535 .unwrap_or(LiteralValue::Empty)
12536 }
12537 None => self
12538 .graph
12539 .get_value(m.vertex)
12540 .unwrap_or(LiteralValue::Empty),
12541 })
12542 .collect();
12543
12544 let mut excluded = vec![false; n];
12549 let mut last_value = snapshot.clone();
12550 let mut stamped = 0usize;
12551 for (i, m) in members.iter().enumerate() {
12552 match self.graph.get_vertex_kind(m.vertex) {
12553 VertexKind::FormulaArray => {
12554 self.stamp_cycle_error(m.vertex, &circ_error, delta.as_deref_mut());
12558 excluded[i] = true;
12559 last_value[i] = circ_error.clone();
12560 stamped += 1;
12561 }
12562 VertexKind::FormulaScalar | VertexKind::NamedScalar | VertexKind::NamedArray => {}
12563 _ => excluded[i] = true,
12564 }
12565 }
12566
12567 let collector = LiveEdgeCollector::new_with_names(&cell_refs, &name_keys);
12568
12569 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
12571 let mut pos: Vec<i64> = vec![-1; n];
12573 let mut changed = vec![false; n];
12575
12576 macro_rules! run_member {
12579 ($i:expr) => {{
12580 let i: usize = $i;
12581 let m = &members[i];
12582 if i < recordable {
12583 collector.set_current(i as u32);
12584 }
12585 let value = {
12586 let ctx = RecordingContext::new(&*self, &collector);
12587 match self.evaluate_vertex_recorded(m.vertex, &ctx, &collector) {
12588 Ok(v) => v,
12589 Err(e) => LiteralValue::Error(e),
12590 }
12591 };
12592 let is_cell_formula = m.cell.is_some();
12593 if is_cell_formula && matches!(value, LiteralValue::Array(_)) {
12594 self.stamp_cycle_error(m.vertex, &circ_error, None);
12599 excluded[i] = true;
12600 stamped += 1;
12601 changed[i] = last_value[i] != circ_error;
12602 last_value[i] = circ_error.clone();
12603 } else {
12604 self.graph.update_vertex_value(m.vertex, value.clone());
12605 self.mirror_vertex_value_to_overlay(m.vertex, &value);
12606 #[cfg(debug_assertions)]
12610 if let Some(cell) = m.cell {
12611 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12612 debug_assert!(
12613 self.read_delta_overlay_cell(
12614 &sheet_name,
12615 cell.coord.row() + 1,
12616 cell.coord.col() + 1
12617 )
12618 .is_none(),
12619 "user overlay must never shadow a formula SCC member ({sheet_name}!r{}c{})",
12620 cell.coord.row() + 1,
12621 cell.coord.col() + 1
12622 );
12623 }
12624 changed[i] = last_value[i] != value;
12625 last_value[i] = value;
12626 }
12627 }};
12628 }
12629
12630 let check_cancel = |flag: Option<&AtomicBool>| -> Result<(), ExcelError> {
12631 if let Some(flag) = flag
12632 && flag.load(Ordering::Relaxed)
12633 {
12634 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12635 .with_message("Evaluation cancelled during SCC evaluation".to_string()));
12636 }
12637 Ok(())
12638 };
12639
12640 check_cancel(cancel_flag)?;
12642 let mut passes = 1usize;
12643 {
12644 let mut p = 0i64;
12645 for i in 0..n {
12646 if excluded[i] {
12647 continue;
12648 }
12649 run_member!(i);
12650 pos[i] = p;
12651 p += 1;
12652 }
12653 }
12654
12655 let policy = self.config.cycle.policy;
12671 let cap = n + 2;
12672 let mut witnessed_cycles = 0usize;
12673 let mut capped = false;
12674 let mut iterating = false;
12676 let mut converged = false;
12677 let mut prev_pass: Option<Vec<LiteralValue>> = None;
12681 let mut iter_max_delta = 0f64;
12684 let mut iter_nan_converged = 0usize;
12685 let mut settle_passes = 0usize;
12689 loop {
12690 let drained = collector.take_edges();
12693 for i in 0..n {
12694 if pos[i] >= 0 {
12695 out_edges[i].clear();
12696 }
12697 }
12698 for (from, to) in drained {
12699 debug_assert!(
12700 pos[from as usize] >= 0,
12701 "edge from a member that did not run"
12702 );
12703 out_edges[from as usize].push(to);
12704 }
12705 let mut edges: Vec<(u32, u32)> = Vec::new();
12706 for (i, outs) in out_edges.iter().enumerate() {
12707 if excluded[i] {
12708 continue;
12709 }
12710 for &t in outs {
12711 edges.push((i as u32, t));
12712 }
12713 }
12714 edges.sort_unstable();
12715 edges.dedup();
12716
12717 let analysis = analyze_live_graph(n, &edges);
12718
12719 if analysis.cycle_count > 0 {
12720 witnessed_cycles = witnessed_cycles.max(analysis.cycle_count);
12724 match policy {
12725 CyclePolicy::Error => {
12726 for i in 0..n {
12732 if analysis.in_cycle[i] && !excluded[i] {
12733 self.stamp_cycle_error(members[i].vertex, &circ_error, None);
12734 excluded[i] = true;
12735 last_value[i] = circ_error.clone();
12736 stamped += 1;
12737 }
12738 }
12739 check_cancel(cancel_flag)?;
12740 let order: Vec<usize> = analysis
12741 .topo
12742 .iter()
12743 .map(|&i| i as usize)
12744 .filter(|&i| !excluded[i])
12745 .collect();
12746 if !order.is_empty() {
12747 passes += 1;
12748 for i in order {
12749 run_member!(i);
12750 }
12751 }
12752 break;
12753 }
12754 CyclePolicy::Iterate {
12755 max_iterations,
12756 max_change,
12757 } => {
12758 iterating = true;
12760
12761 if let Some(prev) = &prev_pass {
12767 let mut round_max_delta = 0f64;
12768 let mut round_nan = 0usize;
12769 let mut all_converged = true;
12770 for i in 0..n {
12771 if excluded[i] {
12772 continue;
12776 }
12777 let out = crate::engine::convergence::values_converged(
12778 &prev[i],
12779 &last_value[i],
12780 max_change,
12781 self.config.date_system,
12782 );
12783 if out.nan_converged {
12784 round_nan += 1;
12785 }
12786 if let Some(d) = out.abs_delta {
12787 round_max_delta = round_max_delta.max(d);
12788 }
12789 if !out.converged {
12790 all_converged = false;
12791 }
12792 }
12793 iter_max_delta = round_max_delta;
12796 iter_nan_converged = round_nan;
12797 if all_converged {
12798 converged = true;
12799 break;
12800 }
12801 }
12802
12803 if passes >= max_iterations as usize {
12815 capped = true;
12816 break;
12817 }
12818
12819 check_cancel(cancel_flag)?;
12820 prev_pass = Some(last_value.clone());
12828 for x in pos.iter_mut() {
12829 *x = -1;
12830 }
12831 changed.fill(false);
12832 passes += 1;
12833 let mut p = 0i64;
12834 for i in 0..n {
12835 if excluded[i] {
12836 continue;
12837 }
12838 run_member!(i);
12839 pos[i] = p;
12840 p += 1;
12841 }
12842 continue;
12843 }
12844 }
12845 }
12846
12847 let mut stale: Vec<usize> = Vec::new();
12850 for i in 0..n {
12851 if excluded[i] {
12852 continue;
12853 }
12854 let is_stale = out_edges[i].iter().any(|&t| {
12855 let t = t as usize;
12856 changed[t] && (pos[i] < 0 || (pos[t] >= 0 && pos[i] < pos[t]))
12857 });
12858 if is_stale {
12859 stale.push(i);
12860 }
12861 }
12862 if stale.is_empty() {
12863 break; }
12865 if 1 + settle_passes >= cap {
12866 capped = true;
12868 for (i, m) in members.iter().enumerate() {
12869 if !excluded[i] {
12870 self.stamp_cycle_error(m.vertex, &circ_error, None);
12871 excluded[i] = true;
12872 last_value[i] = circ_error.clone();
12873 stamped += 1;
12874 }
12875 }
12876 break;
12877 }
12878
12879 check_cancel(cancel_flag)?;
12880 prev_pass = None;
12887 let topo_pos = analysis.topo_positions();
12888 stale.sort_unstable_by_key(|&i| topo_pos[i]);
12889 for x in pos.iter_mut() {
12890 *x = -1;
12891 }
12892 changed.fill(false);
12893 passes += 1;
12894 settle_passes += 1;
12895 for (p, i) in stale.into_iter().enumerate() {
12896 run_member!(i);
12897 pos[i] = p as i64;
12898 }
12899 }
12900
12901 if iterating && !converged && !capped {
12906 converged = true;
12907 }
12908
12909 collector.clear_current();
12912 if let Some(d) = delta
12913 && d.mode != DeltaMode::Off
12914 {
12915 for (i, m) in members.iter().enumerate() {
12916 if let Some(cell) = m.cell
12917 && last_value[i] != snapshot[i]
12918 {
12919 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12920 }
12921 }
12922 }
12923
12924 if iterating {
12931 self.pending_iterative_redirty
12932 .extend(members.iter().map(|m| m.vertex));
12933 }
12934
12935 {
12936 let t = &mut self.last_cycle_telemetry;
12937 t.static_sccs += 1;
12938 if witnessed_cycles == 0 && stamped == 0 && !capped {
12939 t.phantom_sccs += 1;
12940 }
12941 t.live_cycles_witnessed += witnessed_cycles;
12942 t.circ_cells_stamped += stamped;
12943 t.settle_passes_total += passes;
12944 t.max_passes_single_scc = t.max_passes_single_scc.max(passes);
12945 if iterating {
12946 t.iterated_sccs += 1;
12947 if converged {
12948 t.converged_sccs += 1;
12949 }
12950 t.max_abs_delta_at_stop = t.max_abs_delta_at_stop.max(iter_max_delta);
12951 t.nan_converged += iter_nan_converged;
12952 }
12953 if capped {
12954 t.capped_sccs += 1;
12955 }
12956 t.elapsed_ms += task_start.elapsed().as_millis();
12957 }
12958
12959 Ok(stamped)
12960 }
12961
12962 fn evaluate_vertex_recorded(
12970 &self,
12971 vertex_id: VertexId,
12972 ctx: &RecordingContext<'_, R>,
12973 collector: &LiveEdgeCollector,
12974 ) -> Result<LiteralValue, ExcelError> {
12975 if !self.graph.vertex_exists(vertex_id) {
12976 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
12977 .with_message(format!("Vertex not found: {vertex_id:?}")));
12978 }
12979
12980 let kind = self.graph.get_vertex_kind(vertex_id);
12981 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
12982
12983 match kind {
12984 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
12985 let Some(ast_id) = self.graph.get_formula_id(vertex_id) else {
12986 return Ok(LiteralValue::Number(0.0)); };
12988 let sheet_name = self.graph.sheet_name(sheet_id);
12989 let cell_ref = self
12990 .graph
12991 .get_cell_ref(vertex_id)
12992 .expect("cell ref for vertex");
12993 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
12994 interpreter
12995 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
12996 .map(|cv| cv.into_literal())
12997 }
12998 VertexKind::NamedScalar | VertexKind::NamedArray => {
12999 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
13000 ExcelError::new(ExcelErrorKind::Name)
13001 .with_message("Named range metadata missing".to_string())
13002 })?;
13003
13004 match &named_range.definition {
13005 NamedDefinition::Formula { ast, .. } => {
13006 let context_sheet = match named_range.scope {
13007 NameScope::Sheet(id) => id,
13008 NameScope::Workbook => sheet_id,
13009 };
13010 let sheet_name = self.graph.sheet_name(context_sheet);
13011 let cell_ref = self
13012 .graph
13013 .get_cell_ref(vertex_id)
13014 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
13015 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
13016 if kind == VertexKind::NamedScalar {
13017 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
13018 } else {
13019 match interpreter.evaluate_ast(ast) {
13020 Ok(cv) => match cv.into_literal() {
13021 v @ LiteralValue::Array(_) => Ok(v),
13022 other => Ok(LiteralValue::Array(vec![vec![other]])),
13023 },
13024 Err(err) => Ok(LiteralValue::Error(err)),
13025 }
13026 }
13027 }
13028 NamedDefinition::Cell(cell_ref) => {
13029 collector.record_scalar(
13033 cell_ref.sheet_id,
13034 cell_ref.coord.row(),
13035 cell_ref.coord.col(),
13036 );
13037 self.evaluate_vertex_immutable(vertex_id)
13038 }
13039 NamedDefinition::Range(range_ref) => {
13040 if range_ref.start.sheet_id == range_ref.end.sheet_id {
13041 collector.record_rect(
13042 range_ref.start.sheet_id,
13043 range_ref.start.coord.row(),
13044 range_ref.start.coord.col(),
13045 range_ref.end.coord.row(),
13046 range_ref.end.coord.col(),
13047 );
13048 }
13049 self.evaluate_vertex_immutable(vertex_id)
13050 }
13051 NamedDefinition::Literal(_) => self.evaluate_vertex_immutable(vertex_id),
13052 }
13053 }
13054 _ => self.evaluate_vertex_immutable(vertex_id),
13055 }
13056 }
13057
13058 fn commit_spill_and_mirror(
13060 &mut self,
13061 anchor_vertex: VertexId,
13062 targets: &[CellRef],
13063 rows: Vec<Vec<LiteralValue>>,
13064 delta: Option<&mut DeltaCollector>,
13065 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13066 ) -> Result<(), ExcelError> {
13067 let prev_spill_cells = self
13068 .graph
13069 .spill_cells_for_anchor(anchor_vertex)
13070 .map(|cells| cells.to_vec())
13071 .unwrap_or_default();
13072
13073 if let Some(delta) = delta
13074 && delta.mode != DeltaMode::Off
13075 {
13076 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
13077 targets.iter().copied().collect();
13078 let empty = LiteralValue::Empty;
13079
13080 for cell in prev_spill_cells.iter() {
13082 if target_set.contains(cell) {
13083 continue;
13084 }
13085 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13086 let old = self
13087 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13088 .unwrap_or(LiteralValue::Empty);
13089 if old != empty {
13090 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13091 }
13092 }
13093
13094 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
13096 let width = rows[0].len();
13097 for (idx, cell) in targets.iter().enumerate() {
13098 let r_off = idx / width;
13099 let c_off = idx % width;
13100 let new = rows
13101 .get(r_off)
13102 .and_then(|r| r.get(c_off))
13103 .cloned()
13104 .unwrap_or(LiteralValue::Empty);
13105 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13106 let old = self
13107 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13108 .unwrap_or(LiteralValue::Empty);
13109 if old != new {
13110 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13111 }
13112 }
13113 } else {
13114 for cell in targets.iter() {
13116 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13117 let old = self
13118 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13119 .unwrap_or(LiteralValue::Empty);
13120 if !matches!(old, LiteralValue::Empty) {
13121 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13122 }
13123 }
13124 }
13125 }
13126
13127 let arrow_sheets = &self.arrow_sheets;
13130 self.spill_mgr.commit_array_with_value_probe(
13131 &mut self.graph,
13132 anchor_vertex,
13133 targets,
13134 rows.clone(),
13135 overwritable_formulas,
13136 |g, cell| {
13137 let sheet_name = g.sheet_name(cell.sheet_id);
13138 let asheet = arrow_sheets.sheet(sheet_name)?;
13139 let r0 = cell.coord.row() as usize;
13140 let c0 = cell.coord.col() as usize;
13141 let v = asheet.get_cell_value(r0, c0);
13142 if matches!(v, LiteralValue::Empty) {
13143 None
13144 } else {
13145 Some(v)
13146 }
13147 },
13148 )?;
13149
13150 if let Some(scope) = Self::formula_plane_region_from_cells(&prev_spill_cells) {
13151 self.record_formula_plane_structural_change(scope);
13152 }
13153 if let Some(scope) = Self::formula_plane_region_from_cells(targets) {
13154 self.record_formula_plane_structural_change(scope);
13155 }
13156
13157 if self.config.arrow_storage_enabled
13158 && self.config.delta_overlay_enabled
13159 && self.config.write_formula_overlay_enabled
13160 {
13161 if !prev_spill_cells.is_empty() {
13162 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
13163 targets.iter().copied().collect();
13164 let empty = LiteralValue::Empty;
13165 for cell in prev_spill_cells.iter() {
13166 if !target_set.contains(cell) {
13167 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13168 self.mirror_value_to_computed_overlay(
13169 &sheet_name,
13170 cell.coord.row() + 1,
13171 cell.coord.col() + 1,
13172 &empty,
13173 );
13174 }
13175 }
13176 }
13177
13178 for (idx, cell) in targets.iter().enumerate() {
13179 if rows.is_empty() || rows[0].is_empty() {
13180 break;
13181 }
13182 let width = rows[0].len();
13183 let r_off = idx / width;
13184 let c_off = idx % width;
13185 let v = rows[r_off][c_off].clone();
13186 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13187 self.mirror_value_to_computed_overlay(
13188 &sheet_name,
13189 cell.coord.row() + 1,
13190 cell.coord.col() + 1,
13191 &v,
13192 );
13193 }
13194 }
13195 Ok(())
13196 }
13197}
13198
13199use crate::engine::effects::Effect;
13204use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
13205
13206impl<R> Engine<R>
13207where
13208 R: EvaluationContext,
13209{
13210 pub(crate) fn plan_vertex_effects(
13217 &mut self,
13218 vertex_id: VertexId,
13219 computed_value: LiteralValue,
13220 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13221 ) -> Result<Vec<Effect>, ExcelError> {
13222 let kind = self.graph.get_vertex_kind(vertex_id);
13223 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
13224
13225 if !is_formula {
13229 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
13230 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
13231 && owner != vertex_id
13232 {
13233 return Ok(Vec::new());
13234 }
13235 return Ok(vec![Effect::WriteCell {
13237 vertex_id,
13238 value: computed_value,
13239 }]);
13240 }
13241
13242 match computed_value {
13243 LiteralValue::Array(rows) => {
13244 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
13245 }
13246 other => self.plan_scalar_effects(vertex_id, other),
13247 }
13248 }
13249
13250 fn plan_scalar_effects(
13252 &self,
13253 vertex_id: VertexId,
13254 value: LiteralValue,
13255 ) -> Result<Vec<Effect>, ExcelError> {
13256 let has_spill = self
13257 .graph
13258 .spill_cells_for_anchor(vertex_id)
13259 .is_some_and(|c| !c.is_empty());
13260
13261 let mut effects = Vec::new();
13262 if has_spill {
13263 effects.push(Effect::SpillClear {
13264 anchor_vertex: vertex_id,
13265 });
13266 }
13267 effects.push(Effect::WriteCell { vertex_id, value });
13268 Ok(effects)
13269 }
13270
13271 fn plan_array_effects(
13273 &mut self,
13274 vertex_id: VertexId,
13275 rows: Vec<Vec<LiteralValue>>,
13276 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13277 ) -> Result<Vec<Effect>, ExcelError> {
13278 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
13280
13281 let anchor = self
13282 .graph
13283 .get_cell_ref(vertex_id)
13284 .expect("cell ref for vertex");
13285 let sheet_id = anchor.sheet_id;
13286 let h = rows.len() as u32;
13287 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
13288
13289 let spill_cells = (h as u64).saturating_mul(w as u64);
13291 if spill_cells > self.config.spill.max_spill_cells as u64 {
13292 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
13293 }
13294
13295 const PACKED_MAX_ROW: u32 = 1_048_575;
13297 const PACKED_MAX_COL: u32 = 16_383;
13298 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
13299 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
13300 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
13301 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
13302 }
13303
13304 let mut targets = Vec::new();
13305 for r in 0..h {
13306 for c in 0..w {
13307 targets.push(self.graph.make_cell_ref_internal(
13308 sheet_id,
13309 anchor.coord.row() + r,
13310 anchor.coord.col() + c,
13311 ));
13312 }
13313 }
13314
13315 match self.spill_mgr.reserve(
13317 vertex_id,
13318 anchor,
13319 SpillShape { rows: h, cols: w },
13320 SpillMeta {
13321 epoch: self.recalc_epoch,
13322 config: self.config.spill,
13323 },
13324 ) {
13325 Ok(()) => {
13326 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
13328 vertex_id,
13329 &targets,
13330 overwritable_formulas,
13331 ) {
13332 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
13333 }
13334
13335 if !self.graph.value_cache_enabled() {
13339 let sheet_name = self.graph.sheet_name(sheet_id);
13340 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
13341 for cell in targets.iter() {
13342 if *cell == anchor {
13344 continue;
13345 }
13346 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
13348 continue;
13349 }
13350 if let Some(&vid) = self.graph.get_vertex_id_for_address(cell)
13352 && vid != vertex_id
13353 {
13354 match self.graph.get_vertex_kind(vid) {
13355 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
13356 continue;
13357 }
13358 _ => {}
13359 }
13360 }
13361
13362 let v = asheet.get_cell_value(
13363 cell.coord.row() as usize,
13364 cell.coord.col() as usize,
13365 );
13366 if !matches!(v, LiteralValue::Empty) {
13367 return self.plan_spill_error_effects(
13368 vertex_id,
13369 "BlockedByValue",
13370 h,
13371 w,
13372 );
13373 }
13374 }
13375 }
13376 }
13377
13378 let top_left = rows
13379 .first()
13380 .and_then(|r| r.first())
13381 .cloned()
13382 .unwrap_or(LiteralValue::Empty);
13383
13384 let mut effects = Vec::new();
13385 let has_prev = self
13387 .graph
13388 .spill_cells_for_anchor(vertex_id)
13389 .is_some_and(|c| !c.is_empty());
13390 if has_prev {
13391 effects.push(Effect::SpillClear {
13392 anchor_vertex: vertex_id,
13393 });
13394 }
13395 effects.push(Effect::SpillCommit {
13396 anchor_vertex: vertex_id,
13397 anchor_cell: anchor,
13398 target_cells: targets,
13399 values: rows,
13400 });
13401 effects.push(Effect::WriteCell {
13402 vertex_id,
13403 value: top_left,
13404 });
13405 Ok(effects)
13406 }
13407 Err(e) => {
13408 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
13409 self.plan_spill_error_effects(vertex_id, &msg, h, w)
13410 }
13411 }
13412 }
13413
13414 fn plan_spill_error_effects(
13416 &self,
13417 vertex_id: VertexId,
13418 message: &str,
13419 expected_rows: u32,
13420 expected_cols: u32,
13421 ) -> Result<Vec<Effect>, ExcelError> {
13422 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13423 .with_message(message)
13424 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13425 expected_rows,
13426 expected_cols,
13427 });
13428 let spill_val = LiteralValue::Error(spill_err);
13429
13430 let effects = vec![
13431 Effect::SpillClear {
13432 anchor_vertex: vertex_id,
13433 },
13434 Effect::WriteCell {
13435 vertex_id,
13436 value: spill_val,
13437 },
13438 ];
13439 Ok(effects)
13440 }
13441
13442 pub(crate) fn apply_effect(
13444 &mut self,
13445 effect: &Effect,
13446 delta: Option<&mut DeltaCollector>,
13447 log: Option<&mut ChangeLog>,
13448 ) -> Result<(), ExcelError> {
13449 self.apply_effect_with_computed_writes(effect, delta, log, None)
13450 }
13451
13452 fn apply_effect_with_computed_writes(
13453 &mut self,
13454 effect: &Effect,
13455 delta: Option<&mut DeltaCollector>,
13456 log: Option<&mut ChangeLog>,
13457 computed_writes: Option<&mut ComputedWriteBuffer>,
13458 ) -> Result<(), ExcelError> {
13459 match effect {
13460 Effect::WriteCell { vertex_id, value } => {
13461 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
13462 }
13463 Effect::SpillClear { anchor_vertex } => {
13464 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
13465 }
13466 Effect::SpillCommit {
13467 anchor_vertex,
13468 anchor_cell: _,
13469 target_cells,
13470 values,
13471 } => {
13472 self.apply_spill_commit(
13473 *anchor_vertex,
13474 target_cells,
13475 values.clone(),
13476 delta,
13477 log,
13478 computed_writes,
13479 )?;
13480 }
13481 }
13482 Ok(())
13483 }
13484
13485 fn apply_write_cell(
13487 &mut self,
13488 vertex_id: VertexId,
13489 value: &LiteralValue,
13490 delta: Option<&mut DeltaCollector>,
13491 mut computed_writes: Option<&mut ComputedWriteBuffer>,
13492 ) -> Result<(), ExcelError> {
13493 if let Some(d) = delta
13494 && d.mode != DeltaMode::Off
13495 {
13496 if let Some(buffer) = computed_writes.as_deref_mut() {
13497 self.flush_computed_write_buffer(buffer)?;
13498 }
13499 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
13500 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13501 let old = self
13502 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13503 .unwrap_or(LiteralValue::Empty);
13504 if old != *value {
13505 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13506 }
13507 }
13508 }
13509 self.graph.update_vertex_value(vertex_id, value.clone());
13510 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
13511 Ok(())
13512 }
13513
13514 fn apply_spill_clear(
13516 &mut self,
13517 anchor_vertex: VertexId,
13518 delta: Option<&mut DeltaCollector>,
13519 log: Option<&mut ChangeLog>,
13520 computed_writes: Option<&mut ComputedWriteBuffer>,
13521 ) -> Result<(), ExcelError> {
13522 if let Some(buffer) = computed_writes {
13523 self.flush_computed_write_buffer(buffer)?;
13524 }
13525
13526 let spill_cells = self
13527 .graph
13528 .spill_cells_for_anchor(anchor_vertex)
13529 .map(|cells| cells.to_vec())
13530 .unwrap_or_default();
13531 if spill_cells.is_empty() {
13532 return Ok(());
13533 }
13534
13535 let snapshot = if log.is_some() {
13537 self.snapshot_spill_for_anchor(anchor_vertex)
13538 } else {
13539 None
13540 };
13541
13542 if let Some(d) = delta
13544 && d.mode != DeltaMode::Off
13545 {
13546 let empty = LiteralValue::Empty;
13547 for cell in spill_cells.iter() {
13548 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13549 let old = self
13550 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13551 .unwrap_or(LiteralValue::Empty);
13552 if old != empty {
13553 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13554 }
13555 }
13556 }
13557
13558 self.graph.clear_spill_region(anchor_vertex);
13559 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
13560 self.record_formula_plane_structural_change(scope);
13561 }
13562
13563 if self.config.arrow_storage_enabled
13565 && self.config.delta_overlay_enabled
13566 && self.config.write_formula_overlay_enabled
13567 {
13568 let empty = LiteralValue::Empty;
13569 for cell in spill_cells.iter() {
13570 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13571 self.mirror_value_to_computed_overlay(
13572 &sheet_name,
13573 cell.coord.row() + 1,
13574 cell.coord.col() + 1,
13575 &empty,
13576 );
13577 }
13578 }
13579
13580 if let Some(log) = log
13582 && let Some(old) = snapshot
13583 {
13584 log.record(ChangeEvent::SpillCleared {
13585 anchor: anchor_vertex,
13586 old,
13587 });
13588 }
13589 Ok(())
13590 }
13591
13592 fn apply_spill_commit(
13594 &mut self,
13595 anchor_vertex: VertexId,
13596 target_cells: &[CellRef],
13597 values: Vec<Vec<LiteralValue>>,
13598 delta: Option<&mut DeltaCollector>,
13599 log: Option<&mut ChangeLog>,
13600 computed_writes: Option<&mut ComputedWriteBuffer>,
13601 ) -> Result<(), ExcelError> {
13602 if let Some(buffer) = computed_writes {
13603 self.flush_computed_write_buffer(buffer)?;
13604 }
13605
13606 let old_snapshot = if log.is_some() {
13608 self.snapshot_spill_for_anchor(anchor_vertex)
13609 } else {
13610 None
13611 };
13612
13613 self.commit_spill_and_mirror(
13615 anchor_vertex,
13616 target_cells,
13617 values.clone(),
13618 delta,
13619 None, )?;
13621
13622 if let Some(log) = log {
13624 log.record(ChangeEvent::SpillCommitted {
13625 anchor: anchor_vertex,
13626 old: old_snapshot,
13627 new: SpillSnapshot {
13628 target_cells: target_cells.to_vec(),
13629 values,
13630 },
13631 });
13632 }
13633 Ok(())
13634 }
13635
13636 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
13641 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
13642 if cells.is_empty() {
13643 return None;
13644 }
13645
13646 let max = self.config.spill.max_spill_cells as usize;
13647 let mut cells = cells;
13648 if cells.len() > max {
13649 cells.truncate(max);
13650 }
13651
13652 let first = *cells.first().expect("non-empty spill cells");
13653 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
13654 let row0 = first.coord.row();
13655 let col0 = first.coord.col();
13656
13657 let mut max_row = row0;
13658 let mut max_col = col0;
13659 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
13660 for cell in &cells {
13661 max_row = max_row.max(cell.coord.row());
13662 max_col = max_col.max(cell.coord.col());
13663 let v = self
13664 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13665 .unwrap_or(LiteralValue::Empty);
13666 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
13667 }
13668
13669 let rows = (max_row - row0 + 1) as usize;
13670 let cols = (max_col - col0 + 1) as usize;
13671 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
13672 for r in 0..rows {
13673 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
13674 for c in 0..cols {
13675 row.push(
13676 by_coord
13677 .get(&(row0 + r as u32, col0 + c as u32))
13678 .cloned()
13679 .unwrap_or(LiteralValue::Empty),
13680 );
13681 }
13682 values.push(row);
13683 }
13684
13685 Some(SpillSnapshot {
13686 target_cells: cells,
13687 values,
13688 })
13689 }
13690
13691 fn flush_before_range_dependent_vertex(
13692 &mut self,
13693 vertex_id: VertexId,
13694 computed_writes: &mut ComputedWriteBuffer,
13695 ) -> Result<(), ExcelError> {
13696 if self.graph.get_range_dependencies(vertex_id).is_some() {
13697 self.flush_computed_write_buffer(computed_writes)?;
13698 }
13699 Ok(())
13700 }
13701
13702 fn plan_vertex_effects_with_computed_flush(
13703 &mut self,
13704 vertex_id: VertexId,
13705 computed_value: LiteralValue,
13706 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13707 computed_writes: &mut ComputedWriteBuffer,
13708 ) -> Result<Vec<Effect>, ExcelError> {
13709 if matches!(&computed_value, LiteralValue::Array(_)) {
13710 self.flush_computed_write_buffer(computed_writes)?;
13711 }
13712 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
13713 }
13714
13715 fn evaluate_small_layer_direct_effects(
13718 &mut self,
13719 layer: &super::scheduler::Layer,
13720 mut delta: Option<&mut DeltaCollector>,
13721 mut log: Option<&mut ChangeLog>,
13722 cancel_flag: Option<&AtomicBool>,
13723 cancel_check_every: usize,
13724 cancel_message: &'static str,
13725 ) -> Result<usize, ExcelError> {
13726 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13727 if cancel_check_every > 0
13728 && i % cancel_check_every == 0
13729 && cancel_flag.is_some_and(|flag| flag.load(Ordering::Relaxed))
13730 {
13731 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13732 .with_message(cancel_message.to_string()));
13733 }
13734 let value = match self.evaluate_vertex_immutable(vertex_id) {
13735 Ok(v) => v,
13736 Err(e) => LiteralValue::Error(e),
13737 };
13738 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
13739 for effect in &effects {
13740 self.apply_effect_with_computed_writes(
13741 effect,
13742 delta.as_deref_mut(),
13743 log.as_deref_mut(),
13744 None,
13745 )?;
13746 }
13747 }
13748 Ok(layer.vertices.len())
13749 }
13750
13751 fn evaluate_layer_sequential_effects(
13753 &mut self,
13754 layer: &super::scheduler::Layer,
13755 ) -> Result<usize, ExcelError> {
13756 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13757 return self.evaluate_small_layer_direct_effects(
13758 layer,
13759 None,
13760 None,
13761 None,
13762 0,
13763 "Evaluation cancelled within layer",
13764 );
13765 }
13766
13767 let mut computed_writes = ComputedWriteBuffer::default();
13768 for &vertex_id in &layer.vertices {
13769 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13770 let value = match self.evaluate_vertex_immutable(vertex_id) {
13771 Ok(v) => v,
13772 Err(e) => LiteralValue::Error(e),
13773 };
13774 let effects = match self.plan_vertex_effects_with_computed_flush(
13775 vertex_id,
13776 value,
13777 None,
13778 &mut computed_writes,
13779 ) {
13780 Ok(effects) => effects,
13781 Err(e) => {
13782 self.flush_computed_write_buffer(&mut computed_writes)?;
13783 return Err(e);
13784 }
13785 };
13786 for effect in &effects {
13787 if let Err(e) = self.apply_effect_with_computed_writes(
13788 effect,
13789 None,
13790 None,
13791 Some(&mut computed_writes),
13792 ) {
13793 self.flush_computed_write_buffer(&mut computed_writes)?;
13794 return Err(e);
13795 }
13796 }
13797 }
13798 self.flush_computed_write_buffer(&mut computed_writes)?;
13799 Ok(layer.vertices.len())
13800 }
13801
13802 fn evaluate_layer_sequential_with_delta_effects(
13804 &mut self,
13805 layer: &super::scheduler::Layer,
13806 delta: &mut DeltaCollector,
13807 ) -> Result<usize, ExcelError> {
13808 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13809 return self.evaluate_small_layer_direct_effects(
13810 layer,
13811 Some(delta),
13812 None,
13813 None,
13814 0,
13815 "Evaluation cancelled within layer",
13816 );
13817 }
13818
13819 let mut computed_writes = ComputedWriteBuffer::default();
13820 for &vertex_id in &layer.vertices {
13821 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13822 let value = match self.evaluate_vertex_immutable(vertex_id) {
13823 Ok(v) => v,
13824 Err(e) => LiteralValue::Error(e),
13825 };
13826 let effects = match self.plan_vertex_effects_with_computed_flush(
13827 vertex_id,
13828 value,
13829 None,
13830 &mut computed_writes,
13831 ) {
13832 Ok(effects) => effects,
13833 Err(e) => {
13834 self.flush_computed_write_buffer(&mut computed_writes)?;
13835 return Err(e);
13836 }
13837 };
13838 for effect in &effects {
13839 if let Err(e) = self.apply_effect_with_computed_writes(
13840 effect,
13841 Some(delta),
13842 None,
13843 Some(&mut computed_writes),
13844 ) {
13845 self.flush_computed_write_buffer(&mut computed_writes)?;
13846 return Err(e);
13847 }
13848 }
13849 }
13850 self.flush_computed_write_buffer(&mut computed_writes)?;
13851 Ok(layer.vertices.len())
13852 }
13853
13854 fn evaluate_layer_sequential_cancellable_effects(
13856 &mut self,
13857 layer: &super::scheduler::Layer,
13858 cancel_flag: &AtomicBool,
13859 ) -> Result<usize, ExcelError> {
13860 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13861 return self.evaluate_small_layer_direct_effects(
13862 layer,
13863 None,
13864 None,
13865 Some(cancel_flag),
13866 256,
13867 "Evaluation cancelled within layer",
13868 );
13869 }
13870
13871 let mut computed_writes = ComputedWriteBuffer::default();
13872 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13873 if i % 256 == 0 && cancel_flag.load(Ordering::Relaxed) {
13874 self.flush_computed_write_buffer(&mut computed_writes)?;
13875 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13876 .with_message("Evaluation cancelled within layer".to_string()));
13877 }
13878 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13879 let value = match self.evaluate_vertex_immutable(vertex_id) {
13880 Ok(v) => v,
13881 Err(e) => LiteralValue::Error(e),
13882 };
13883 let effects = match self.plan_vertex_effects_with_computed_flush(
13884 vertex_id,
13885 value,
13886 None,
13887 &mut computed_writes,
13888 ) {
13889 Ok(effects) => effects,
13890 Err(e) => {
13891 self.flush_computed_write_buffer(&mut computed_writes)?;
13892 return Err(e);
13893 }
13894 };
13895 for effect in &effects {
13896 if let Err(e) = self.apply_effect_with_computed_writes(
13897 effect,
13898 None,
13899 None,
13900 Some(&mut computed_writes),
13901 ) {
13902 self.flush_computed_write_buffer(&mut computed_writes)?;
13903 return Err(e);
13904 }
13905 }
13906 }
13907 self.flush_computed_write_buffer(&mut computed_writes)?;
13908 Ok(layer.vertices.len())
13909 }
13910
13911 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
13913 &mut self,
13914 layer: &super::scheduler::Layer,
13915 cancel_flag: &AtomicBool,
13916 ) -> Result<usize, ExcelError> {
13917 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13918 return self.evaluate_small_layer_direct_effects(
13919 layer,
13920 None,
13921 None,
13922 Some(cancel_flag),
13923 128,
13924 "Demand-driven evaluation cancelled within layer",
13925 );
13926 }
13927
13928 let mut computed_writes = ComputedWriteBuffer::default();
13929 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13930 if i % 128 == 0 && cancel_flag.load(Ordering::Relaxed) {
13931 self.flush_computed_write_buffer(&mut computed_writes)?;
13932 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13933 .with_message("Demand-driven evaluation cancelled within layer".to_string()));
13934 }
13935 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13936 let value = match self.evaluate_vertex_immutable(vertex_id) {
13937 Ok(v) => v,
13938 Err(e) => LiteralValue::Error(e),
13939 };
13940 let effects = match self.plan_vertex_effects_with_computed_flush(
13941 vertex_id,
13942 value,
13943 None,
13944 &mut computed_writes,
13945 ) {
13946 Ok(effects) => effects,
13947 Err(e) => {
13948 self.flush_computed_write_buffer(&mut computed_writes)?;
13949 return Err(e);
13950 }
13951 };
13952 for effect in &effects {
13953 if let Err(e) = self.apply_effect_with_computed_writes(
13954 effect,
13955 None,
13956 None,
13957 Some(&mut computed_writes),
13958 ) {
13959 self.flush_computed_write_buffer(&mut computed_writes)?;
13960 return Err(e);
13961 }
13962 }
13963 }
13964 self.flush_computed_write_buffer(&mut computed_writes)?;
13965 Ok(layer.vertices.len())
13966 }
13967
13968 fn evaluate_layer_parallel_effects(
13970 &mut self,
13971 layer: &super::scheduler::Layer,
13972 ) -> Result<usize, ExcelError> {
13973 use rayon::prelude::*;
13974
13975 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
13976
13977 let mut phase1: Vec<VertexId> = Vec::new();
13978 let mut phase2: Vec<VertexId> = Vec::new();
13979 for &vid in &layer.vertices {
13980 if self.graph.get_range_dependencies(vid).is_some() {
13981 phase2.push(vid);
13982 } else {
13983 phase1.push(vid);
13984 }
13985 }
13986
13987 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
13988 let mut applied = 0usize;
13989
13990 for group in [&phase1[..], &phase2[..]] {
13991 if group.is_empty() {
13992 continue;
13993 }
13994 let mut computed_writes = ComputedWriteBuffer::default();
13995
13996 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
13997 thread_pool.install(|| {
13998 group
13999 .par_iter()
14000 .map(
14001 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
14002 Ok(v) => Ok((vertex_id, v)),
14003 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
14004 },
14005 )
14006 .collect()
14007 });
14008
14009 match results {
14010 Ok(vertex_results) => {
14011 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14014 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14015 for (vertex_id, result) in vertex_results {
14016 if matches!(result, LiteralValue::Array(_)) {
14017 arrays.push((vertex_id, result));
14018 } else {
14019 others.push((vertex_id, result));
14020 }
14021 }
14022 for (vertex_id, result) in arrays {
14023 let effects = match self.plan_vertex_effects_with_computed_flush(
14024 vertex_id,
14025 result,
14026 Some(&inflight),
14027 &mut computed_writes,
14028 ) {
14029 Ok(effects) => effects,
14030 Err(e) => {
14031 self.flush_computed_write_buffer(&mut computed_writes)?;
14032 return Err(e);
14033 }
14034 };
14035 for effect in &effects {
14036 if let Err(e) = self.apply_effect_with_computed_writes(
14037 effect,
14038 None,
14039 None,
14040 Some(&mut computed_writes),
14041 ) {
14042 self.flush_computed_write_buffer(&mut computed_writes)?;
14043 return Err(e);
14044 }
14045 }
14046 applied = applied.saturating_add(1);
14047 }
14048 self.flush_computed_write_buffer(&mut computed_writes)?;
14050 for (vertex_id, result) in others {
14051 let effects = match self.plan_vertex_effects_with_computed_flush(
14052 vertex_id,
14053 result,
14054 Some(&inflight),
14055 &mut computed_writes,
14056 ) {
14057 Ok(effects) => effects,
14058 Err(e) => {
14059 self.flush_computed_write_buffer(&mut computed_writes)?;
14060 return Err(e);
14061 }
14062 };
14063 for effect in &effects {
14064 if let Err(e) = self.apply_effect_with_computed_writes(
14065 effect,
14066 None,
14067 None,
14068 Some(&mut computed_writes),
14069 ) {
14070 self.flush_computed_write_buffer(&mut computed_writes)?;
14071 return Err(e);
14072 }
14073 }
14074 applied = applied.saturating_add(1);
14075 }
14076 self.flush_computed_write_buffer(&mut computed_writes)?;
14078 }
14079 Err(e) => {
14080 self.flush_computed_write_buffer(&mut computed_writes)?;
14081 return Err(e);
14082 }
14083 }
14084 }
14085
14086 Ok(applied)
14087 }
14088
14089 fn evaluate_layer_parallel_with_delta_effects(
14091 &mut self,
14092 layer: &super::scheduler::Layer,
14093 delta: &mut DeltaCollector,
14094 ) -> Result<usize, ExcelError> {
14095 use rayon::prelude::*;
14096
14097 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
14098
14099 let mut phase1: Vec<VertexId> = Vec::new();
14100 let mut phase2: Vec<VertexId> = Vec::new();
14101 for &vid in &layer.vertices {
14102 if self.graph.get_range_dependencies(vid).is_some() {
14103 phase2.push(vid);
14104 } else {
14105 phase1.push(vid);
14106 }
14107 }
14108
14109 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
14110 let mut applied = 0usize;
14111
14112 for group in [&phase1[..], &phase2[..]] {
14113 if group.is_empty() {
14114 continue;
14115 }
14116 let mut computed_writes = ComputedWriteBuffer::default();
14117 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
14118 thread_pool.install(|| {
14119 group
14120 .par_iter()
14121 .map(
14122 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
14123 Ok(v) => Ok((vertex_id, v)),
14124 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
14125 },
14126 )
14127 .collect()
14128 });
14129
14130 match results {
14131 Ok(vertex_results) => {
14132 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14133 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14134 for (vertex_id, result) in vertex_results {
14135 if matches!(result, LiteralValue::Array(_)) {
14136 arrays.push((vertex_id, result));
14137 } else {
14138 others.push((vertex_id, result));
14139 }
14140 }
14141 for (vertex_id, result) in arrays {
14142 let effects = match self.plan_vertex_effects_with_computed_flush(
14143 vertex_id,
14144 result,
14145 Some(&inflight),
14146 &mut computed_writes,
14147 ) {
14148 Ok(effects) => effects,
14149 Err(e) => {
14150 self.flush_computed_write_buffer(&mut computed_writes)?;
14151 return Err(e);
14152 }
14153 };
14154 for effect in &effects {
14155 if let Err(e) = self.apply_effect_with_computed_writes(
14156 effect,
14157 Some(delta),
14158 None,
14159 Some(&mut computed_writes),
14160 ) {
14161 self.flush_computed_write_buffer(&mut computed_writes)?;
14162 return Err(e);
14163 }
14164 }
14165 applied = applied.saturating_add(1);
14166 }
14167 self.flush_computed_write_buffer(&mut computed_writes)?;
14168 for (vertex_id, result) in others {
14169 let effects = match self.plan_vertex_effects_with_computed_flush(
14170 vertex_id,
14171 result,
14172 Some(&inflight),
14173 &mut computed_writes,
14174 ) {
14175 Ok(effects) => effects,
14176 Err(e) => {
14177 self.flush_computed_write_buffer(&mut computed_writes)?;
14178 return Err(e);
14179 }
14180 };
14181 for effect in &effects {
14182 if let Err(e) = self.apply_effect_with_computed_writes(
14183 effect,
14184 Some(delta),
14185 None,
14186 Some(&mut computed_writes),
14187 ) {
14188 self.flush_computed_write_buffer(&mut computed_writes)?;
14189 return Err(e);
14190 }
14191 }
14192 applied = applied.saturating_add(1);
14193 }
14194 self.flush_computed_write_buffer(&mut computed_writes)?;
14195 }
14196 Err(e) => {
14197 self.flush_computed_write_buffer(&mut computed_writes)?;
14198 return Err(e);
14199 }
14200 }
14201 }
14202
14203 Ok(applied)
14204 }
14205
14206 fn evaluate_layer_parallel_cancellable_effects(
14208 &mut self,
14209 layer: &super::scheduler::Layer,
14210 cancel_flag: &AtomicBool,
14211 ) -> Result<usize, ExcelError> {
14212 use rayon::prelude::*;
14213
14214 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
14215
14216 if cancel_flag.load(Ordering::Relaxed) {
14217 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
14218 .with_message("Parallel evaluation cancelled before starting".to_string()));
14219 }
14220
14221 let mut phase1: Vec<VertexId> = Vec::new();
14222 let mut phase2: Vec<VertexId> = Vec::new();
14223 for &vid in &layer.vertices {
14224 if self.graph.get_range_dependencies(vid).is_some() {
14225 phase2.push(vid);
14226 } else {
14227 phase1.push(vid);
14228 }
14229 }
14230
14231 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
14232 let mut applied = 0usize;
14233
14234 for group in [&phase1[..], &phase2[..]] {
14235 if group.is_empty() {
14236 continue;
14237 }
14238 let mut computed_writes = ComputedWriteBuffer::default();
14239
14240 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
14241 thread_pool.install(|| {
14242 group
14243 .par_iter()
14244 .map(|&vertex_id| {
14245 if cancel_flag.load(Ordering::Relaxed) {
14246 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
14247 .with_message(
14248 "Parallel evaluation cancelled during execution"
14249 .to_string(),
14250 ));
14251 }
14252 match self.evaluate_vertex_immutable(vertex_id) {
14253 Ok(v) => Ok((vertex_id, v)),
14254 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
14255 }
14256 })
14257 .collect()
14258 });
14259
14260 match results {
14261 Ok(vertex_results) => {
14262 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14263 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14264 for (vertex_id, result) in vertex_results {
14265 if matches!(result, LiteralValue::Array(_)) {
14266 arrays.push((vertex_id, result));
14267 } else {
14268 others.push((vertex_id, result));
14269 }
14270 }
14271 for (vertex_id, result) in arrays {
14272 let effects = match self.plan_vertex_effects_with_computed_flush(
14273 vertex_id,
14274 result,
14275 Some(&inflight),
14276 &mut computed_writes,
14277 ) {
14278 Ok(effects) => effects,
14279 Err(e) => {
14280 self.flush_computed_write_buffer(&mut computed_writes)?;
14281 return Err(e);
14282 }
14283 };
14284 for effect in &effects {
14285 if let Err(e) = self.apply_effect_with_computed_writes(
14286 effect,
14287 None,
14288 None,
14289 Some(&mut computed_writes),
14290 ) {
14291 self.flush_computed_write_buffer(&mut computed_writes)?;
14292 return Err(e);
14293 }
14294 }
14295 applied = applied.saturating_add(1);
14296 }
14297 self.flush_computed_write_buffer(&mut computed_writes)?;
14298 for (vertex_id, result) in others {
14299 let effects = match self.plan_vertex_effects_with_computed_flush(
14300 vertex_id,
14301 result,
14302 Some(&inflight),
14303 &mut computed_writes,
14304 ) {
14305 Ok(effects) => effects,
14306 Err(e) => {
14307 self.flush_computed_write_buffer(&mut computed_writes)?;
14308 return Err(e);
14309 }
14310 };
14311 for effect in &effects {
14312 if let Err(e) = self.apply_effect_with_computed_writes(
14313 effect,
14314 None,
14315 None,
14316 Some(&mut computed_writes),
14317 ) {
14318 self.flush_computed_write_buffer(&mut computed_writes)?;
14319 return Err(e);
14320 }
14321 }
14322 applied = applied.saturating_add(1);
14323 }
14324 self.flush_computed_write_buffer(&mut computed_writes)?;
14325 }
14326 Err(e) => {
14327 self.flush_computed_write_buffer(&mut computed_writes)?;
14328 return Err(e);
14329 }
14330 }
14331 }
14332
14333 Ok(applied)
14334 }
14335
14336 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
14343 self.begin_evaluation_request();
14344 let _source_cache = self.source_cache_session();
14345 self.validate_deterministic_mode()?;
14346 if self.config.defer_graph_building {
14347 self.build_graph_all()?;
14348 }
14349 if self.graph.formula_authority().active_span_count() > 0 {
14350 return self.evaluate_authoritative_formula_plane_all();
14351 }
14352 self.reset_virtual_dep_telemetry_if_disabled();
14353 let start = crate::instant::FzInstant::now();
14354 let mut computed_vertices = 0;
14355 let mut cycle_errors = 0;
14356
14357 let mut replan_iterations = 0;
14358 const MAX_REPLAN: usize = 5;
14359 let mut telemetry = self
14360 .config
14361 .enable_virtual_dep_telemetry
14362 .then(|| self.start_virtual_dep_telemetry());
14363
14364 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
14365
14366 loop {
14367 let to_evaluate = self.graph.get_evaluation_vertices();
14368 if to_evaluate.is_empty() {
14369 if let Some(t) = telemetry.as_mut()
14370 && t.bailout_reason.is_none()
14371 {
14372 t.bailout_reason = Some("no_work");
14373 }
14374 break;
14375 }
14376
14377 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14378 if let Some(t) = telemetry.as_mut() {
14379 Self::accumulate_schedule_meta(t, &meta);
14380 }
14381
14382 for &unit in &schedule.units {
14385 match unit {
14386 ScheduleUnit::Cycle(i) => {
14387 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
14398 cycle_errors += 1;
14399 }
14400 }
14401 ScheduleUnit::Layer(i) => {
14402 computed_vertices +=
14403 self.evaluate_layer_logged(schedule.unit_layer(i), log)?;
14404 }
14405 }
14406 }
14407
14408 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14409 if let Some(t) = telemetry.as_mut() {
14410 t.changed_vdeps_total += changed_vertices.len();
14411 }
14412 self.graph.clear_dirty_flags(&to_evaluate);
14413 for v in &changed_vertices {
14414 self.graph.set_dirty(*v, true);
14415 }
14416
14417 if changed_vertices.is_empty() {
14418 if let Some(t) = telemetry.as_mut() {
14419 t.bailout_reason = Some("converged");
14420 }
14421 break;
14422 }
14423 if replan_iterations >= MAX_REPLAN {
14424 if let Some(t) = telemetry.as_mut() {
14425 t.bailout_reason = Some("max_replan");
14426 }
14427 break;
14428 }
14429 replan_iterations += 1;
14430 }
14431
14432 if let Some(mut t) = telemetry {
14433 t.replan_iterations = replan_iterations;
14434 self.last_virtual_dep_telemetry = t;
14435 }
14436
14437 log.end_compound();
14438
14439 self.redirty_for_next_recalc();
14440 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14441
14442 Ok(EvalResult {
14443 computed_vertices,
14444 cycle_errors,
14445 elapsed: start.elapsed(),
14446 })
14447 }
14448
14449 fn evaluate_layer_logged(
14451 &mut self,
14452 layer: &super::scheduler::Layer,
14453 log: &mut ChangeLog,
14454 ) -> Result<usize, ExcelError> {
14455 let mut computed_writes = ComputedWriteBuffer::default();
14456 for &vertex_id in &layer.vertices {
14457 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
14458 let value = match self.evaluate_vertex_immutable(vertex_id) {
14459 Ok(v) => v,
14460 Err(e) => LiteralValue::Error(e),
14461 };
14462 let effects = match self.plan_vertex_effects_with_computed_flush(
14463 vertex_id,
14464 value,
14465 None,
14466 &mut computed_writes,
14467 ) {
14468 Ok(effects) => effects,
14469 Err(e) => {
14470 self.flush_computed_write_buffer(&mut computed_writes)?;
14471 return Err(e);
14472 }
14473 };
14474 for effect in &effects {
14475 if let Err(e) = self.apply_effect_with_computed_writes(
14476 effect,
14477 None,
14478 Some(log),
14479 Some(&mut computed_writes),
14480 ) {
14481 self.flush_computed_write_buffer(&mut computed_writes)?;
14482 return Err(e);
14483 }
14484 }
14485 }
14486 self.flush_computed_write_buffer(&mut computed_writes)?;
14487 Ok(layer.vertices.len())
14488 }
14489}