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 pat = StringArray::new_scalar(text_pat);
1330 let mut bool_parts: Vec<BooleanArray> = Vec::new();
1331
1332 for res in view.iter_row_chunks() {
1333 let cs = res.ok()?;
1334 if cs.row_len == 0 {
1335 continue;
1336 }
1337 #[cfg(test)]
1338 criteria_mask_test_hooks::inc_total();
1339
1340 let slices = view.slice_lowered_text(cs.row_start, cs.row_len);
1341 if col_in_view >= slices.len() {
1342 return None;
1343 }
1344
1345 let seg_opt = slices[col_in_view].as_ref().map(|a| a.as_ref());
1346 let seg = match seg_opt {
1347 Some(s) => s,
1348 None => {
1349 #[cfg(test)]
1350 criteria_mask_test_hooks::inc_all_null();
1351 if text_kind == 0 && empty_special {
1352 let mut bb = BooleanBuilder::with_capacity(cs.row_len);
1354 bb.append_n(cs.row_len, true);
1355 bool_parts.push(bb.finish());
1356 } else {
1357 bool_parts.push(BooleanArray::new_null(cs.row_len));
1359 }
1360 continue;
1361 }
1362 };
1363
1364 let seg_sa = seg.as_any().downcast_ref::<StringArray>()?;
1365 let mut m = match text_kind {
1366 0 => ilike(seg_sa, &pat).ok()?,
1367 1 => nilike(seg_sa, &pat).ok()?,
1368 2 => ilike(seg_sa, &pat).ok()?,
1369 _ => return None,
1370 };
1371
1372 if text_kind == 0 && empty_special {
1373 let mut bb = BooleanBuilder::with_capacity(seg_sa.len());
1375 for i in 0..seg_sa.len() {
1376 bb.append_value(seg_sa.is_null(i));
1377 }
1378 let nulls = bb.finish();
1379 m = boolean::or_kleene(&m, &nulls).ok()?;
1380 }
1381
1382 bool_parts.push(m);
1383 }
1384
1385 if bool_parts.is_empty() {
1386 None
1387 } else if bool_parts.len() == 1 {
1388 Some(std::sync::Arc::new(bool_parts.remove(0)))
1389 } else {
1390 let anys: Vec<&dyn arrow_array::Array> = bool_parts
1391 .iter()
1392 .map(|a| a as &dyn arrow_array::Array)
1393 .collect();
1394 let conc: ArrayRef = concat_arrays(&anys).ok()?;
1395 let ba = conc.as_any().downcast_ref::<BooleanArray>()?.clone();
1396 Some(std::sync::Arc::new(ba))
1397 }
1398}
1399
1400#[derive(Debug, Clone)]
1401pub struct LayerInfo {
1402 pub vertex_count: usize,
1403 pub parallel_eligible: bool,
1404 pub sample_cells: Vec<String>, }
1406
1407#[derive(Debug, Clone)]
1408pub struct EvalPlan {
1409 pub total_vertices_to_evaluate: usize,
1410 pub layers: Vec<LayerInfo>,
1411 pub cycles_detected: usize,
1412 pub dirty_count: usize,
1413 pub volatile_count: usize,
1414 pub parallel_enabled: bool,
1415 pub estimated_parallel_layers: usize,
1416 pub target_cells: Vec<String>,
1417}
1418
1419impl<R> Engine<R>
1420where
1421 R: EvaluationContext,
1422{
1423 pub fn new(resolver: R, config: EvalConfig) -> Self {
1430 if let Err(msg) = config.cycle.validate() {
1431 panic!("invalid CycleConfig: {msg}");
1432 }
1433 crate::builtins::load_builtins();
1434
1435 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1436 #[cfg(feature = "system-clock")]
1437 {
1438 Arc::new(crate::timezone::SystemClock::new(
1439 crate::timezone::TimeZoneSpec::default(),
1440 ))
1441 }
1442 #[cfg(not(feature = "system-clock"))]
1443 {
1444 Arc::new(crate::timezone::FixedClock::new(
1445 chrono::DateTime::UNIX_EPOCH,
1446 crate::timezone::TimeZoneSpec::Utc,
1447 ))
1448 }
1449 });
1450
1451 let thread_pool = if config.enable_parallel {
1453 let mut builder = ThreadPoolBuilder::new();
1454 if let Some(max_threads) = config.max_threads {
1455 builder = builder.num_threads(max_threads);
1456 }
1457
1458 match builder.build() {
1459 Ok(pool) => Some(Arc::new(pool)),
1460 Err(_) => {
1461 None
1463 }
1464 }
1465 } else {
1466 None
1467 };
1468
1469 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1470 let mut engine = Self {
1471 graph: DependencyGraph::new_with_config(config.clone()),
1472 resolver,
1473 config,
1474 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1475 clock: crate::timezone::SnapshotClock::new(clock),
1476 thread_pool,
1477 recalc_epoch: 0,
1478 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1479 topology_epoch: 0,
1480 cached_static_schedule: None,
1481 spill_mgr: ShimSpillManager::default(),
1482 arrow_sheets: SheetStore::default(),
1483 has_edited: false,
1484 overlay_compactions: 0,
1485 computed_overlay_bytes_estimate: 0,
1486 computed_overlay_mirroring_disabled: false,
1487 force_materialize_range_views: false,
1488 row_bounds_cache: std::sync::RwLock::new(None),
1489 used_axis_bounds_cache: std::sync::RwLock::new(None),
1490 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1491 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1492 staged_formulas: std::collections::HashMap::new(),
1493 row_visibility: FxHashMap::default(),
1494 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1495 formula_parse_diagnostics: Vec::new(),
1496 last_formula_ingest_report: None,
1497 formula_ingest_report_total: FormulaIngestReport::default(),
1498 formula_plane_cycle_member_span_demotions: 0,
1499 formula_plane_capacity_bailouts: 0,
1500 active_cancel_flag: None,
1501 action_depth: 0,
1502 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1503 virtual_dep_fallback_activations: 0,
1504 last_cycle_telemetry: CycleTelemetry::default(),
1505 pending_iterative_redirty: Vec::new(),
1506 iterative_state_values: FxHashMap::default(),
1507 formula_plane_indexes_epoch_seen: 0,
1508 #[cfg(test)]
1509 last_formula_plane_span_eval_report: None,
1510 };
1511 engine.config.arrow_storage_enabled = true;
1513 engine.config.delta_overlay_enabled = true;
1514 engine.config.write_formula_overlay_enabled = true;
1515 let default_sheet = engine.graph.default_sheet_name().to_string();
1516 engine.ensure_arrow_sheet(&default_sheet);
1517 engine
1518 }
1519
1520 pub fn with_thread_pool(
1525 resolver: R,
1526 config: EvalConfig,
1527 thread_pool: Arc<rayon::ThreadPool>,
1528 ) -> Self {
1529 if let Err(msg) = config.cycle.validate() {
1530 panic!("invalid CycleConfig: {msg}");
1531 }
1532 crate::builtins::load_builtins();
1533 let clock = config.deterministic_mode.build_clock().unwrap_or_else(|_| {
1534 #[cfg(feature = "system-clock")]
1535 {
1536 Arc::new(crate::timezone::SystemClock::new(
1537 crate::timezone::TimeZoneSpec::default(),
1538 ))
1539 }
1540 #[cfg(not(feature = "system-clock"))]
1541 {
1542 Arc::new(crate::timezone::FixedClock::new(
1543 chrono::DateTime::UNIX_EPOCH,
1544 crate::timezone::TimeZoneSpec::Utc,
1545 ))
1546 }
1547 });
1548 let lookup_cache_max_bytes = config.lookup_index_cache_max_bytes;
1549 let mut engine = Self {
1550 graph: DependencyGraph::new_with_config(config.clone()),
1551 resolver,
1552 config,
1553 workbook_load_limits: crate::engine::WorkbookLoadLimits::default(),
1554 clock: crate::timezone::SnapshotClock::new(clock),
1555 thread_pool: Some(thread_pool),
1556 recalc_epoch: 0,
1557 snapshot_id: std::sync::atomic::AtomicU64::new(1),
1558 topology_epoch: 0,
1559 cached_static_schedule: None,
1560 spill_mgr: ShimSpillManager::default(),
1561 arrow_sheets: SheetStore::default(),
1562 has_edited: false,
1563 overlay_compactions: 0,
1564 computed_overlay_bytes_estimate: 0,
1565 computed_overlay_mirroring_disabled: false,
1566 force_materialize_range_views: false,
1567 row_bounds_cache: std::sync::RwLock::new(None),
1568 used_axis_bounds_cache: std::sync::RwLock::new(None),
1569 lookup_index_cache: LookupIndexCache::new(lookup_cache_max_bytes),
1570 source_cache: Arc::new(std::sync::RwLock::new(SourceCache::default())),
1571 staged_formulas: std::collections::HashMap::new(),
1572 row_visibility: FxHashMap::default(),
1573 row_visibility_mask_cache: std::sync::RwLock::new(FxHashMap::default()),
1574 formula_parse_diagnostics: Vec::new(),
1575 last_formula_ingest_report: None,
1576 formula_ingest_report_total: FormulaIngestReport::default(),
1577 formula_plane_cycle_member_span_demotions: 0,
1578 formula_plane_capacity_bailouts: 0,
1579 active_cancel_flag: None,
1580 action_depth: 0,
1581 last_virtual_dep_telemetry: VirtualDepTelemetry::default(),
1582 virtual_dep_fallback_activations: 0,
1583 last_cycle_telemetry: CycleTelemetry::default(),
1584 pending_iterative_redirty: Vec::new(),
1585 iterative_state_values: FxHashMap::default(),
1586 formula_plane_indexes_epoch_seen: 0,
1587 #[cfg(test)]
1588 last_formula_plane_span_eval_report: None,
1589 };
1590 engine.config.arrow_storage_enabled = true;
1592 engine.config.delta_overlay_enabled = true;
1593 engine.config.write_formula_overlay_enabled = true;
1594 let default_sheet = engine.graph.default_sheet_name().to_string();
1595 engine.ensure_arrow_sheet(&default_sheet);
1596 engine
1597 }
1598
1599 pub fn workbook_load_limits(&self) -> &crate::engine::WorkbookLoadLimits {
1600 &self.workbook_load_limits
1601 }
1602
1603 pub fn set_workbook_load_limits(&mut self, limits: crate::engine::WorkbookLoadLimits) {
1604 self.workbook_load_limits = limits;
1605 }
1606
1607 fn clear_source_cache(&self) {
1608 if let Ok(mut g) = self.source_cache.write() {
1609 *g = SourceCache::default();
1610 }
1611 }
1612
1613 pub fn last_virtual_dep_telemetry(&self) -> &VirtualDepTelemetry {
1614 &self.last_virtual_dep_telemetry
1615 }
1616
1617 pub fn last_cycle_telemetry(&self) -> &CycleTelemetry {
1621 &self.last_cycle_telemetry
1622 }
1623
1624 fn begin_evaluation_request(&mut self) {
1628 self.last_cycle_telemetry = CycleTelemetry::default();
1629 self.pending_iterative_redirty.clear();
1632 self.clock.refresh();
1636 }
1637
1638 fn redirty_for_next_recalc(&mut self) {
1645 self.graph.redirty_volatiles();
1646 let pending = std::mem::take(&mut self.pending_iterative_redirty);
1647 self.iterative_state_values.clear();
1653 for &vertex in &pending {
1654 if !self.graph.vertex_exists(vertex) {
1655 continue;
1656 }
1657 if let Some(cell) = self.graph.get_cell_ref(vertex) {
1658 let sheet_name = self.graph.sheet_name(cell.sheet_id);
1659 if let Some(value) =
1660 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
1661 && !matches!(value, LiteralValue::Empty)
1662 {
1663 self.iterative_state_values.insert(vertex, value);
1664 }
1665 }
1666 }
1667 if !pending.is_empty() {
1668 self.graph.redirty_iterative_members(&pending);
1669 }
1670 }
1671
1672 pub fn virtual_dep_fallback_activations(&self) -> u64 {
1673 self.virtual_dep_fallback_activations
1674 }
1675
1676 pub(crate) fn last_lookup_index_cache_report(&self) -> LookupIndexCacheReport {
1677 self.lookup_index_cache.report()
1678 }
1679
1680 fn lookup_view_contains_volatile(&self, view: &RangeView<'_>, sheet_id: SheetId) -> bool {
1681 let start_row = view.start_row();
1682 let end_row = view.end_row();
1683 let start_col = view.start_col();
1684 let end_col = view.end_col();
1685 for row in start_row..=end_row {
1686 let Ok(row_u32) = u32::try_from(row) else {
1687 return true;
1688 };
1689 for col in start_col..=end_col {
1690 let Ok(col_u32) = u32::try_from(col) else {
1691 return true;
1692 };
1693 let cell_ref = self
1694 .graph
1695 .make_cell_ref_internal(sheet_id, row_u32, col_u32);
1696 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(&cell_ref)
1697 && self.graph.is_volatile(*vertex_id)
1698 {
1699 return true;
1700 }
1701 }
1702 }
1703 false
1704 }
1705
1706 fn build_lookup_index_impl(
1707 &self,
1708 view: &RangeView<'_>,
1709 axis: LookupAxis,
1710 ) -> Option<Arc<LookupIndex>> {
1711 let (rows, cols) = view.dims();
1712 if rows == 0 || cols == 0 {
1713 self.lookup_index_cache.note_skipped_tiny();
1714 return None;
1715 }
1716 let len = match axis {
1717 LookupAxis::ColumnInView(col) => {
1718 if col >= cols {
1719 self.lookup_index_cache.note_skipped_tiny();
1720 return None;
1721 }
1722 rows
1723 }
1724 LookupAxis::RowInView(row) => {
1725 if row >= rows {
1726 self.lookup_index_cache.note_skipped_tiny();
1727 return None;
1728 }
1729 cols
1730 }
1731 };
1732 if len < 64 {
1733 self.lookup_index_cache.note_skipped_tiny();
1734 return None;
1735 }
1736
1737 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
1738 let key = LookupIndexKey {
1739 sheet_id,
1740 start_row: u32::try_from(view.start_row()).ok()?,
1741 start_col: u32::try_from(view.start_col()).ok()?,
1742 end_row: u32::try_from(view.end_row()).ok()?,
1743 end_col: u32::try_from(view.end_col()).ok()?,
1744 axis,
1745 snapshot_id: self.data_snapshot_id(),
1746 };
1747 if let Some(index) = self.lookup_index_cache.get(&key) {
1748 return Some(index);
1749 }
1750 if self
1751 .lookup_index_cache
1752 .would_exceed_cap(estimate_bytes(len, 0))
1753 {
1754 self.lookup_index_cache.note_skipped_cap();
1755 return None;
1756 }
1757 if !self.lookup_index_cache.should_build(key) {
1758 return None;
1759 }
1760 if self.lookup_index_cache.is_known_volatile(&key) {
1761 self.lookup_index_cache.note_skipped_volatile();
1762 return None;
1763 }
1764 if self.lookup_view_contains_volatile(view, sheet_id) {
1765 self.lookup_index_cache.note_volatile_key(key);
1766 self.lookup_index_cache.note_skipped_volatile();
1767 return None;
1768 }
1769 match LookupIndex::build(view, axis).ok()? {
1770 BuildOutcome::Built(index) => self.lookup_index_cache.insert_if_room(key, index),
1771 BuildOutcome::ErrorInLookupAxis => {
1772 self.lookup_index_cache.note_skipped_error();
1773 None
1774 }
1775 BuildOutcome::Degenerate => {
1776 self.lookup_index_cache.note_skipped_tiny();
1777 None
1778 }
1779 }
1780 }
1781
1782 fn reset_virtual_dep_telemetry_if_disabled(&mut self) {
1783 if !self.config.enable_virtual_dep_telemetry {
1784 self.last_virtual_dep_telemetry = VirtualDepTelemetry {
1785 fallback_mode_activations: self.virtual_dep_fallback_activations,
1786 ..VirtualDepTelemetry::default()
1787 };
1788 }
1789 }
1790
1791 fn source_cache_session(&self) -> SourceCacheSession {
1792 self.clear_source_cache();
1793 SourceCacheSession {
1794 cache: self.source_cache.clone(),
1795 }
1796 }
1797
1798 fn resolve_source_scalar_cached(
1799 &self,
1800 name: &str,
1801 version: Option<u64>,
1802 ) -> Result<LiteralValue, ExcelError> {
1803 let key = (name.to_string(), version);
1804 if let Ok(mut g) = self.source_cache.write() {
1805 if let Some(v) = g.scalars.get(&key) {
1806 return Ok(v.clone());
1807 }
1808
1809 let v = self.resolver.resolve_source_scalar(name).map_err(|err| {
1810 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1811 ExcelError::new(ExcelErrorKind::Ref)
1812 .with_message(format!("Unresolved source scalar: {name}"))
1813 } else {
1814 err
1815 }
1816 })?;
1817 g.scalars.insert(key, v.clone());
1818 Ok(v)
1819 } else {
1820 self.resolver.resolve_source_scalar(name).map_err(|err| {
1821 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1822 ExcelError::new(ExcelErrorKind::Ref)
1823 .with_message(format!("Unresolved source scalar: {name}"))
1824 } else {
1825 err
1826 }
1827 })
1828 }
1829 }
1830
1831 fn resolve_source_table_cached(
1832 &self,
1833 name: &str,
1834 version: Option<u64>,
1835 ) -> Result<Arc<dyn crate::traits::Table>, ExcelError> {
1836 let key = (name.to_string(), version);
1837 if let Ok(mut g) = self.source_cache.write() {
1838 if let Some(t) = g.tables.get(&key) {
1839 return Ok(t.clone());
1840 }
1841
1842 let t = self.resolver.resolve_source_table(name).map_err(|err| {
1843 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1844 ExcelError::new(ExcelErrorKind::Ref)
1845 .with_message(format!("Unresolved source table: {name}"))
1846 } else {
1847 err
1848 }
1849 })?;
1850 let t: Arc<dyn crate::traits::Table> = Arc::from(t);
1851 g.tables.insert(key, t.clone());
1852 Ok(t)
1853 } else {
1854 self.resolver
1855 .resolve_source_table(name)
1856 .map_err(|err| {
1857 if matches!(err.kind, ExcelErrorKind::Name | ExcelErrorKind::NImpl) {
1858 ExcelError::new(ExcelErrorKind::Ref)
1859 .with_message(format!("Unresolved source table: {name}"))
1860 } else {
1861 err
1862 }
1863 })
1864 .map(Arc::from)
1865 }
1866 }
1867
1868 fn source_table_to_range_view(
1869 &self,
1870 table: &dyn crate::traits::Table,
1871 spec: &Option<formualizer_parse::parser::TableSpecifier>,
1872 ) -> Result<RangeView<'static>, ExcelError> {
1873 use formualizer_parse::parser::{SpecialItem, TableSpecifier};
1874
1875 let owned = match spec {
1876 Some(TableSpecifier::Column(c)) => {
1877 let c = c.trim();
1878 if c == "@" || c.contains('[') || c.contains(']') || c.contains(',') {
1879 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1880 "Complex structured references not yet supported".to_string(),
1881 ));
1882 }
1883 table.get_column(c)?.materialise().into_owned()
1884 }
1885 Some(TableSpecifier::ColumnRange(start, end)) => {
1886 let cols = table.columns();
1887 let start = start.trim();
1888 let end = end.trim();
1889 let start_key = start.to_lowercase();
1890 let end_key = end.to_lowercase();
1891 let start_idx = cols.iter().position(|n| n.to_lowercase() == start_key);
1892 let end_idx = cols.iter().position(|n| n.to_lowercase() == end_key);
1893 if let (Some(mut si), Some(mut ei)) = (start_idx, end_idx) {
1894 if si > ei {
1895 std::mem::swap(&mut si, &mut ei);
1896 }
1897 let h = table.data_height();
1898 let w = ei - si + 1;
1899 let mut rows = vec![vec![LiteralValue::Empty; w]; h];
1900 for (offset, ci) in (si..=ei).enumerate() {
1901 let cname = &cols[ci];
1902 let col_range = table.get_column(cname)?;
1903 let (rh, _) = col_range.dimensions();
1904 for (r, row) in rows.iter_mut().enumerate().take(h.min(rh)) {
1905 row[offset] = col_range.get(r, 0)?;
1906 }
1907 }
1908 rows
1909 } else {
1910 return Err(ExcelError::new(ExcelErrorKind::Ref)
1911 .with_message("Column range refers to unknown column(s)".to_string()));
1912 }
1913 }
1914 Some(TableSpecifier::SpecialItem(SpecialItem::Headers))
1915 | Some(TableSpecifier::Headers) => table
1916 .headers_row()
1917 .map(|r| r.materialise().into_owned())
1918 .unwrap_or_default(),
1919 Some(TableSpecifier::SpecialItem(SpecialItem::Totals))
1920 | Some(TableSpecifier::Totals) => table
1921 .totals_row()
1922 .map(|r| r.materialise().into_owned())
1923 .unwrap_or_default(),
1924 Some(TableSpecifier::SpecialItem(SpecialItem::Data)) | Some(TableSpecifier::Data) => {
1925 table
1926 .data_body()
1927 .map(|r| r.materialise().into_owned())
1928 .unwrap_or_default()
1929 }
1930 Some(TableSpecifier::SpecialItem(SpecialItem::All)) | Some(TableSpecifier::All) => {
1931 let mut out: Vec<Vec<LiteralValue>> = Vec::new();
1932 if let Some(h) = table.headers_row() {
1933 out.extend(h.iter_rows());
1934 }
1935 if let Some(body) = table.data_body() {
1936 out.extend(body.iter_rows());
1937 }
1938 if let Some(tr) = table.totals_row() {
1939 out.extend(tr.iter_rows());
1940 }
1941 out
1942 }
1943 Some(TableSpecifier::SpecialItem(SpecialItem::ThisRow)) => {
1944 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
1945 "@ (This Row) requires table-aware context; not yet supported".to_string(),
1946 ));
1947 }
1948 Some(TableSpecifier::Row(_)) | Some(TableSpecifier::Combination(_)) => {
1949 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1950 .with_message("Complex structured references not yet supported".to_string()));
1951 }
1952 None => {
1953 return Err(ExcelError::new(ExcelErrorKind::NImpl)
1954 .with_message("Table reference without specifier is unsupported".to_string()));
1955 }
1956 };
1957
1958 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
1959 }
1960
1961 pub fn default_sheet_id(&self) -> SheetId {
1962 self.graph.default_sheet_id()
1963 }
1964
1965 pub fn default_sheet_name(&self) -> &str {
1966 self.graph.default_sheet_name()
1967 }
1968
1969 pub fn set_workbook_seed(&mut self, seed: u64) {
1971 self.config.workbook_seed = seed;
1972 }
1973
1974 pub fn set_volatile_level(&mut self, level: crate::traits::VolatileLevel) {
1976 self.config.volatile_level = level;
1977 }
1978
1979 pub fn set_deterministic_mode(
1981 &mut self,
1982 mode: crate::engine::DeterministicMode,
1983 ) -> Result<(), ExcelError> {
1984 let clock = mode.build_clock()?;
1985 self.config.deterministic_mode = mode;
1986 self.clock = crate::timezone::SnapshotClock::new(clock);
1987 Ok(())
1988 }
1989
1990 pub fn set_clock(&mut self, clock: Arc<dyn crate::timezone::ClockProvider>) {
1998 self.clock = crate::timezone::SnapshotClock::new(clock);
1999 }
2000
2001 fn validate_deterministic_mode(&self) -> Result<(), ExcelError> {
2002 self.config.deterministic_mode.validate()
2003 }
2004
2005 pub fn sheet_id(&self, name: &str) -> Option<SheetId> {
2006 self.graph.sheet_id(name)
2007 }
2008
2009 pub fn sheet_id_mut(&mut self, name: &str) -> SheetId {
2010 self.add_sheet(name)
2011 .unwrap_or_else(|_| self.graph.sheet_id_mut(name))
2012 }
2013
2014 pub fn sheet_name(&self, id: SheetId) -> &str {
2015 self.graph.sheet_name(id)
2016 }
2017
2018 pub fn add_sheet(&mut self, name: &str) -> Result<SheetId, ExcelError> {
2019 let id = self.graph.add_sheet(name)?;
2020 self.ensure_arrow_sheet(name);
2021 self.mark_topology_edited();
2026 Ok(id)
2027 }
2028
2029 pub fn duplicate_sheet(&mut self, source: &str, new_name: &str) -> Result<SheetId, ExcelError> {
2030 let source_id = self.graph.sheet_id(source).ok_or_else(|| {
2031 ExcelError::new(ExcelErrorKind::Value).with_message("Source sheet does not exist")
2032 })?;
2033 self.demote_spans_preserving_computed_overlays(source_id, Region::whole_sheet(source_id))
2036 .map_err(Self::editor_error_to_excel)?;
2037 let new_id = self.graph.duplicate_sheet(source_id, new_name)?;
2038
2039 if let Some(source_sheet) = self.arrow_sheets.sheet(source).cloned() {
2040 let mut copied_sheet = source_sheet;
2041 copied_sheet.name = Arc::<str>::from(new_name);
2042 self.arrow_sheets.sheets.push(copied_sheet);
2043 } else {
2044 self.ensure_arrow_sheet(new_name);
2045 }
2046
2047 self.clear_all_computed_overlays();
2048 self.mark_all_formula_vertices_dirty();
2049 self.mark_topology_edited();
2050 Ok(new_id)
2051 }
2052
2053 fn ensure_arrow_sheet(&mut self, name: &str) {
2054 if self.arrow_sheets.sheet(name).is_some() {
2055 return;
2056 }
2057 self.arrow_sheets
2058 .sheets
2059 .push(crate::arrow_store::ArrowSheet {
2060 name: std::sync::Arc::<str>::from(name),
2061 columns: Vec::new(),
2062 nrows: 0,
2063 chunk_starts: Vec::new(),
2064 chunk_rows: 32 * 1024,
2065 });
2066 }
2067
2068 pub fn remove_sheet(&mut self, sheet_id: SheetId) -> Result<(), ExcelError> {
2069 let name = self.graph.sheet_name(sheet_id).to_string();
2070 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
2074 .map_err(Self::editor_error_to_excel)?;
2075 self.graph.remove_sheet(sheet_id)?;
2076 self.arrow_sheets.sheets.retain(|s| s.name.as_ref() != name);
2077 self.clear_all_computed_overlays();
2078 self.mark_all_formula_vertices_dirty();
2079 self.staged_formulas.remove(&name);
2080 if self.row_visibility.remove(&sheet_id).is_some() {
2081 self.invalidate_row_visibility_mask_cache();
2082 }
2083 self.record_formula_plane_structural_change(StructuralScope::RemovedSheet(sheet_id));
2084 self.mark_topology_edited();
2085 Ok(())
2086 }
2087
2088 fn rename_sheet_in_arrow_store(&mut self, target_name: &str, new_name: &str) -> bool {
2090 if let Some(asheet) = self
2091 .arrow_sheets
2092 .sheets
2093 .iter_mut()
2094 .find(|s| s.name.as_ref() == target_name)
2095 {
2096 asheet.name = std::sync::Arc::<str>::from(new_name);
2097 return true;
2098 }
2099 false
2100 }
2101
2102 pub fn rename_sheet(&mut self, sheet_id: SheetId, new_name: &str) -> Result<(), ExcelError> {
2103 let old_name = self.graph.sheet_name(sheet_id).to_string();
2104
2105 self.rename_sheet_in_arrow_store(&old_name, new_name);
2108
2109 match self.graph.rename_sheet(sheet_id, new_name) {
2111 Ok(_) => {
2112 self.rename_staged_formula_sheet(&old_name, new_name);
2113 let sheet_vertices: Vec<VertexId> =
2115 self.graph.vertices_in_sheet(sheet_id).collect();
2116 for v_id in sheet_vertices {
2117 self.graph.mark_vertex_dirty(v_id);
2118 }
2119 self.mark_topology_edited();
2123 Ok(())
2124 }
2125 Err(e) => {
2126 self.rename_sheet_in_arrow_store(new_name, &old_name);
2128 Err(e)
2129 }
2130 }
2131 }
2132
2133 pub fn named_ranges_iter(
2134 &self,
2135 ) -> impl Iterator<Item = (&String, &crate::engine::named_range::NamedRange)> {
2136 self.graph.named_ranges_iter()
2137 }
2138
2139 pub fn sheet_named_ranges_iter(
2140 &self,
2141 ) -> impl Iterator<Item = (&(SheetId, String), &crate::engine::named_range::NamedRange)> {
2142 self.graph.sheet_named_ranges_iter()
2143 }
2144
2145 pub fn resolve_name_entry(
2146 &self,
2147 name: &str,
2148 current_sheet: SheetId,
2149 ) -> Option<&crate::engine::named_range::NamedRange> {
2150 self.graph.resolve_name_entry(name, current_sheet)
2151 }
2152
2153 pub fn named_ranges_snapshot(&self) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
2154 let mut out: Vec<crate::engine::named_range::NamedRangeSnapshot> = Vec::new();
2155
2156 for (name, named) in self.graph.named_ranges_iter() {
2157 out.push(crate::engine::named_range::NamedRangeSnapshot {
2158 name: name.clone(),
2159 scope: NameScope::Workbook,
2160 definition: named.definition.clone(),
2161 });
2162 }
2163
2164 for ((sheet_id, name), named) in self.graph.sheet_named_ranges_iter() {
2165 out.push(crate::engine::named_range::NamedRangeSnapshot {
2166 name: name.clone(),
2167 scope: NameScope::Sheet(*sheet_id),
2168 definition: named.definition.clone(),
2169 });
2170 }
2171
2172 out.sort_by(|a, b| {
2173 let a_scope = match a.scope {
2174 NameScope::Workbook => (0u8, 0u32),
2175 NameScope::Sheet(id) => (1u8, u32::from(id)),
2176 };
2177 let b_scope = match b.scope {
2178 NameScope::Workbook => (0u8, 0u32),
2179 NameScope::Sheet(id) => (1u8, u32::from(id)),
2180 };
2181 a_scope.cmp(&b_scope).then_with(|| a.name.cmp(&b.name))
2182 });
2183
2184 out
2185 }
2186
2187 pub fn named_ranges_snapshot_for_sheet(
2188 &self,
2189 sheet_id: SheetId,
2190 ) -> Vec<crate::engine::named_range::NamedRangeSnapshot> {
2191 self.named_ranges_snapshot()
2192 .into_iter()
2193 .filter(|entry| match entry.scope {
2194 NameScope::Workbook => true,
2195 NameScope::Sheet(id) => id == sheet_id,
2196 })
2197 .collect()
2198 }
2199
2200 pub fn define_name(
2201 &mut self,
2202 name: &str,
2203 definition: NamedDefinition,
2204 scope: NameScope,
2205 ) -> Result<(), ExcelError> {
2206 self.invalidate_formula_plane_spans_for_name(name)?;
2212 self.graph.define_name(name, definition, scope)?;
2213 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2214 self.mark_topology_edited();
2215 Ok(())
2216 }
2217
2218 pub fn update_name(
2219 &mut self,
2220 name: &str,
2221 definition: NamedDefinition,
2222 scope: NameScope,
2223 ) -> Result<(), ExcelError> {
2224 self.invalidate_formula_plane_spans_for_name(name)?;
2230 self.graph.update_name(name, definition, scope)?;
2231 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2232 self.mark_topology_edited();
2233 Ok(())
2234 }
2235
2236 pub fn delete_name(&mut self, name: &str, scope: NameScope) -> Result<(), ExcelError> {
2237 self.invalidate_formula_plane_spans_for_name(name)?;
2241 self.graph.delete_name(name, scope)?;
2242 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2243 self.mark_topology_edited();
2244 Ok(())
2245 }
2246
2247 fn invalidate_formula_plane_spans_for_name(&mut self, name: &str) -> Result<(), ExcelError> {
2254 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
2255 return Ok(());
2256 }
2257 let regions: Vec<Region> = {
2258 let authority = self.graph.formula_authority();
2259 authority
2260 .plane
2261 .name_dependent_span_refs(name)
2262 .into_iter()
2263 .filter_map(|span_ref| authority.plane.spans.get(span_ref))
2264 .map(|span| Region::from_domain(span.result_region.domain()))
2265 .collect()
2266 };
2267 for region in regions {
2268 self.demote_spans_preserving_computed_overlays(region.sheet_id(), region)
2269 .map_err(Self::editor_error_to_excel)?;
2270 }
2271 Ok(())
2272 }
2273
2274 pub fn define_table(
2275 &mut self,
2276 name: &str,
2277 range: crate::reference::RangeRef,
2278 header_row: bool,
2279 headers: Vec<String>,
2280 totals_row: bool,
2281 ) -> Result<(), ExcelError> {
2282 self.graph
2283 .define_table(name, range, header_row, headers, totals_row)?;
2284 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2285 self.mark_topology_edited();
2286 Ok(())
2287 }
2288
2289 pub fn define_source_scalar(
2290 &mut self,
2291 name: &str,
2292 version: Option<u64>,
2293 ) -> Result<(), ExcelError> {
2294 self.graph.define_source_scalar(name, version)?;
2295 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2296 self.mark_topology_edited();
2297 Ok(())
2298 }
2299
2300 pub fn define_source_table(
2301 &mut self,
2302 name: &str,
2303 version: Option<u64>,
2304 ) -> Result<(), ExcelError> {
2305 self.graph.define_source_table(name, version)?;
2306 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2307 self.mark_topology_edited();
2308 Ok(())
2309 }
2310
2311 pub fn set_source_scalar_version(
2312 &mut self,
2313 name: &str,
2314 version: Option<u64>,
2315 ) -> Result<(), ExcelError> {
2316 self.graph.set_source_scalar_version(name, version)?;
2317 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2318 Ok(())
2319 }
2320
2321 pub fn set_source_table_version(
2322 &mut self,
2323 name: &str,
2324 version: Option<u64>,
2325 ) -> Result<(), ExcelError> {
2326 self.graph.set_source_table_version(name, version)?;
2327 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2328 Ok(())
2329 }
2330
2331 pub fn invalidate_source(&mut self, name: &str) -> Result<(), ExcelError> {
2332 self.graph.invalidate_source(name)?;
2333 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
2334 Ok(())
2335 }
2336
2337 pub fn vertex_value(&self, vertex: VertexId) -> Option<LiteralValue> {
2338 self.graph.get_value(vertex)
2339 }
2340
2341 pub fn graph_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
2342 self.graph.get_cell_value(sheet, row, col)
2343 }
2344
2345 pub fn vertex_for_cell(&self, cell: &CellRef) -> Option<VertexId> {
2346 self.graph.get_vertex_for_cell(cell)
2347 }
2348
2349 pub fn evaluation_vertices(&self) -> Vec<VertexId> {
2350 self.graph.get_evaluation_vertices()
2351 }
2352
2353 pub fn baseline_stats(&self) -> EngineBaselineStats {
2355 let graph = self.graph.baseline_stats();
2356 let formula_authority = self.graph.formula_authority();
2357 EngineBaselineStats {
2358 graph_vertex_count: graph.graph_vertex_count,
2359 graph_formula_vertex_count: graph.graph_formula_vertex_count,
2360 graph_edge_count: graph.graph_edge_count,
2361 dirty_vertex_count: graph.dirty_vertex_count,
2362 evaluation_vertex_count: graph.evaluation_vertex_count,
2363 formula_ast_root_count: graph.formula_ast_root_count,
2364 formula_ast_node_count: graph.formula_ast_node_count,
2365 staged_formula_count: self.staged_formula_count(),
2366 formula_plane_active_span_count: formula_authority.active_span_count(),
2367 formula_plane_producer_result_entries: formula_authority.producer_results.len(),
2368 formula_plane_consumer_read_entries: formula_authority.consumer_reads.len(),
2369 formula_plane_cycle_member_span_demotions: self
2370 .formula_plane_cycle_member_span_demotions,
2371 }
2372 }
2373
2374 #[cfg(test)]
2375 pub(crate) fn used_axis_bounds_cache_stats(&self) -> (usize, usize, usize, usize) {
2376 self.used_axis_bounds_cache
2377 .read()
2378 .ok()
2379 .and_then(|guard| {
2380 guard.as_ref().map(|cache| {
2381 (
2382 cache.row_hits.load(Ordering::Relaxed),
2383 cache.row_misses.load(Ordering::Relaxed),
2384 cache.col_hits.load(Ordering::Relaxed),
2385 cache.col_misses.load(Ordering::Relaxed),
2386 )
2387 })
2388 })
2389 .unwrap_or((0, 0, 0, 0))
2390 }
2391
2392 pub fn set_first_load_assume_new(&mut self, enabled: bool) {
2393 self.graph.set_first_load_assume_new(enabled);
2394 }
2395
2396 pub fn reset_ensure_touched(&mut self) {
2397 self.graph.reset_ensure_touched();
2398 }
2399
2400 pub fn finalize_sheet_index(&mut self, sheet: &str) {
2401 self.graph.finalize_sheet_index(sheet);
2402 }
2403
2404 pub fn action<T>(
2413 &mut self,
2414 name: impl AsRef<str>,
2415 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2416 ) -> Result<T, crate::engine::EditorError> {
2417 if self.action_depth != 0 {
2418 return Err(crate::engine::EditorError::TransactionFailed {
2419 reason: "Nested Engine::action calls are not supported (ticket 614: commit-only surface)"
2420 .to_string(),
2421 });
2422 }
2423
2424 self.action_depth = 1;
2425 let engine_ptr: *mut Engine<R> = self;
2426 let _guard = ActionDepthGuard {
2427 engine: engine_ptr,
2428 _marker: std::marker::PhantomData,
2429 };
2430
2431 let mut tx = EngineAction {
2432 engine: self,
2433 name: name.as_ref().to_string(),
2434 log: None,
2435 arrow_undo: None,
2436 atomic_policy: false,
2437 };
2438 f(&mut tx)
2439 }
2440
2441 pub fn action_atomic<T>(
2445 &mut self,
2446 name: impl Into<String>,
2447 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2448 ) -> Result<T, crate::engine::EditorError> {
2449 let (v, _j) = self.action_atomic_journal(name, f)?;
2450 Ok(v)
2451 }
2452
2453 pub fn action_atomic_journal<T>(
2455 &mut self,
2456 name: impl Into<String>,
2457 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2458 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2459 if self.action_depth != 0 {
2460 return Err(crate::engine::EditorError::TransactionFailed {
2461 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2462 .to_string(),
2463 });
2464 }
2465
2466 self.action_depth = 1;
2467 let engine_ptr: *mut Engine<R> = self;
2468 let _guard = ActionDepthGuard {
2469 engine: engine_ptr,
2470 _marker: std::marker::PhantomData,
2471 };
2472
2473 let name_str = name.into();
2474 let mut log = crate::engine::ChangeLog::new();
2475 let start_len = log.len();
2476 self.action_atomic_impl(&mut log, start_len, name_str, f)
2477 }
2478
2479 fn action_atomic_impl<T>(
2480 &mut self,
2481 log: &mut crate::engine::ChangeLog,
2482 start_len: usize,
2483 name: String,
2484 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2485 ) -> Result<(T, crate::engine::ActionJournal), crate::engine::EditorError> {
2486 let mut arrow_undo = crate::engine::ArrowUndoBatch::default();
2487 let arrow_ptr: *mut crate::engine::ArrowUndoBatch = &mut arrow_undo;
2488
2489 let log_ptr: *mut crate::engine::ChangeLog = log;
2490 let mut tx = EngineAction {
2491 engine: self,
2492 name: name.clone(),
2493 log: Some(log_ptr),
2494 arrow_undo: Some(arrow_ptr),
2495 atomic_policy: true,
2496 };
2497
2498 let res = f(&mut tx);
2499
2500 let graph_events: Vec<crate::engine::ChangeEvent> =
2502 unsafe { (&*log_ptr).events() }[start_len..].to_vec();
2503 let graph_batch = crate::engine::GraphUndoBatch {
2504 events: graph_events,
2505 };
2506 let affected_cells = arrow_undo.ops.len();
2507 let journal = crate::engine::ActionJournal {
2508 name,
2509 graph: graph_batch,
2510 arrow: arrow_undo,
2511 affected_cells,
2512 };
2513
2514 match res {
2515 Ok(v) => {
2516 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2517 for event in &journal.graph.events {
2518 self.record_formula_plane_change_for_event(event);
2519 }
2520 self.mark_data_edited();
2521 }
2522 Ok((v, journal))
2523 }
2524 Err(e) => {
2525 if let Err(rb) = self.rollback_from_action_journal(&journal) {
2526 return Err(crate::engine::EditorError::TransactionFailed {
2527 reason: format!(
2528 "Engine::action_atomic rollback failed after error '{e}': {rb}"
2529 ),
2530 });
2531 }
2532 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2533 for event in &journal.graph.events {
2534 self.record_formula_plane_change_for_event(event);
2535 }
2536 }
2537 Err(e)
2538 }
2539 }
2540 }
2541
2542 pub fn action_with_logger<T>(
2549 &mut self,
2550 log: &mut crate::engine::ChangeLog,
2551 name: impl AsRef<str>,
2552 f: impl FnOnce(&mut EngineAction<'_, R>) -> Result<T, crate::engine::EditorError>,
2553 ) -> Result<T, crate::engine::EditorError> {
2554 if self.action_depth != 0 {
2555 return Err(crate::engine::EditorError::TransactionFailed {
2556 reason: "Nested Engine::action calls are not supported (deterministic rule)"
2557 .to_string(),
2558 });
2559 }
2560
2561 self.action_depth = 1;
2562 let engine_ptr: *mut Engine<R> = self;
2563 let _guard = ActionDepthGuard {
2564 engine: engine_ptr,
2565 _marker: std::marker::PhantomData,
2566 };
2567
2568 let start_len = log.len();
2569 let name_str = name.as_ref().to_string();
2570 log.begin_compound(name_str.clone());
2571
2572 let res = self.action_atomic_impl(log, start_len, name_str, f);
2575
2576 match res {
2577 Ok((v, _journal)) => {
2578 log.end_compound();
2579 Ok(v)
2580 }
2581 Err(e) => {
2582 log.end_compound();
2584 log.truncate(start_len);
2585 Err(e)
2586 }
2587 }
2588 }
2589
2590 fn rollback_from_action_journal(
2591 &mut self,
2592 journal: &crate::engine::ActionJournal,
2593 ) -> Result<(), crate::engine::EditorError> {
2594 journal.graph.undo(&mut self.graph)?;
2596 self.apply_inverse_row_visibility_events(&journal.graph.events);
2598 self.apply_arrow_undo_batch(&journal.arrow, true);
2600 Ok(())
2601 }
2602
2603 fn rollback_from_change_events(
2604 &mut self,
2605 events: &[crate::engine::ChangeEvent],
2606 ) -> Result<(), crate::engine::EditorError> {
2607 use crate::engine::ChangeEvent;
2608
2609 {
2611 let mut editor = crate::engine::VertexEditor::new(&mut self.graph);
2612 let mut compound_stack: Vec<usize> = Vec::new();
2613 for ev in events.iter().rev() {
2614 match ev {
2615 ChangeEvent::CompoundEnd { depth } => compound_stack.push(*depth),
2616 ChangeEvent::CompoundStart { depth, .. } => {
2617 if compound_stack.last() == Some(depth) {
2618 compound_stack.pop();
2619 }
2620 }
2621 ChangeEvent::SetRowVisibility { .. } => {
2622 }
2624 _ => {
2625 editor.apply_inverse(ev.clone())?;
2626 }
2627 }
2628 }
2629 }
2630
2631 for ev in events.iter().rev() {
2633 self.apply_inverse_row_visibility_event(ev);
2634 }
2635
2636 for ev in events.iter().rev() {
2638 self.mirror_inverse_change_to_arrow(ev);
2639 }
2640
2641 Ok(())
2642 }
2643
2644 fn read_cell_formula_ast(&self, sheet: &str, row: u32, col: u32) -> Option<ASTNode> {
2645 let sheet_id = self.graph.sheet_id(sheet)?;
2646 let coord = Coord::from_excel(row, col, true, true);
2647 let cell = CellRef::new(sheet_id, coord);
2648 let vid = self.graph.get_vertex_for_cell(&cell)?;
2649 let ast_id = self.graph.get_formula_id(vid)?;
2650 self.graph
2651 .data_store()
2652 .retrieve_ast(ast_id, self.graph.sheet_reg())
2653 }
2654
2655 pub fn edit_with_logger<T>(
2656 &mut self,
2657 log: &mut crate::engine::ChangeLog,
2658 f: impl FnOnce(&mut crate::engine::VertexEditor) -> T,
2659 ) -> T {
2660 let start_len = log.len();
2662
2663 struct ArrowSpillReader<'a> {
2666 sheets: &'a crate::arrow_store::SheetStore,
2667 }
2668 impl crate::engine::graph::editor::vertex_editor::SpillValueReader for ArrowSpillReader<'_> {
2669 fn read_cell_value(
2670 &self,
2671 sheet: &str,
2672 row: u32,
2673 col: u32,
2674 ) -> Option<formualizer_common::LiteralValue> {
2675 use formualizer_common::LiteralValue;
2676 let asheet = self.sheets.sheet(sheet)?;
2677 let r0 = row.saturating_sub(1) as usize;
2678 let c0 = col.saturating_sub(1) as usize;
2679 let v = asheet.get_cell_value(r0, c0);
2680 if matches!(v, LiteralValue::Empty) {
2681 None
2682 } else {
2683 Some(v)
2684 }
2685 }
2686 }
2687
2688 let ret = {
2689 let spill_reader = ArrowSpillReader {
2690 sheets: &self.arrow_sheets,
2691 };
2692 let mut editor = crate::engine::VertexEditor::with_logger_and_spill_reader(
2693 &mut self.graph,
2694 log,
2695 &spill_reader,
2696 );
2697 f(&mut editor)
2698 };
2699
2700 for ev in &log.events()[start_len..] {
2703 self.mirror_forward_change_to_arrow(ev);
2704 }
2705 for ev in &log.events()[start_len..] {
2706 self.record_formula_plane_change_for_event(ev);
2707 }
2708
2709 ret
2710 }
2711
2712 pub fn undo_logged(
2713 &mut self,
2714 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2715 log: &mut crate::engine::ChangeLog,
2716 ) -> Result<(), crate::engine::EditorError> {
2717 let batch = undo.undo(&mut self.graph, log)?;
2718 for item in batch.iter().rev() {
2719 self.apply_inverse_row_visibility_event(&item.event);
2720 self.apply_inverse_staged_formula_event(&item.event);
2721 }
2722 self.mirror_undo_batch_to_arrow(&batch);
2723 if !batch.is_empty() {
2724 for item in &batch {
2725 self.record_formula_plane_change_for_event(&item.event);
2726 }
2727 }
2728 Ok(())
2729 }
2730
2731 pub fn redo_logged(
2732 &mut self,
2733 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2734 log: &mut crate::engine::ChangeLog,
2735 ) -> Result<(), crate::engine::EditorError> {
2736 let batch = undo.redo(&mut self.graph, log)?;
2737 for item in &batch {
2738 self.apply_forward_row_visibility_event(&item.event);
2739 self.apply_forward_staged_formula_event(&item.event);
2740 }
2741 self.mirror_redo_batch_to_arrow(&batch);
2742 if !batch.is_empty() {
2743 for item in &batch {
2744 self.record_formula_plane_change_for_event(&item.event);
2745 }
2746 }
2747 Ok(())
2748 }
2749
2750 pub fn undo_action(
2754 &mut self,
2755 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2756 ) -> Result<(), crate::engine::EditorError> {
2757 let Some(journal) = undo.pop_undo_action() else {
2758 return Ok(());
2759 };
2760
2761 journal.graph.undo(&mut self.graph)?;
2762 self.apply_inverse_row_visibility_events(&journal.graph.events);
2763 self.apply_arrow_undo_batch(&journal.arrow, true);
2764 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2765 for event in &journal.graph.events {
2766 self.record_formula_plane_change_for_event(event);
2767 }
2768 self.mark_data_edited();
2769 }
2770
2771 undo.push_redo_action(journal);
2772 Ok(())
2773 }
2774
2775 pub fn redo_action(
2779 &mut self,
2780 undo: &mut crate::engine::graph::editor::undo_engine::UndoEngine,
2781 ) -> Result<(), crate::engine::EditorError> {
2782 let Some(journal) = undo.pop_redo_action() else {
2783 return Ok(());
2784 };
2785
2786 journal.graph.redo(&mut self.graph)?;
2787 self.apply_forward_row_visibility_events(&journal.graph.events);
2788 self.apply_arrow_undo_batch(&journal.arrow, false);
2789 if !journal.graph.is_empty() || !journal.arrow.is_empty() {
2790 for event in &journal.graph.events {
2791 self.record_formula_plane_change_for_event(event);
2792 }
2793 self.mark_data_edited();
2794 }
2795
2796 undo.push_done_action(journal);
2797 Ok(())
2798 }
2799
2800 fn cellref_to_sheet_row_col(&self, addr: &crate::reference::CellRef) -> (String, u32, u32) {
2801 let sheet = self.graph.sheet_name(addr.sheet_id).to_string();
2802 let row = addr.coord.row() + 1;
2804 let col = addr.coord.col() + 1;
2805 (sheet, row, col)
2806 }
2807
2808 fn mirror_undo_batch_to_arrow(
2809 &mut self,
2810 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2811 ) {
2812 for item in batch.iter().rev() {
2814 self.mirror_inverse_change_to_arrow(&item.event);
2815 }
2816 }
2817
2818 fn mirror_redo_batch_to_arrow(
2819 &mut self,
2820 batch: &[crate::engine::graph::editor::undo_engine::UndoBatchItem],
2821 ) {
2822 for item in batch.iter() {
2824 self.mirror_forward_change_to_arrow(&item.event);
2825 }
2826 }
2827
2828 fn mirror_inverse_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2829 use crate::engine::ChangeEvent;
2830 use formualizer_common::LiteralValue;
2831
2832 match ev {
2833 ChangeEvent::SetValue {
2834 addr,
2835 old_value,
2836 old_formula,
2837 ..
2838 } => {
2839 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2840 if old_formula.is_some() {
2841 self.clear_delta_overlay_cell(&sheet, row, col);
2842 } else {
2843 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2844 self.mirror_value_to_overlay(&sheet, row, col, &v);
2845 }
2846 }
2847 ChangeEvent::SetFormula {
2848 addr,
2849 old_value,
2850 old_formula,
2851 ..
2852 } => {
2853 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2854 if old_formula.is_some() {
2855 self.clear_delta_overlay_cell(&sheet, row, col);
2856 } else {
2857 let v = old_value.clone().unwrap_or(LiteralValue::Empty);
2858 self.mirror_value_to_overlay(&sheet, row, col, &v);
2859 }
2860 }
2861 ChangeEvent::SpillCommitted { old, new, .. } => {
2862 self.mirror_spill_snapshot(new, true);
2864 if let Some(snap) = old {
2865 self.mirror_spill_snapshot(snap, false);
2866 }
2867 }
2868 ChangeEvent::SpillCleared { old, .. } => {
2869 self.mirror_spill_snapshot(old, false);
2871 }
2872 ChangeEvent::SetRowVisibility { .. } => {
2873 }
2875 _ => {}
2876 }
2877 }
2878
2879 fn mirror_forward_change_to_arrow(&mut self, ev: &crate::engine::ChangeEvent) {
2880 use crate::engine::ChangeEvent;
2881
2882 match ev {
2883 ChangeEvent::SetValue { addr, new, .. } => {
2884 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2885 self.mirror_value_to_overlay(&sheet, row, col, new);
2886 }
2887 ChangeEvent::SetFormula { addr, .. } => {
2888 let (sheet, row, col) = self.cellref_to_sheet_row_col(addr);
2889 self.clear_delta_overlay_cell(&sheet, row, col);
2890 }
2892 ChangeEvent::SpillCommitted { old, new, .. } => {
2893 if let Some(snap) = old {
2894 self.mirror_spill_snapshot(snap, true);
2895 }
2896 self.mirror_spill_snapshot(new, false);
2897 }
2898 ChangeEvent::SpillCleared { old, .. } => {
2899 self.mirror_spill_snapshot(old, true);
2900 }
2901 ChangeEvent::SetRowVisibility { .. } => {
2902 }
2904 _ => {
2905 }
2907 }
2908 }
2909
2910 fn mirror_spill_snapshot(
2911 &mut self,
2912 snap: &crate::engine::graph::editor::change_log::SpillSnapshot,
2913 clear_only: bool,
2914 ) {
2915 use formualizer_common::LiteralValue;
2916
2917 let mut i = 0usize;
2918 for row in &snap.values {
2919 for v in row {
2920 if let Some(cell) = snap.target_cells.get(i) {
2921 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2922 let out = if clear_only {
2923 LiteralValue::Empty
2924 } else {
2925 v.clone()
2926 };
2927 self.mirror_value_to_computed_overlay(&sheet, r, c, &out);
2928 }
2929 i += 1;
2930 }
2931 }
2932 if clear_only {
2934 for cell in snap.target_cells.iter().skip(i) {
2935 let (sheet, r, c) = self.cellref_to_sheet_row_col(cell);
2936 self.mirror_value_to_computed_overlay(&sheet, r, c, &LiteralValue::Empty);
2937 }
2938 }
2939 }
2940
2941 pub fn set_default_sheet_by_name(&mut self, name: &str) {
2942 self.graph.set_default_sheet_by_name(name);
2943 }
2944
2945 pub fn set_default_sheet_by_id(&mut self, id: SheetId) {
2946 self.graph.set_default_sheet_by_id(id);
2947 }
2948
2949 pub fn set_sheet_index_mode(&mut self, mode: crate::engine::SheetIndexMode) {
2950 self.graph.set_sheet_index_mode(mode);
2951 }
2952
2953 fn clear_cached_static_schedule(&mut self) {
2954 self.cached_static_schedule = None;
2955 }
2956
2957 pub fn mark_data_edited(&mut self) {
2960 self.snapshot_id
2961 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2962 self.has_edited = true;
2963 }
2964
2965 pub fn mark_topology_edited(&mut self) {
2967 self.snapshot_id
2968 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
2969 self.topology_epoch = self.topology_epoch.wrapping_add(1);
2970 self.clear_cached_static_schedule();
2971 self.has_edited = true;
2972 }
2973
2974 fn mark_all_formula_vertices_dirty(&mut self) {
2975 let vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
2976 for vertex in vertices {
2977 self.graph.mark_vertex_dirty(vertex);
2978 }
2979 }
2980
2981 fn mark_moved_formula_vertices_dirty(
2982 &mut self,
2983 summary: &crate::engine::graph::editor::vertex_editor::ShiftSummary,
2984 ) {
2985 for vertex in &summary.vertices_moved {
2986 if self.graph.get_formula_id(*vertex).is_some() {
2987 self.graph.mark_vertex_dirty(*vertex);
2988 }
2989 }
2990 }
2991
2992 pub fn sheet_store(&self) -> &SheetStore {
2994 &self.arrow_sheets
2995 }
2996
2997 pub fn sheet_store_mut(&mut self) -> &mut SheetStore {
2999 &mut self.arrow_sheets
3000 }
3001
3002 pub fn has_staged_formulas(&self) -> bool {
3003 !self.staged_formulas.is_empty()
3004 }
3005
3006 pub fn staged_formula_count(&self) -> usize {
3007 self.staged_formulas.values().map(StagedSheet::len).sum()
3008 }
3009
3010 pub fn stage_formula_text(&mut self, sheet: &str, row: u32, col: u32, text: String) {
3012 self.staged_formulas
3013 .entry(sheet.to_string())
3014 .or_default()
3015 .stage(row, col, text);
3016 }
3017
3018 pub fn clear_staged_formula_text(&mut self, sheet: &str, row: u32, col: u32) -> Option<String> {
3019 let mut removed = None;
3020 let mut remove_sheet = false;
3021 if let Some(entries) = self.staged_formulas.get_mut(sheet) {
3022 removed = entries.remove(row, col);
3023 remove_sheet = entries.is_empty();
3024 }
3025 if remove_sheet {
3026 self.staged_formulas.remove(sheet);
3027 }
3028 removed
3029 }
3030
3031 pub fn clear_staged_formulas_for_sheet(&mut self, sheet: &str) {
3032 self.staged_formulas.remove(sheet);
3033 }
3034
3035 pub fn rename_staged_formula_sheet(&mut self, old: &str, new: &str) {
3036 let Some(entries) = self.staged_formulas.remove(old) else {
3037 return;
3038 };
3039 for (row, col, text) in entries.into_entries() {
3040 self.stage_formula_text(new, row, col, text);
3041 }
3042 }
3043
3044 pub fn get_staged_formula_text(&self, sheet: &str, row: u32, col: u32) -> Option<String> {
3046 self.staged_formulas
3047 .get(sheet)
3048 .and_then(|v| v.get(row, col).map(str::to_owned))
3049 }
3050
3051 pub fn formula_parse_diagnostics(&self) -> &[FormulaParseDiagnostic] {
3052 &self.formula_parse_diagnostics
3053 }
3054
3055 pub fn take_formula_parse_diagnostics(&mut self) -> Vec<FormulaParseDiagnostic> {
3056 std::mem::take(&mut self.formula_parse_diagnostics)
3057 }
3058
3059 pub fn clear_formula_parse_diagnostics(&mut self) {
3060 self.formula_parse_diagnostics.clear();
3061 }
3062
3063 pub fn last_formula_ingest_report(&self) -> Option<&FormulaIngestReport> {
3064 self.last_formula_ingest_report.as_ref()
3065 }
3066
3067 pub fn formula_ingest_report_total(&self) -> &FormulaIngestReport {
3068 &self.formula_ingest_report_total
3069 }
3070
3071 #[cfg(test)]
3072 pub(crate) fn last_formula_plane_span_eval_report(&self) -> Option<&SpanEvalReport> {
3073 self.last_formula_plane_span_eval_report.as_ref()
3074 }
3075
3076 #[cfg(test)]
3077 pub(crate) fn formula_plane_indexes_epoch(&self) -> u64 {
3078 self.graph.formula_authority().indexes_epoch()
3079 }
3080
3081 #[cfg(test)]
3082 pub(crate) fn formula_plane_capacity_bailouts(&self) -> u64 {
3083 self.formula_plane_capacity_bailouts
3084 }
3085
3086 fn record_formula_ingest_report(&mut self, report: FormulaIngestReport) {
3087 self.formula_ingest_report_total.mode = report.mode;
3088 self.formula_ingest_report_total.accumulate(&report);
3089 self.last_formula_ingest_report = Some(report);
3090 }
3091
3092 fn analyze_formula_plane_shadow_candidates(
3093 &mut self,
3094 batches: &[FormulaIngestBatch],
3095 ) -> FormulaIngestReport {
3096 let mut report = FormulaIngestReport::with_mode(FormulaPlaneMode::Shadow);
3097 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3098
3099 let _active_epoch = self.graph.formula_authority().plane.epoch();
3102
3103 let batch_sheet_ids: Vec<SheetId> = batches
3104 .iter()
3105 .map(|batch| self.graph.sheet_id_mut(&batch.sheet_name))
3106 .collect();
3107 let mut groups: BTreeMap<
3108 (SheetId, u64, u32),
3109 Vec<(FormulaPlacementCandidate, CandidateAnalysis)>,
3110 > = BTreeMap::new();
3111 {
3112 let mut pipeline = self.ingest_pipeline();
3113 for (batch, sheet_id) in batches.iter().zip(batch_sheet_ids.iter().copied()) {
3114 for record in &batch.formulas {
3115 if record.row == 0 || record.col == 0 {
3116 report.shadow_candidate_cells =
3117 report.shadow_candidate_cells.saturating_add(1);
3118 report.shadow_fallback_cells =
3119 report.shadow_fallback_cells.saturating_add(1);
3120 Self::record_shadow_fallback_reason(
3121 &mut report,
3122 PlacementFallbackReason::UnsupportedShapeOrGaps,
3123 1,
3124 );
3125 continue;
3126 }
3127
3128 let placement = CellRef::new(
3129 sheet_id,
3130 Coord::from_excel(record.row, record.col, true, true),
3131 );
3132 let ingested = match pipeline.ingest_formula(
3133 FormulaAstInput::RawArena(record.ast_id),
3134 placement,
3135 record.formula_text.clone(),
3136 ) {
3137 Ok(ingested) => ingested,
3138 Err(_) => {
3139 report.shadow_candidate_cells =
3140 report.shadow_candidate_cells.saturating_add(1);
3141 report.shadow_fallback_cells =
3142 report.shadow_fallback_cells.saturating_add(1);
3143 Self::record_shadow_fallback_reason(
3144 &mut report,
3145 PlacementFallbackReason::UnsupportedCanonicalTemplate,
3146 1,
3147 );
3148 continue;
3149 }
3150 };
3151 let candidate = FormulaPlacementCandidate::new(
3152 sheet_id,
3153 record.row - 1,
3154 record.col - 1,
3155 ingested.ast_id,
3156 record.formula_text.clone(),
3157 );
3158 let analysis = match CandidateAnalysis::from_ingested(&candidate, &ingested) {
3159 Ok(analysis) => analysis,
3160 Err(reason) => {
3161 report.shadow_candidate_cells =
3162 report.shadow_candidate_cells.saturating_add(1);
3163 report.shadow_fallback_cells =
3164 report.shadow_fallback_cells.saturating_add(1);
3165 Self::record_shadow_fallback_reason(&mut report, reason, 1);
3166 continue;
3167 }
3168 };
3169 groups
3170 .entry((
3171 sheet_id,
3172 ingested.parameterized_canonical_hash,
3173 candidate.col,
3174 ))
3175 .or_default()
3176 .push((candidate, analysis));
3177 }
3178 }
3179 }
3180
3181 let mut scratch_plane = FormulaPlane::default();
3182 for entries in groups.into_values() {
3183 let (candidates, analyses): (Vec<_>, Vec<_>) = entries.into_iter().unzip();
3184 for (component, component_analyses) in
3185 Self::split_candidate_components_with_analyses(candidates, analyses)
3186 {
3187 let placement_report = place_candidate_family_with_analyses(
3188 &mut scratch_plane,
3189 component,
3190 component_analyses,
3191 );
3192 let counters = placement_report.counters;
3193 report.shadow_candidate_cells = report
3194 .shadow_candidate_cells
3195 .saturating_add(counters.formula_cells_seen);
3196 report.shadow_accepted_span_cells = report
3197 .shadow_accepted_span_cells
3198 .saturating_add(counters.accepted_span_cells);
3199 report.shadow_fallback_cells = report
3200 .shadow_fallback_cells
3201 .saturating_add(counters.legacy_cells);
3202 report.shadow_templates_interned = report
3203 .shadow_templates_interned
3204 .saturating_add(counters.templates_interned);
3205 report.shadow_spans_created = report
3206 .shadow_spans_created
3207 .saturating_add(counters.spans_created);
3208 report.graph_formula_vertices_avoided_shadow = report
3209 .graph_formula_vertices_avoided_shadow
3210 .saturating_add(counters.formula_vertices_avoided);
3211 report.ast_roots_avoided_shadow = report
3212 .ast_roots_avoided_shadow
3213 .saturating_add(counters.ast_roots_avoided);
3214 report.edge_rows_avoided_shadow = report
3215 .edge_rows_avoided_shadow
3216 .saturating_add(counters.edge_rows_avoided);
3217 for (reason, count) in counters.fallback_reasons {
3218 Self::record_shadow_fallback_reason(&mut report, reason, count);
3219 }
3220 }
3221 }
3222 report
3223 }
3224
3225 fn record_shadow_fallback_reason(
3226 report: &mut FormulaIngestReport,
3227 reason: PlacementFallbackReason,
3228 count: u64,
3229 ) {
3230 *report
3231 .fallback_reasons
3232 .entry(format!("{reason:?}"))
3233 .or_default() += count;
3234 }
3235
3236 fn analyze_formula_plane_authoritative_ingest(
3237 &mut self,
3238 batches: &[FormulaIngestBatch],
3239 ) -> (
3240 FormulaIngestReport,
3241 Vec<FormulaIngestBatch>,
3242 PlannedFormulaMaterialize,
3243 ) {
3244 let mut report =
3245 FormulaIngestReport::with_mode(FormulaPlaneMode::AuthoritativeExperimental);
3246 report.formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3247
3248 let mut pending_candidates: Vec<(String, FormulaPlacementCandidate)> = Vec::new();
3249 let mut fallback: BTreeMap<String, Vec<FormulaIngestRecord>> = BTreeMap::new();
3250 let mut planned_fallback: PlannedFormulaMaterialize = BTreeMap::new();
3251
3252 for batch in batches {
3253 let sheet_id = self.graph.sheet_id_mut(&batch.sheet_name);
3254 for record in &batch.formulas {
3255 if record.row == 0 || record.col == 0 {
3256 report.shadow_candidate_cells = report.shadow_candidate_cells.saturating_add(1);
3257 report.shadow_fallback_cells = report.shadow_fallback_cells.saturating_add(1);
3258 Self::record_shadow_fallback_reason(
3259 &mut report,
3260 PlacementFallbackReason::UnsupportedShapeOrGaps,
3261 1,
3262 );
3263 fallback
3264 .entry(batch.sheet_name.clone())
3265 .or_default()
3266 .push(record.clone());
3267 continue;
3268 }
3269
3270 pending_candidates.push((
3271 batch.sheet_name.clone(),
3272 FormulaPlacementCandidate::new(
3273 sheet_id,
3274 record.row - 1,
3275 record.col - 1,
3276 record.ast_id,
3277 record.formula_text.clone(),
3278 ),
3279 ));
3280 }
3281 }
3282
3283 let mut groups: BTreeMap<(SheetId, u64, u32), Vec<usize>> = BTreeMap::new();
3284 let mut analyses_by_index: Vec<Option<CandidateAnalysis>> =
3285 (0..pending_candidates.len()).map(|_| None).collect();
3286 let mut plans_by_index: Vec<Option<DependencyPlanRow>> =
3287 (0..pending_candidates.len()).map(|_| None).collect();
3288 {
3289 let mut pipeline = self.ingest_pipeline();
3290 for (idx, (sheet_name, candidate)) in pending_candidates.iter_mut().enumerate() {
3291 let placement = CellRef::new(
3292 candidate.sheet_id,
3293 Coord::from_excel(
3294 candidate.row.saturating_add(1),
3295 candidate.col.saturating_add(1),
3296 true,
3297 true,
3298 ),
3299 );
3300 let ingested = pipeline.ingest_formula(
3301 FormulaAstInput::RawArena(candidate.ast_id),
3302 placement,
3303 candidate.formula_text.clone(),
3304 );
3305 match ingested {
3306 Ok(ingested) => {
3307 candidate.ast_id = ingested.ast_id;
3308 let canonical_hash = ingested.parameterized_canonical_hash;
3309 let dep_plan = ingested.dep_plan.clone();
3310 match CandidateAnalysis::from_ingested(candidate, &ingested) {
3311 Ok(analysis) => {
3312 groups
3313 .entry((candidate.sheet_id, canonical_hash, candidate.col))
3314 .or_default()
3315 .push(idx);
3316 analyses_by_index[idx] = Some(analysis);
3317 plans_by_index[idx] = Some(dep_plan);
3318 }
3319 Err(reason) => {
3320 report.shadow_candidate_cells =
3321 report.shadow_candidate_cells.saturating_add(1);
3322 report.shadow_fallback_cells =
3323 report.shadow_fallback_cells.saturating_add(1);
3324 Self::record_shadow_fallback_reason(&mut report, reason, 1);
3325 planned_fallback
3326 .entry(sheet_name.clone())
3327 .or_default()
3328 .push((
3329 candidate.row.saturating_add(1),
3330 candidate.col.saturating_add(1),
3331 candidate.ast_id,
3332 dep_plan,
3333 ));
3334 }
3335 }
3336 }
3337 Err(_) => {
3338 report.shadow_candidate_cells =
3339 report.shadow_candidate_cells.saturating_add(1);
3340 report.shadow_fallback_cells =
3341 report.shadow_fallback_cells.saturating_add(1);
3342 Self::record_shadow_fallback_reason(
3343 &mut report,
3344 PlacementFallbackReason::UnsupportedCanonicalTemplate,
3345 1,
3346 );
3347 fallback.entry(sheet_name.clone()).or_default().push(
3348 FormulaIngestRecord::new(
3349 candidate.row.saturating_add(1),
3350 candidate.col.saturating_add(1),
3351 candidate.ast_id,
3352 candidate.formula_text.clone(),
3353 ),
3354 );
3355 }
3356 }
3357 }
3358 }
3359
3360 for ((_sheet_id, _canonical_hash, _col), candidate_indices) in groups {
3361 let sheet_name = pending_candidates[candidate_indices[0]].0.clone();
3362 let mut plans_by_coord: BTreeMap<(u32, u32), Vec<DependencyPlanRow>> = BTreeMap::new();
3363 for idx in &candidate_indices {
3364 if let Some(plan) = plans_by_index[*idx].take() {
3367 let candidate = &pending_candidates[*idx].1;
3368 plans_by_coord
3369 .entry((candidate.row, candidate.col))
3370 .or_default()
3371 .push(plan);
3372 }
3373 }
3374 let candidates: Vec<_> = candidate_indices
3375 .iter()
3376 .map(|idx| pending_candidates[*idx].1.clone())
3377 .collect();
3378 let components = Self::split_shadow_candidate_components(candidates);
3379 let analyzed_components =
3380 if components.len() == 1 && components[0].len() == candidate_indices.len() {
3381 let component = components.into_iter().next().expect("one component");
3382 let component_analyses = candidate_indices
3383 .iter()
3384 .map(|idx| {
3385 analyses_by_index[*idx]
3386 .take()
3387 .expect("candidate analysis must be used once")
3388 })
3389 .collect();
3390 vec![(component, component_analyses)]
3391 } else {
3392 let mut indices_by_coord: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3393 for idx in candidate_indices.iter().rev() {
3394 let candidate = &pending_candidates[*idx].1;
3395 indices_by_coord
3396 .entry((candidate.row, candidate.col))
3397 .or_default()
3398 .push(*idx);
3399 }
3400
3401 components
3402 .into_iter()
3403 .map(|component| {
3404 let mut component_analyses = Vec::with_capacity(component.len());
3405 for candidate in &component {
3406 let idx = indices_by_coord
3407 .get_mut(&(candidate.row, candidate.col))
3408 .and_then(Vec::pop)
3409 .expect("component candidate must have a precomputed analysis");
3410 component_analyses.push(
3411 analyses_by_index[idx]
3412 .take()
3413 .expect("candidate analysis must be used once"),
3414 );
3415 }
3416 (component, component_analyses)
3417 })
3418 .collect()
3419 };
3420
3421 for (component, component_analyses) in analyzed_components {
3422 for (component, component_analyses) in
3423 split_candidate_affine_literal_runs(component, component_analyses)
3424 {
3425 let placement_report = {
3426 let authority = self.graph.formula_authority_mut();
3427 place_candidate_family_with_analyses(
3428 &mut authority.plane,
3429 component.clone(),
3430 component_analyses,
3431 )
3432 };
3433 Self::accumulate_formula_plane_placement_report(&mut report, &placement_report);
3434
3435 let mut candidate_by_placement: FxHashMap<
3443 crate::formula_plane::runtime::PlacementCoord,
3444 &FormulaPlacementCandidate,
3445 > = FxHashMap::with_capacity_and_hasher(component.len(), Default::default());
3446 for candidate in &component {
3447 candidate_by_placement
3448 .entry(candidate.placement())
3449 .or_insert(candidate);
3450 }
3451 for result in &placement_report.results {
3452 let FormulaPlacementResult::Legacy { placement, .. } = result else {
3453 continue;
3454 };
3455 if let Some(&candidate) = candidate_by_placement.get(placement) {
3456 let plan = plans_by_coord
3457 .get_mut(&(candidate.row, candidate.col))
3458 .and_then(Vec::pop);
3459 if let Some(plan) = plan {
3460 planned_fallback
3461 .entry(sheet_name.clone())
3462 .or_default()
3463 .push((
3464 candidate.row.saturating_add(1),
3465 candidate.col.saturating_add(1),
3466 candidate.ast_id,
3467 plan,
3468 ));
3469 } else {
3470 fallback.entry(sheet_name.clone()).or_default().push(
3471 FormulaIngestRecord::new(
3472 candidate.row.saturating_add(1),
3473 candidate.col.saturating_add(1),
3474 candidate.ast_id,
3475 candidate.formula_text.clone(),
3476 ),
3477 );
3478 }
3479 }
3480 }
3481 }
3482 }
3483 }
3484
3485 let _index_report = self.graph.formula_authority_mut().rebuild_indexes();
3486
3487 let fallback_batches = fallback
3488 .into_iter()
3489 .map(|(sheet_name, formulas)| FormulaIngestBatch::new(sheet_name, formulas))
3490 .collect();
3491 (report, fallback_batches, planned_fallback)
3492 }
3493
3494 fn accumulate_formula_plane_placement_report(
3495 report: &mut FormulaIngestReport,
3496 placement_report: &crate::formula_plane::placement::FormulaPlacementReport,
3497 ) {
3498 let counters = &placement_report.counters;
3499 report.shadow_candidate_cells = report
3500 .shadow_candidate_cells
3501 .saturating_add(counters.formula_cells_seen);
3502 report.shadow_accepted_span_cells = report
3503 .shadow_accepted_span_cells
3504 .saturating_add(counters.accepted_span_cells);
3505 report.shadow_fallback_cells = report
3506 .shadow_fallback_cells
3507 .saturating_add(counters.legacy_cells);
3508 report.shadow_templates_interned = report
3509 .shadow_templates_interned
3510 .saturating_add(counters.templates_interned);
3511 report.shadow_spans_created = report
3512 .shadow_spans_created
3513 .saturating_add(counters.spans_created);
3514 report.graph_formula_vertices_avoided_shadow = report
3515 .graph_formula_vertices_avoided_shadow
3516 .saturating_add(counters.formula_vertices_avoided);
3517 report.ast_roots_avoided_shadow = report
3518 .ast_roots_avoided_shadow
3519 .saturating_add(counters.ast_roots_avoided);
3520 report.edge_rows_avoided_shadow = report
3521 .edge_rows_avoided_shadow
3522 .saturating_add(counters.edge_rows_avoided);
3523 for (reason, count) in &counters.fallback_reasons {
3524 Self::record_shadow_fallback_reason(report, *reason, *count);
3525 }
3526 }
3527
3528 fn split_candidate_components_with_analyses(
3529 candidates: Vec<FormulaPlacementCandidate>,
3530 mut analyses: Vec<CandidateAnalysis>,
3531 ) -> Vec<(Vec<FormulaPlacementCandidate>, Vec<CandidateAnalysis>)> {
3532 let components = Self::split_shadow_candidate_components(candidates.clone());
3533 let mut analysis_by_coord: BTreeMap<(u32, u32), Vec<CandidateAnalysis>> = BTreeMap::new();
3534 for (candidate, analysis) in candidates.into_iter().zip(analyses.drain(..)) {
3535 analysis_by_coord
3536 .entry((candidate.row, candidate.col))
3537 .or_default()
3538 .push(analysis);
3539 }
3540 components
3541 .into_iter()
3542 .flat_map(|component| {
3543 let mut component_analyses = Vec::with_capacity(component.len());
3544 for candidate in &component {
3545 let analysis = analysis_by_coord
3546 .get_mut(&(candidate.row, candidate.col))
3547 .and_then(Vec::pop)
3548 .expect("component candidate must have a precomputed analysis");
3549 component_analyses.push(analysis);
3550 }
3551 split_candidate_affine_literal_runs(component, component_analyses)
3552 })
3553 .collect()
3554 }
3555
3556 fn split_shadow_candidate_components(
3557 candidates: Vec<FormulaPlacementCandidate>,
3558 ) -> Vec<Vec<FormulaPlacementCandidate>> {
3559 if candidates.len() <= 1 {
3560 return vec![candidates];
3561 }
3562
3563 let is_row_run = candidates.windows(2).all(|w| {
3567 w[0].sheet_id == w[1].sheet_id && w[0].col == w[1].col && w[0].row + 1 == w[1].row
3568 });
3569 let is_col_run = candidates.windows(2).all(|w| {
3570 w[0].sheet_id == w[1].sheet_id && w[0].row == w[1].row && w[0].col + 1 == w[1].col
3571 });
3572 if is_row_run || is_col_run {
3573 return vec![candidates];
3574 }
3575
3576 let mut coord_to_indices: BTreeMap<(u32, u32), Vec<usize>> = BTreeMap::new();
3577 for (idx, candidate) in candidates.iter().enumerate() {
3578 coord_to_indices
3579 .entry((candidate.row, candidate.col))
3580 .or_default()
3581 .push(idx);
3582 }
3583
3584 let mut remaining: BTreeSet<usize> = (0..candidates.len()).collect();
3585 let mut components = Vec::new();
3586 while let Some(&start) = remaining.iter().next() {
3587 remaining.remove(&start);
3588 let mut queue = VecDeque::from([start]);
3589 let mut component_indices = Vec::new();
3590
3591 while let Some(idx) = queue.pop_front() {
3592 component_indices.push(idx);
3593 let candidate = &candidates[idx];
3594 let mut neighbor_coords = Vec::with_capacity(5);
3595 neighbor_coords.push((candidate.row, candidate.col));
3596 if let Some(row) = candidate.row.checked_sub(1) {
3597 neighbor_coords.push((row, candidate.col));
3598 }
3599 neighbor_coords.push((candidate.row.saturating_add(1), candidate.col));
3600 if let Some(col) = candidate.col.checked_sub(1) {
3601 neighbor_coords.push((candidate.row, col));
3602 }
3603 neighbor_coords.push((candidate.row, candidate.col.saturating_add(1)));
3604
3605 for coord in neighbor_coords {
3606 if let Some(indices) = coord_to_indices.get(&coord) {
3607 for &neighbor in indices {
3608 if remaining.remove(&neighbor) {
3609 queue.push_back(neighbor);
3610 }
3611 }
3612 }
3613 }
3614 }
3615
3616 component_indices.sort_by_key(|idx| {
3617 let candidate = &candidates[*idx];
3618 (candidate.row, candidate.col, *idx)
3619 });
3620 components.push(
3621 component_indices
3622 .into_iter()
3623 .map(|idx| candidates[idx].clone())
3624 .collect(),
3625 );
3626 }
3627
3628 components
3629 }
3630
3631 pub fn ingest_formula_batches(
3632 &mut self,
3633 batches: Vec<FormulaIngestBatch>,
3634 ) -> Result<FormulaIngestReport, ExcelError> {
3635 let formula_cells_seen = batches.iter().map(|batch| batch.len() as u64).sum();
3636 let (mut report, materialize_batches, planned_materialize) =
3637 match self.config.formula_plane_mode {
3638 FormulaPlaneMode::Off => (
3639 FormulaIngestReport::with_mode(FormulaPlaneMode::Off),
3640 batches,
3641 BTreeMap::new(),
3642 ),
3643 FormulaPlaneMode::Shadow => (
3644 self.analyze_formula_plane_shadow_candidates(&batches),
3645 batches,
3646 BTreeMap::new(),
3647 ),
3648 FormulaPlaneMode::AuthoritativeExperimental => {
3649 self.analyze_formula_plane_authoritative_ingest(&batches)
3650 }
3651 };
3652 report.formula_cells_seen = formula_cells_seen;
3653
3654 if !materialize_batches.iter().all(FormulaIngestBatch::is_empty)
3655 || !planned_materialize.is_empty()
3656 {
3657 let mut builder = self.begin_bulk_ingest();
3658 for batch in materialize_batches {
3659 if batch.is_empty() {
3660 continue;
3661 }
3662 let sheet_id = builder.add_sheet(&batch.sheet_name);
3663 builder.add_formula_ids(
3664 sheet_id,
3665 batch
3666 .formulas
3667 .into_iter()
3668 .map(|record| (record.row, record.col, record.ast_id)),
3669 );
3670 }
3671 for (sheet_name, formulas) in planned_materialize {
3672 if formulas.is_empty() {
3673 continue;
3674 }
3675 let sheet_id = builder.add_sheet(&sheet_name);
3676 builder.add_formula_plans(sheet_id, formulas);
3677 }
3678 let summary = builder.finish()?;
3679 report.graph_formula_cells_materialized = summary.formulas as u64;
3680 report.graph_vertices_created = summary.vertices as u64;
3681 report.graph_edges_created = summary.edges as u64;
3682 }
3683
3684 self.record_formula_ingest_report(report.clone());
3685 Ok(report)
3686 }
3687
3688 pub fn handle_formula_parse_error(
3689 &mut self,
3690 sheet: &str,
3691 row: u32,
3692 col: u32,
3693 formula: &str,
3694 message: String,
3695 ) -> Result<Option<ASTNode>, ExcelError> {
3696 let policy = self.config.formula_parse_policy;
3697
3698 if policy == FormulaParsePolicy::Strict {
3699 let col_a1 = col_letters_from_1based(col).unwrap_or_else(|_| "?".to_string());
3700 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(format!(
3701 "Formula parse error at {sheet}!{col_a1}{row}: {message}"
3702 )));
3703 }
3704
3705 self.formula_parse_diagnostics.push(FormulaParseDiagnostic {
3706 sheet: sheet.to_string(),
3707 row,
3708 col,
3709 formula: formula.to_string(),
3710 message: message.clone(),
3711 policy,
3712 });
3713
3714 match policy {
3715 FormulaParsePolicy::Strict => unreachable!(),
3716 FormulaParsePolicy::KeepCachedValue => Ok(None),
3717 FormulaParsePolicy::AsText => Ok(Some(ASTNode::new(
3718 ASTNodeType::Literal(LiteralValue::Text(formula.to_string())),
3719 None,
3720 ))),
3721 FormulaParsePolicy::CoerceToError => {
3722 let err = ExcelError::new(ExcelErrorKind::Error)
3723 .with_message(format!("Malformed formula: {message}"));
3724 Ok(Some(ASTNode::new(
3725 ASTNodeType::Literal(LiteralValue::Error(err)),
3726 None,
3727 )))
3728 }
3729 }
3730 }
3731
3732 pub fn build_graph_all(&mut self) -> Result<(), formualizer_parse::ExcelError> {
3734 if self.staged_formulas.is_empty() {
3735 return Ok(());
3736 }
3737 let staged = std::mem::take(&mut self.staged_formulas);
3739 for sheet in staged.keys() {
3740 let _ = self.add_sheet(sheet);
3741 }
3742
3743 let mut prepared: PreparedFormulaBatches = Vec::new();
3745 for (sheet, entries) in staged {
3746 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3747 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3748 rustc_hash::FxHashMap::default();
3749 cache.reserve(4096);
3750
3751 for (row, col, txt) in entries.into_entries() {
3752 let key = if txt.starts_with('=') {
3753 txt
3754 } else {
3755 format!("={txt}")
3756 };
3757 let ast_id = if let Some(cached) = cache.get(&key) {
3758 *cached
3759 } else {
3760 let parsed = match formualizer_parse::parser::parse(&key) {
3761 Ok(parsed) => Some(parsed),
3762 Err(e) => {
3763 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3764 }
3765 };
3766 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3767 cache.insert(key.clone(), ast_id);
3768 ast_id
3769 };
3770
3771 if let Some(ast_id) = ast_id {
3772 formulas.push(FormulaIngestRecord::new(
3773 row,
3774 col,
3775 ast_id,
3776 Some(Arc::<str>::from(key.clone())),
3777 ));
3778 }
3779 }
3780
3781 if !formulas.is_empty() {
3782 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3783 }
3784 }
3785
3786 if !prepared.is_empty() {
3787 let _ = self.ingest_formula_batches(prepared)?;
3788 }
3789 Ok(())
3790 }
3791
3792 pub fn build_graph_for_sheets<'a, I: IntoIterator<Item = &'a str>>(
3794 &mut self,
3795 sheets: I,
3796 ) -> Result<(), formualizer_parse::ExcelError> {
3797 let mut collected: StagedFormulaBatches = Vec::new();
3798 for s in sheets {
3799 if let Some(entries) = self.staged_formulas.remove(s) {
3800 collected.push((s.to_string(), entries.into_entries()));
3801 }
3802 }
3803
3804 if collected.is_empty() {
3805 return Ok(());
3806 }
3807
3808 for (sheet, _) in &collected {
3809 let _ = self.add_sheet(sheet);
3810 }
3811
3812 let mut prepared: PreparedFormulaBatches = Vec::new();
3814 let mut cache: rustc_hash::FxHashMap<String, Option<crate::engine::arena::AstNodeId>> =
3815 rustc_hash::FxHashMap::default();
3816 cache.reserve(4096);
3817
3818 for (sheet, entries) in collected {
3819 let mut formulas: Vec<FormulaIngestRecord> = Vec::new();
3820 for (row, col, txt) in entries {
3821 let key = if txt.starts_with('=') {
3822 txt
3823 } else {
3824 format!("={txt}")
3825 };
3826 let ast_id = if let Some(cached) = cache.get(&key) {
3827 *cached
3828 } else {
3829 let parsed = match formualizer_parse::parser::parse(&key) {
3830 Ok(parsed) => Some(parsed),
3831 Err(e) => {
3832 self.handle_formula_parse_error(&sheet, row, col, &key, e.to_string())?
3833 }
3834 };
3835 let ast_id = parsed.as_ref().map(|ast| self.intern_formula_ast(ast));
3836 cache.insert(key.clone(), ast_id);
3837 ast_id
3838 };
3839
3840 if let Some(ast_id) = ast_id {
3841 formulas.push(FormulaIngestRecord::new(
3842 row,
3843 col,
3844 ast_id,
3845 Some(Arc::<str>::from(key.clone())),
3846 ));
3847 }
3848 }
3849 if !formulas.is_empty() {
3850 prepared.push(FormulaIngestBatch::new(sheet, formulas));
3851 }
3852 }
3853
3854 if !prepared.is_empty() {
3855 let _ = self.ingest_formula_batches(prepared)?;
3856 }
3857 Ok(())
3858 }
3859
3860 pub fn begin_bulk_ingest_arrow(
3862 &mut self,
3863 ) -> crate::engine::arrow_ingest::ArrowBulkIngestBuilder<'_, R> {
3864 crate::engine::arrow_ingest::ArrowBulkIngestBuilder::new(self)
3865 }
3866
3867 pub fn begin_bulk_update_arrow(
3869 &mut self,
3870 ) -> crate::engine::arrow_ingest::ArrowBulkUpdateBuilder<'_, R> {
3871 crate::engine::arrow_ingest::ArrowBulkUpdateBuilder::new(self)
3872 }
3873
3874 fn ensure_known_sheet_id(&self, sheet: &str) -> Result<SheetId, crate::engine::EditorError> {
3875 self.graph.sheet_id(sheet).ok_or(
3876 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
3877 name: sheet.to_string(),
3878 reason: "Unknown sheet".to_string(),
3879 },
3880 )
3881 }
3882
3883 fn normalize_row_1based(row_1based: u32) -> Result<u32, crate::engine::EditorError> {
3884 if row_1based == 0 {
3885 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3886 }
3887 Ok(row_1based - 1)
3888 }
3889
3890 fn normalize_row_range_1based(
3891 start_row_1based: u32,
3892 end_row_1based: u32,
3893 ) -> Result<(u32, u32), crate::engine::EditorError> {
3894 if start_row_1based == 0 || end_row_1based == 0 {
3895 return Err(crate::engine::EditorError::OutOfBounds { row: 0, col: 0 });
3896 }
3897 if start_row_1based > end_row_1based {
3898 return Err(crate::engine::EditorError::TransactionFailed {
3899 reason: "Row range start is greater than end".to_string(),
3900 });
3901 }
3902 Ok((start_row_1based - 1, end_row_1based - 1))
3903 }
3904
3905 fn invalidate_row_visibility_mask_cache(&self) {
3906 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
3907 cache.clear();
3908 }
3909 }
3910
3911 fn set_row_hidden_by_sheet_id(
3912 &mut self,
3913 sheet_id: SheetId,
3914 row0: u32,
3915 hidden: bool,
3916 source: RowVisibilitySource,
3917 ) -> bool {
3918 let changed = {
3919 let state = self.row_visibility.entry(sheet_id).or_default();
3920 state.set_row_hidden(row0, hidden, source)
3921 };
3922
3923 let remove_entry = self
3924 .row_visibility
3925 .get(&sheet_id)
3926 .map(|state| state.is_empty())
3927 .unwrap_or(false);
3928 if remove_entry {
3929 self.row_visibility.remove(&sheet_id);
3930 }
3931
3932 if changed {
3933 self.invalidate_row_visibility_mask_cache();
3934 }
3935
3936 changed
3937 }
3938
3939 fn set_rows_hidden_by_sheet_id(
3940 &mut self,
3941 sheet_id: SheetId,
3942 start_row0: u32,
3943 end_row0: u32,
3944 hidden: bool,
3945 source: RowVisibilitySource,
3946 ) -> bool {
3947 let changed = {
3948 let state = self.row_visibility.entry(sheet_id).or_default();
3949 state.set_rows_hidden(start_row0, end_row0, hidden, source)
3950 };
3951
3952 let remove_entry = self
3953 .row_visibility
3954 .get(&sheet_id)
3955 .map(|state| state.is_empty())
3956 .unwrap_or(false);
3957 if remove_entry {
3958 self.row_visibility.remove(&sheet_id);
3959 }
3960
3961 if changed {
3962 self.invalidate_row_visibility_mask_cache();
3963 }
3964
3965 changed
3966 }
3967
3968 fn shift_row_visibility_insert(&mut self, sheet_id: SheetId, before0: u32, count: u32) {
3969 if count == 0 {
3970 return;
3971 }
3972 let mut changed = false;
3973 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3974 changed = state.insert_rows(before0, count);
3975 state.is_empty()
3976 } else {
3977 false
3978 };
3979 if remove_entry {
3980 self.row_visibility.remove(&sheet_id);
3981 }
3982 if changed {
3983 self.invalidate_row_visibility_mask_cache();
3984 }
3985 }
3986
3987 fn shift_row_visibility_delete(&mut self, sheet_id: SheetId, start0: u32, count: u32) {
3988 if count == 0 {
3989 return;
3990 }
3991 let mut changed = false;
3992 let remove_entry = if let Some(state) = self.row_visibility.get_mut(&sheet_id) {
3993 changed = state.delete_rows(start0, count);
3994 state.is_empty()
3995 } else {
3996 false
3997 };
3998 if remove_entry {
3999 self.row_visibility.remove(&sheet_id);
4000 }
4001 if changed {
4002 self.invalidate_row_visibility_mask_cache();
4003 }
4004 }
4005
4006 fn apply_inverse_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
4007 if let crate::engine::ChangeEvent::SetRowVisibility {
4008 sheet_id,
4009 row0,
4010 source,
4011 old_hidden,
4012 ..
4013 } = event
4014 {
4015 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *old_hidden, *source);
4016 }
4017 }
4018
4019 fn apply_forward_row_visibility_event(&mut self, event: &crate::engine::ChangeEvent) {
4020 if let crate::engine::ChangeEvent::SetRowVisibility {
4021 sheet_id,
4022 row0,
4023 source,
4024 new_hidden,
4025 ..
4026 } = event
4027 {
4028 let _ = self.set_row_hidden_by_sheet_id(*sheet_id, *row0, *new_hidden, *source);
4029 }
4030 }
4031
4032 fn apply_inverse_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
4033 for event in events.iter().rev() {
4034 self.apply_inverse_row_visibility_event(event);
4035 }
4036 }
4037
4038 fn apply_forward_row_visibility_events(&mut self, events: &[crate::engine::ChangeEvent]) {
4039 for event in events {
4040 self.apply_forward_row_visibility_event(event);
4041 }
4042 }
4043
4044 fn apply_inverse_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
4045 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
4046 sheet,
4047 row,
4048 col,
4049 old,
4050 ..
4051 } = event
4052 {
4053 self.apply_staged_formula_cell(sheet, *row, *col, old.as_deref());
4054 }
4055 }
4056
4057 fn apply_forward_staged_formula_event(&mut self, event: &crate::engine::ChangeEvent) {
4058 if let crate::engine::ChangeEvent::StagedFormulaCellChanged {
4059 sheet,
4060 row,
4061 col,
4062 new,
4063 ..
4064 } = event
4065 {
4066 self.apply_staged_formula_cell(sheet, *row, *col, new.as_deref());
4067 }
4068 }
4069
4070 fn apply_staged_formula_cell(&mut self, sheet: &str, row: u32, col: u32, target: Option<&str>) {
4073 match target {
4074 Some(text) => self.stage_formula_text(sheet, row, col, text.to_string()),
4075 None => {
4076 self.clear_staged_formula_text(sheet, row, col);
4077 }
4078 }
4079 }
4080
4081 pub fn set_row_hidden(
4082 &mut self,
4083 sheet: &str,
4084 row_1based: u32,
4085 hidden: bool,
4086 source: RowVisibilitySource,
4087 ) -> Result<(), crate::engine::EditorError> {
4088 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4089 let row0 = Self::normalize_row_1based(row_1based)?;
4090 if self.set_row_hidden_by_sheet_id(sheet_id, row0, hidden, source) {
4091 self.record_formula_plane_structural_change(StructuralScope::Region(
4092 Region::whole_row(sheet_id, row0),
4093 ));
4094 self.mark_data_edited();
4095 }
4096 Ok(())
4097 }
4098
4099 pub fn set_rows_hidden(
4100 &mut self,
4101 sheet: &str,
4102 start_row_1based: u32,
4103 end_row_1based: u32,
4104 hidden: bool,
4105 source: RowVisibilitySource,
4106 ) -> Result<(), crate::engine::EditorError> {
4107 let sheet_id = self.ensure_known_sheet_id(sheet)?;
4108 let (start_row0, end_row0) =
4109 Self::normalize_row_range_1based(start_row_1based, end_row_1based)?;
4110 if self.set_rows_hidden_by_sheet_id(sheet_id, start_row0, end_row0, hidden, source) {
4111 if start_row0 == end_row0 {
4112 self.record_formula_plane_structural_change(StructuralScope::Region(
4113 Region::whole_row(sheet_id, start_row0),
4114 ));
4115 } else {
4116 self.record_formula_plane_structural_change(StructuralScope::Sheet(sheet_id));
4117 }
4118 self.mark_data_edited();
4119 }
4120 Ok(())
4121 }
4122
4123 pub fn is_row_hidden(
4124 &self,
4125 sheet: &str,
4126 row_1based: u32,
4127 source: Option<RowVisibilitySource>,
4128 ) -> Option<bool> {
4129 let sheet_id = self.graph.sheet_id(sheet)?;
4130 let row0 = row_1based.checked_sub(1)?;
4131 Some(
4132 self.row_visibility
4133 .get(&sheet_id)
4134 .map(|state| state.is_row_hidden(row0, source))
4135 .unwrap_or(false),
4136 )
4137 }
4138
4139 pub fn row_visibility_version(&self, sheet: &str) -> Option<u64> {
4140 let sheet_id = self.graph.sheet_id(sheet)?;
4141 Some(
4142 self.row_visibility
4143 .get(&sheet_id)
4144 .map(|state| state.version())
4145 .unwrap_or(0),
4146 )
4147 }
4148
4149 fn build_row_visibility_mask_for_view(
4150 &self,
4151 view: &RangeView<'_>,
4152 mode: VisibilityMaskMode,
4153 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
4154 let sheet_rows = view.sheet().nrows as usize;
4155 if sheet_rows == 0 || view.start_row() >= sheet_rows {
4156 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
4157 }
4158
4159 let sheet_id = self.graph.sheet_id(view.sheet_name())?;
4160 let start_row0 = view.start_row() as u32;
4161 let end_row0 = view.end_row().min(sheet_rows.saturating_sub(1)) as u32;
4162 let version = self
4163 .row_visibility
4164 .get(&sheet_id)
4165 .map(|state| state.version())
4166 .unwrap_or(0);
4167 let key = VisibilityMaskCacheKey {
4168 sheet_id,
4169 start_row0,
4170 end_row0,
4171 mode,
4172 version,
4173 };
4174
4175 if let Ok(cache) = self.row_visibility_mask_cache.read()
4176 && let Some(mask) = cache.get(&key)
4177 {
4178 #[cfg(test)]
4179 visibility_mask_test_hooks::inc_hit();
4180 return Some(mask.clone());
4181 }
4182
4183 #[cfg(test)]
4184 visibility_mask_test_hooks::inc_miss();
4185
4186 let state = self.row_visibility.get(&sheet_id);
4187 let mut out = Vec::with_capacity((end_row0 - start_row0 + 1) as usize);
4188 for row0 in start_row0..=end_row0 {
4189 let manual_hidden = state
4190 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Manual)))
4191 .unwrap_or(false);
4192 let filter_hidden = state
4193 .map(|s| s.is_row_hidden(row0, Some(RowVisibilitySource::Filter)))
4194 .unwrap_or(false);
4195
4196 let include = match mode {
4197 VisibilityMaskMode::IncludeAll => true,
4198 VisibilityMaskMode::ExcludeManualHidden => !manual_hidden,
4199 VisibilityMaskMode::ExcludeFilterHidden => !filter_hidden,
4200 VisibilityMaskMode::ExcludeManualOrFilterHidden => {
4201 !(manual_hidden || filter_hidden)
4202 }
4203 };
4204 out.push(include);
4205 }
4206
4207 let mask = std::sync::Arc::new(arrow_array::BooleanArray::from(out));
4208 if let Ok(mut cache) = self.row_visibility_mask_cache.write() {
4209 const MAX_CACHE_ENTRIES: usize = 4096;
4210 if cache.len() >= MAX_CACHE_ENTRIES {
4211 cache.clear();
4212 #[cfg(test)]
4213 visibility_mask_test_hooks::inc_eviction();
4214 }
4215 cache.insert(key, mask.clone());
4216 }
4217
4218 Some(mask)
4219 }
4220
4221 fn editor_error_to_excel(error: crate::engine::EditorError) -> ExcelError {
4222 match error {
4223 crate::engine::EditorError::Excel(error) => error,
4224 other => ExcelError::new(ExcelErrorKind::Value).with_message(other.to_string()),
4225 }
4226 }
4227
4228 fn demote_span_containing_cell_for_write(
4229 &mut self,
4230 sheet_id: SheetId,
4231 row0: u32,
4232 col0: u32,
4233 ) -> Result<(), crate::engine::EditorError> {
4234 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
4235 return Ok(());
4236 }
4237 let placement = PlacementCoord::new(sheet_id, row0, col0);
4238 let inside_active_span = self
4239 .graph
4240 .formula_authority()
4241 .plane
4242 .spans
4243 .find_at(placement)
4244 .is_some();
4245 if inside_active_span {
4246 self.demote_spans_preserving_computed_overlays(
4247 sheet_id,
4248 Region::point(sheet_id, row0, col0),
4249 )?;
4250 }
4251 Ok(())
4252 }
4253
4254 fn demote_spans_preserving_computed_overlays(
4255 &mut self,
4256 _sheet_id: SheetId,
4257 affected_region: Region,
4258 ) -> Result<(), crate::engine::EditorError> {
4259 self.demote_spans_for_structural_op_impl(None, affected_region, false)
4263 }
4264
4265 fn structural_row_region(sheet_id: SheetId, start_row0: u32) -> Region {
4266 Region::rows_from(sheet_id, start_row0)
4267 }
4268
4269 fn structural_col_region(sheet_id: SheetId, start_col0: u32) -> Region {
4270 Region::cols_from(sheet_id, start_col0)
4271 }
4272
4273 fn span_result_region_intersects_affected(
4274 span: &crate::formula_plane::runtime::FormulaSpan,
4275 affected_region: &Region,
4276 ) -> bool {
4277 Region::from_domain(span.result_region.domain()).intersects(affected_region)
4278 }
4279
4280 fn span_any_read_region_intersects_affected(
4281 plane: &FormulaPlane,
4282 span: &crate::formula_plane::runtime::FormulaSpan,
4283 affected_region: &Region,
4284 ) -> bool {
4285 span.read_summary_id
4286 .and_then(|read_summary_id| plane.span_read_summaries.get(read_summary_id))
4287 .is_some_and(|summary| {
4288 summary
4289 .dependencies
4290 .iter()
4291 .any(|dependency| dependency.read_region.intersects(affected_region))
4292 })
4293 }
4294
4295 fn insert_formula_plane_dirty_coords_for_span(
4296 &self,
4297 span_ref: FormulaSpanRef,
4298 dirty: ProducerDirtyDomain,
4299 out: &mut FxHashSet<(SheetId, u32, u32)>,
4300 ) -> Result<(), crate::engine::EditorError> {
4301 let authority = self.graph.formula_authority();
4302 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
4303 ExcelError::new(ExcelErrorKind::NImpl)
4304 .with_message("FormulaPlane dirty transfer referenced a stale span")
4305 })?;
4306 match dirty {
4307 ProducerDirtyDomain::Whole => {
4308 out.extend(
4309 span.domain
4310 .iter()
4311 .map(|coord| (coord.sheet_id, coord.row, coord.col)),
4312 );
4313 }
4314 ProducerDirtyDomain::Cells(cells) => {
4315 out.extend(cells.into_iter().filter_map(|key| {
4316 let coord = PlacementCoord::new(key.sheet_id, key.row, key.col);
4317 span.domain
4318 .contains(coord)
4319 .then_some((coord.sheet_id, coord.row, coord.col))
4320 }));
4321 }
4322 ProducerDirtyDomain::Regions(regions) => {
4323 out.extend(span.domain.iter().filter_map(|coord| {
4324 let key = crate::formula_plane::region_index::RegionKey::from(coord);
4325 regions
4326 .iter()
4327 .any(|region| region.contains_key(key))
4328 .then_some((coord.sheet_id, coord.row, coord.col))
4329 }));
4330 }
4331 }
4332 Ok(())
4333 }
4334
4335 fn compute_current_formula_plane_dirty_result_coords(
4336 &self,
4337 ) -> Result<FxHashSet<(SheetId, u32, u32)>, crate::engine::EditorError> {
4338 use crate::formula_plane::producer::compute_dirty_closure;
4339
4340 let authority = self.graph.formula_authority();
4341 let span_refs = authority.active_span_refs();
4342 let span_refs_by_id = span_refs
4343 .iter()
4344 .copied()
4345 .map(|span_ref| (span_ref.id, span_ref))
4346 .collect::<BTreeMap<_, _>>();
4347 let mut dirty_coords = FxHashSet::default();
4348
4349 if self.formula_plane_indexes_epoch_seen != authority.indexes_epoch() {
4350 for span_ref in span_refs {
4351 self.insert_formula_plane_dirty_coords_for_span(
4352 span_ref,
4353 ProducerDirtyDomain::Whole,
4354 &mut dirty_coords,
4355 )?;
4356 }
4357 return Ok(dirty_coords);
4358 }
4359
4360 let pending_changed_regions = authority.pending_changed_regions();
4361 if pending_changed_regions.is_empty() {
4362 return Ok(dirty_coords);
4363 }
4364
4365 let closure = compute_dirty_closure(
4366 &authority.consumer_reads,
4367 pending_changed_regions.iter().copied(),
4368 |producer| authority.producer_results.producer_result_region(producer),
4369 );
4370 for work in closure.work {
4371 let FormulaProducerId::Span(span_id) = work.producer else {
4372 continue;
4373 };
4374 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4375 continue;
4376 };
4377 self.insert_formula_plane_dirty_coords_for_span(
4378 span_ref,
4379 work.dirty,
4380 &mut dirty_coords,
4381 )?;
4382 }
4383 for fallback in closure.fallbacks {
4384 let FormulaProducerId::Span(span_id) = fallback.consumer else {
4385 continue;
4386 };
4387 let Some(span_ref) = span_refs_by_id.get(&span_id).copied() else {
4388 continue;
4389 };
4390 self.insert_formula_plane_dirty_coords_for_span(
4391 span_ref,
4392 ProducerDirtyDomain::Whole,
4393 &mut dirty_coords,
4394 )?;
4395 }
4396
4397 Ok(dirty_coords)
4398 }
4399
4400 fn demote_spans_for_structural_op(
4410 &mut self,
4411 op: StructuralOp,
4412 affected_region: Region,
4413 ) -> Result<(), crate::engine::EditorError> {
4414 if op.count() == 0 {
4415 return Ok(());
4416 }
4417 self.demote_spans_for_structural_op_impl(Some(op), affected_region, true)
4418 }
4419
4420 fn demote_spans_for_structural_op_impl(
4421 &mut self,
4422 op: Option<StructuralOp>,
4423 affected_region: Region,
4424 clear_computed_overlays: bool,
4425 ) -> Result<(), crate::engine::EditorError> {
4426 struct SpanPlan {
4427 span_ref: FormulaSpanRef,
4428 sheet_id: SheetId,
4429 ast: ASTNode,
4430 origin_row: u32,
4431 origin_col: u32,
4432 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4433 placements: Vec<(u32, u32)>,
4434 }
4435
4436 fn substitute_literal_slots_for_template_placement(
4437 ast: &ASTNode,
4438 binding: &[LiteralValue],
4439 ) -> ASTNode {
4440 fn clone_with_slots(
4441 ast: &ASTNode,
4442 binding: &[LiteralValue],
4443 next: &mut usize,
4444 in_array: bool,
4445 ) -> ASTNode {
4446 let node_type = match &ast.node_type {
4447 ASTNodeType::Literal(_) if !in_array => {
4448 let value = binding.get(*next).cloned().unwrap_or(LiteralValue::Empty);
4449 *next = next.saturating_add(1);
4450 ASTNodeType::Literal(value)
4451 }
4452 ASTNodeType::Literal(value) => ASTNodeType::Literal(value.clone()),
4453 ASTNodeType::Reference {
4454 original,
4455 reference,
4456 } => ASTNodeType::Reference {
4457 original: original.clone(),
4458 reference: reference.clone(),
4459 },
4460 ASTNodeType::UnaryOp { op, expr } => ASTNodeType::UnaryOp {
4461 op: op.clone(),
4462 expr: Box::new(clone_with_slots(expr, binding, next, in_array)),
4463 },
4464 ASTNodeType::BinaryOp { op, left, right } => ASTNodeType::BinaryOp {
4465 op: op.clone(),
4466 left: Box::new(clone_with_slots(left, binding, next, in_array)),
4467 right: Box::new(clone_with_slots(right, binding, next, in_array)),
4468 },
4469 ASTNodeType::Function { name, args } => ASTNodeType::Function {
4470 name: name.clone(),
4471 args: args
4472 .iter()
4473 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4474 .collect(),
4475 },
4476 ASTNodeType::Call { callee, args } => ASTNodeType::Call {
4477 callee: Box::new(clone_with_slots(callee, binding, next, in_array)),
4478 args: args
4479 .iter()
4480 .map(|arg| clone_with_slots(arg, binding, next, in_array))
4481 .collect(),
4482 },
4483 ASTNodeType::Array(rows) => ASTNodeType::Array(
4484 rows.iter()
4485 .map(|row| {
4486 row.iter()
4487 .map(|cell| clone_with_slots(cell, binding, next, true))
4488 .collect()
4489 })
4490 .collect(),
4491 ),
4492 };
4493 ASTNode::new(node_type, ast.source_token.clone())
4494 }
4495 let mut next = 0usize;
4496 clone_with_slots(ast, binding, &mut next, false)
4497 }
4498
4499 let span_refs = self.graph.formula_authority().active_span_refs();
4500 if span_refs.is_empty() {
4501 return Ok(());
4502 }
4503 let dirty_span_coords = if clear_computed_overlays {
4504 FxHashSet::default()
4505 } else {
4506 self.compute_current_formula_plane_dirty_result_coords()?
4507 };
4508
4509 struct ShiftPlan {
4510 span_ref: FormulaSpanRef,
4511 template_id: crate::formula_plane::ids::FormulaTemplateId,
4512 new_origin_row: u32,
4513 new_origin_col: u32,
4514 new_domain: crate::formula_plane::runtime::PlacementDomain,
4515 new_read_summary: Option<SpanReadSummary>,
4516 binding_set_id: Option<crate::formula_plane::runtime::SpanBindingSetId>,
4517 force_binding_residual_axes: bool,
4518 }
4519
4520 fn checked_shift_u32(value: u32, delta: i64) -> Option<u32> {
4521 u32::try_from(i64::from(value).checked_add(delta)?).ok()
4522 }
4523
4524 fn shifted_read_summary(
4525 read_summary: &SpanReadSummary,
4526 new_result_region: Region,
4527 op: StructuralOp,
4528 row_delta: i64,
4529 col_delta: i64,
4530 ) -> Option<SpanReadSummary> {
4531 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4532 for dependency in &read_summary.dependencies {
4533 let read_region = match op.classify_region(dependency.read_region) {
4534 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4535 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4536 dependency.read_region
4537 }
4538 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4539 ..
4540 } => dependency
4541 .read_region
4542 .project_through_axis_shift(row_delta, col_delta)?,
4543 crate::formula_plane::structural_shift::AxisShiftCase::Straddles
4544 | crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4545 return None;
4546 }
4547 };
4548 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4549 read_region,
4550 projection: dependency.projection,
4551 });
4552 }
4553 Some(SpanReadSummary {
4554 result_region: new_result_region,
4555 dependencies,
4556 })
4557 }
4558
4559 fn compact_axis_through_delete(
4560 min: u32,
4561 max: u32,
4562 start: u32,
4563 count: u32,
4564 ) -> Option<(u32, u32)> {
4565 let end = start.saturating_add(count);
4566 if max < start || min >= end {
4567 return Some((min.saturating_sub(count), max.saturating_sub(count)));
4568 }
4569 let keeps_left = min < start;
4570 let keeps_right = max >= end;
4571 match (keeps_left, keeps_right) {
4572 (false, false) => None,
4573 (true, false) => Some((min, start.checked_sub(1)?)),
4574 (false, true) => Some((start, max.checked_sub(count)?)),
4575 (true, true) => Some((min, max.checked_sub(count)?)),
4576 }
4577 }
4578
4579 fn compact_domain_through_delete(
4580 domain: &PlacementDomain,
4581 op: StructuralOp,
4582 ) -> Option<PlacementDomain> {
4583 match (domain, op) {
4584 (
4585 PlacementDomain::RowRun {
4586 sheet_id,
4587 row_start,
4588 row_end,
4589 col,
4590 },
4591 StructuralOp::DeleteRows { start, count, .. },
4592 ) => {
4593 let (row_start, row_end) =
4594 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4595 Some(PlacementDomain::row_run(
4596 *sheet_id, row_start, row_end, *col,
4597 ))
4598 }
4599 (
4600 PlacementDomain::Rect {
4601 sheet_id,
4602 row_start,
4603 row_end,
4604 col_start,
4605 col_end,
4606 },
4607 StructuralOp::DeleteRows { start, count, .. },
4608 ) => {
4609 let (row_start, row_end) =
4610 compact_axis_through_delete(*row_start, *row_end, start, count)?;
4611 Some(PlacementDomain::rect(
4612 *sheet_id, row_start, row_end, *col_start, *col_end,
4613 ))
4614 }
4615 (
4616 PlacementDomain::ColRun {
4617 sheet_id,
4618 row,
4619 col_start,
4620 col_end,
4621 },
4622 StructuralOp::DeleteColumns { start, count, .. },
4623 ) => {
4624 let (col_start, col_end) =
4625 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4626 Some(PlacementDomain::col_run(
4627 *sheet_id, *row, col_start, col_end,
4628 ))
4629 }
4630 (
4631 PlacementDomain::Rect {
4632 sheet_id,
4633 row_start,
4634 row_end,
4635 col_start,
4636 col_end,
4637 },
4638 StructuralOp::DeleteColumns { start, count, .. },
4639 ) => {
4640 let (col_start, col_end) =
4641 compact_axis_through_delete(*col_start, *col_end, start, count)?;
4642 Some(PlacementDomain::rect(
4643 *sheet_id, *row_start, *row_end, col_start, col_end,
4644 ))
4645 }
4646 _ => None,
4647 }
4648 }
4649
4650 fn compact_axis_range_through_delete(
4651 axis: crate::formula_plane::region_index::AxisRange,
4652 start: u32,
4653 count: u32,
4654 ) -> Option<crate::formula_plane::region_index::AxisRange> {
4655 use crate::formula_plane::region_index::AxisRange;
4656 match axis {
4657 AxisRange::Point(point) => compact_axis_through_delete(point, point, start, count)
4658 .map(|(point, _)| AxisRange::Point(point)),
4659 AxisRange::Span(min, max) => compact_axis_through_delete(min, max, start, count)
4660 .map(|(min, max)| AxisRange::Span(min, max)),
4661 AxisRange::All => Some(AxisRange::All),
4662 AxisRange::From(_) | AxisRange::To(_) => None,
4663 }
4664 }
4665
4666 fn compact_region_through_delete(region: Region, op: StructuralOp) -> Option<Region> {
4667 let (rows, cols) = region.axis_ranges();
4668 match op {
4669 StructuralOp::DeleteRows {
4670 sheet_id,
4671 start,
4672 count,
4673 } if region.sheet_id() == sheet_id => Some(Region {
4674 sheet_id,
4675 rows: compact_axis_range_through_delete(rows, start, count)?,
4676 cols,
4677 }),
4678 StructuralOp::DeleteColumns {
4679 sheet_id,
4680 start,
4681 count,
4682 } if region.sheet_id() == sheet_id => Some(Region {
4683 sheet_id,
4684 rows,
4685 cols: compact_axis_range_through_delete(cols, start, count)?,
4686 }),
4687 _ => Some(region),
4688 }
4689 }
4690
4691 fn compact_read_summary_through_delete(
4692 read_summary: &SpanReadSummary,
4693 new_result_region: Region,
4694 op: StructuralOp,
4695 ) -> Option<SpanReadSummary> {
4696 let mut dependencies = Vec::with_capacity(read_summary.dependencies.len());
4697 for dependency in &read_summary.dependencies {
4698 let read_region = match op.classify_region(dependency.read_region) {
4699 crate::formula_plane::structural_shift::AxisShiftCase::OtherSheet
4700 | crate::formula_plane::structural_shift::AxisShiftCase::EntirelyBelow => {
4701 dependency.read_region
4702 }
4703 crate::formula_plane::structural_shift::AxisShiftCase::EntirelyAboveShift {
4704 ..
4705 } => {
4706 let (row_delta, col_delta) = op.axis_shift_delta();
4707 dependency
4708 .read_region
4709 .project_through_axis_shift(row_delta, col_delta)?
4710 }
4711 crate::formula_plane::structural_shift::AxisShiftCase::Straddles => {
4712 compact_region_through_delete(dependency.read_region, op)?
4713 }
4714 crate::formula_plane::structural_shift::AxisShiftCase::DeleteFullyContains => {
4715 return None;
4716 }
4717 };
4718 dependencies.push(crate::formula_plane::producer::SpanReadDependency {
4719 read_region,
4720 projection: dependency.projection,
4721 });
4722 }
4723 Some(SpanReadSummary {
4724 result_region: new_result_region,
4725 dependencies,
4726 })
4727 }
4728
4729 fn domain_origin_1_based(domain: &PlacementDomain) -> (u32, u32) {
4730 match domain {
4731 PlacementDomain::RowRun { row_start, col, .. } => (row_start + 1, col + 1),
4732 PlacementDomain::ColRun { row, col_start, .. } => (row + 1, col_start + 1),
4733 PlacementDomain::Rect {
4734 row_start,
4735 col_start,
4736 ..
4737 } => (row_start + 1, col_start + 1),
4738 }
4739 }
4740
4741 let mut shift_plans = Vec::new();
4742 let mut remove_refs = Vec::new();
4743 let mut demote_refs = Vec::new();
4744 for span_ref in span_refs {
4745 let authority = self.graph.formula_authority();
4746 let Some(span) = authority.plane.spans.get(span_ref) else {
4747 continue;
4748 };
4749 let read_summary = span
4750 .read_summary_id
4751 .and_then(|id| authority.plane.span_read_summaries.get(id));
4752 let Some(op) = op else {
4753 let result_region_affected =
4758 Self::span_result_region_intersects_affected(span, &affected_region);
4759 let read_region_affected = Self::span_any_read_region_intersects_affected(
4760 &authority.plane,
4761 span,
4762 &affected_region,
4763 );
4764 if result_region_affected || read_region_affected {
4765 demote_refs.push(span_ref);
4766 }
4767 continue;
4768 };
4769 match classify_span_for_op(span, read_summary, op) {
4770 SpanShiftPlan::NoOp => {}
4771 SpanShiftPlan::Remove => {
4772 remove_refs.push(span_ref);
4773 }
4774 SpanShiftPlan::Demote {
4775 reason:
4776 crate::formula_plane::structural_shift::SpanDemoteReason::DeletePartiallyOverlaps,
4777 } => {
4778 let binding_compaction_safe = span
4779 .binding_set_id
4780 .and_then(|id| authority.plane.binding_sets.get(id))
4781 .is_none_or(|binding_set| binding_set.is_single_literal_binding());
4782 if binding_compaction_safe
4783 && let Some(new_domain) = compact_domain_through_delete(&span.domain, op)
4784 {
4785 let new_result_region = Region::from_domain(&new_domain);
4786 let new_read_summary = if let Some(summary) = read_summary {
4787 compact_read_summary_through_delete(summary, new_result_region, op)
4788 } else {
4789 None
4790 };
4791 if read_summary.is_none() || new_read_summary.is_some() {
4792 let (new_origin_row, new_origin_col) = domain_origin_1_based(&new_domain);
4793 let Some(template) = authority.plane.templates.get(span.template_id)
4794 else {
4795 return Err(ExcelError::new(ExcelErrorKind::Ref)
4796 .with_message(
4797 "FormulaPlane delete compaction found a span with a missing template",
4798 )
4799 .into());
4800 };
4801 let force_binding_residual_axes = span
4802 .binding_set_id
4803 .and_then(|id| authority.plane.binding_sets.get(id))
4804 .is_some_and(|binding_set| {
4805 !binding_set.value_ref_slots.is_empty()
4806 && (new_origin_row != template.origin_row
4807 || new_origin_col != template.origin_col)
4808 });
4809 shift_plans.push(ShiftPlan {
4810 span_ref,
4811 template_id: span.template_id,
4812 new_origin_row,
4813 new_origin_col,
4814 new_domain,
4815 new_read_summary,
4816 binding_set_id: span.binding_set_id,
4817 force_binding_residual_axes,
4818 });
4819 } else {
4820 demote_refs.push(span_ref);
4821 }
4822 } else {
4823 demote_refs.push(span_ref);
4824 }
4825 }
4826 SpanShiftPlan::Demote { .. } => {
4827 demote_refs.push(span_ref);
4828 }
4829 SpanShiftPlan::Shift {
4830 row_delta,
4831 col_delta,
4832 origin_row_delta,
4833 origin_col_delta,
4834 } => {
4835 let Some(template) = authority.plane.templates.get(span.template_id) else {
4836 return Err(ExcelError::new(ExcelErrorKind::Ref)
4837 .with_message("FormulaPlane shift found a span with a missing template")
4838 .into());
4839 };
4840 let Some(new_origin_row) =
4841 checked_shift_u32(template.origin_row, origin_row_delta)
4842 else {
4843 return Err(ExcelError::new(ExcelErrorKind::Ref)
4844 .with_message("FormulaPlane shift overflowed template origin row")
4845 .into());
4846 };
4847 let Some(new_origin_col) =
4848 checked_shift_u32(template.origin_col, origin_col_delta)
4849 else {
4850 return Err(ExcelError::new(ExcelErrorKind::Ref)
4851 .with_message("FormulaPlane shift overflowed template origin column")
4852 .into());
4853 };
4854 let Some(new_domain) =
4855 span.domain.project_through_axis_shift(row_delta, col_delta)
4856 else {
4857 return Err(ExcelError::new(ExcelErrorKind::Ref)
4858 .with_message("FormulaPlane shift overflowed span domain")
4859 .into());
4860 };
4861 let new_result_region = Region::from_domain(&new_domain);
4862 let new_read_summary = if let Some(summary) = read_summary {
4863 Some(
4864 shifted_read_summary(
4865 summary,
4866 new_result_region,
4867 op,
4868 row_delta,
4869 col_delta,
4870 )
4871 .ok_or_else(|| {
4872 ExcelError::new(ExcelErrorKind::Ref).with_message(
4873 "FormulaPlane shift could not project read summary",
4874 )
4875 })?,
4876 )
4877 } else {
4878 None
4879 };
4880 let force_binding_residual_axes = span
4881 .binding_set_id
4882 .and_then(|id| authority.plane.binding_sets.get(id))
4883 .is_some_and(|binding_set| {
4884 !binding_set.value_ref_slots.is_empty()
4885 && (origin_row_delta != 0 || origin_col_delta != 0)
4886 });
4887 shift_plans.push(ShiftPlan {
4888 span_ref,
4889 template_id: span.template_id,
4890 new_origin_row,
4891 new_origin_col,
4892 new_domain,
4893 new_read_summary,
4894 binding_set_id: span.binding_set_id,
4895 force_binding_residual_axes,
4896 });
4897 }
4898 }
4899 }
4900 if !shift_plans.is_empty() || !remove_refs.is_empty() {
4901 let authority = self.graph.formula_authority_mut();
4902 for span_ref in remove_refs {
4903 authority.plane.remove_overlays_for_source_span(span_ref);
4904 authority.plane.remove_span(span_ref);
4905 }
4906 for plan in shift_plans {
4907 let Some(template_id) = authority.plane.intern_shifted_template_origin(
4908 plan.template_id,
4909 plan.new_origin_row,
4910 plan.new_origin_col,
4911 ) else {
4912 return Err(ExcelError::new(ExcelErrorKind::Ref)
4913 .with_message("FormulaPlane shift could not clone template origin")
4914 .into());
4915 };
4916 if let Some(binding_set_id) = plan.binding_set_id {
4917 let Some(template) = authority.plane.templates.get(template_id) else {
4918 return Err(ExcelError::new(ExcelErrorKind::Ref)
4919 .with_message("FormulaPlane shift could not find shifted template")
4920 .into());
4921 };
4922 let (ast_id, origin_row, origin_col) =
4923 (template.ast_id, template.origin_row, template.origin_col);
4924 authority.plane.set_binding_template_anchor(
4925 binding_set_id,
4926 ast_id,
4927 origin_row,
4928 origin_col,
4929 );
4930 }
4931 let read_summary_id = plan
4932 .new_read_summary
4933 .map(|summary| authority.plane.insert_span_read_summary(summary));
4934 let result_region = ResultRegion::scalar_cells(plan.new_domain.clone());
4935 if !authority.plane.replace_span_geometry(
4936 plan.span_ref,
4937 template_id,
4938 plan.new_domain,
4939 result_region,
4940 read_summary_id,
4941 ) {
4942 return Err(ExcelError::new(ExcelErrorKind::Ref)
4943 .with_message("FormulaPlane shift could not update span geometry")
4944 .into());
4945 }
4946 if plan.force_binding_residual_axes
4947 && let Some(binding_set_id) = plan.binding_set_id
4948 {
4949 authority.plane.force_binding_residual_axes(binding_set_id);
4958 }
4959 }
4960 authority.rebuild_indexes();
4961 self.formula_plane_indexes_epoch_seen = 0;
4962 }
4963
4964 let mut span_plans = Vec::new();
4965 for span_ref in demote_refs {
4966 let authority = self.graph.formula_authority();
4967 let Some(span) = authority.plane.spans.get(span_ref) else {
4968 continue;
4969 };
4970 let Some(template) = authority.plane.templates.get(span.template_id) else {
4971 return Err(ExcelError::new(ExcelErrorKind::Ref)
4972 .with_message("FormulaPlane demotion found a span with a missing template")
4973 .into());
4974 };
4975 let ast = self
4976 .graph
4977 .data_store()
4978 .retrieve_ast(template.ast_id, self.graph.sheet_reg())
4979 .ok_or_else(|| {
4980 ExcelError::new(ExcelErrorKind::Ref)
4981 .with_message("FormulaPlane demotion could not retrieve the template AST")
4982 })?;
4983 let placements = span
4984 .domain
4985 .iter()
4986 .map(|placement| (placement.row + 1, placement.col + 1))
4987 .collect();
4988 span_plans.push(SpanPlan {
4989 span_ref,
4990 sheet_id: span.sheet_id,
4991 ast,
4992 origin_row: template.origin_row,
4993 origin_col: template.origin_col,
4994 binding_set_id: span.binding_set_id,
4995 placements,
4996 });
4997 }
4998 if span_plans.is_empty() {
4999 return Ok(());
5000 }
5001
5002 let mut relocated = Vec::new();
5003 let mut placement_cells = Vec::new();
5004 for plan in &span_plans {
5005 for &(row, col) in &plan.placements {
5006 let row_delta = i64::from(row) - i64::from(plan.origin_row);
5007 let col_delta = i64::from(col) - i64::from(plan.origin_col);
5008 let bound_ast = if let Some(binding_set_id) = plan.binding_set_id {
5009 let authority = self.graph.formula_authority();
5010 if let Some(binding_set) = authority.plane.binding_sets.get(binding_set_id) {
5011 if binding_set.is_single_literal_binding() {
5012 plan.ast.clone()
5013 } else {
5014 let placement = crate::formula_plane::runtime::PlacementCoord::new(
5015 plan.sheet_id,
5016 row.saturating_sub(1),
5017 col.saturating_sub(1),
5018 );
5019 let binding =
5020 authority.plane.spans.get(plan.span_ref).and_then(|span| {
5021 binding_set
5022 .literal_bindings_for_placement(&span.domain, placement)
5023 });
5024 if let Some(binding) = binding {
5025 substitute_literal_slots_for_template_placement(
5026 &plan.ast,
5027 binding.as_ref(),
5028 )
5029 } else {
5030 plan.ast.clone()
5031 }
5032 }
5033 } else {
5034 plan.ast.clone()
5035 }
5036 } else {
5037 plan.ast.clone()
5038 };
5039 let ast = relocate_ast_for_template_placement(&bound_ast, row_delta, col_delta)?;
5040 relocated.push((plan.sheet_id, row, col, ast));
5041 placement_cells.push((plan.sheet_id, row, col));
5042 }
5043 }
5044 let planned_by_sheet = {
5045 let mut pipeline = self.ingest_pipeline();
5046 let mut planned_by_sheet: BTreeMap<
5047 SheetId,
5048 Vec<(u32, u32, AstNodeId, DependencyPlanRow)>,
5049 > = BTreeMap::new();
5050 for (formula_sheet_id, row, col, ast) in relocated {
5051 let placement =
5052 CellRef::new(formula_sheet_id, Coord::from_excel(row, col, true, true));
5053 let ingested =
5054 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?;
5055 planned_by_sheet.entry(formula_sheet_id).or_default().push((
5056 row,
5057 col,
5058 ingested.ast_id,
5059 ingested.dep_plan,
5060 ));
5061 }
5062 planned_by_sheet
5063 };
5064 {
5065 let authority = self.graph.formula_authority_mut();
5066 for plan in &span_plans {
5067 authority
5068 .plane
5069 .remove_overlays_for_source_span(plan.span_ref);
5070 authority.plane.remove_span(plan.span_ref);
5071 }
5072 authority.rebuild_indexes();
5073 }
5074 if clear_computed_overlays {
5075 self.clear_computed_overlay_cells_in_region(&placement_cells, &affected_region);
5082 }
5083 for (formula_sheet_id, planned) in planned_by_sheet {
5084 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
5085 self.graph
5086 .bulk_set_formulas_with_plans(&sheet_name, planned)?;
5087 }
5088 if !clear_computed_overlays {
5089 for (formula_sheet_id, row, col) in &placement_cells {
5090 let row0 = row.saturating_sub(1);
5091 let col0 = col.saturating_sub(1);
5092 if dirty_span_coords.contains(&(*formula_sheet_id, row0, col0)) {
5093 continue;
5094 }
5095 let cell =
5096 CellRef::new(*formula_sheet_id, Coord::from_excel(*row, *col, true, true));
5097 if let Some(&vertex_id) = self.graph.get_vertex_id_for_address(&cell) {
5098 self.graph.set_dirty(vertex_id, false);
5099 }
5100 }
5101 }
5102 self.formula_plane_indexes_epoch_seen = 0;
5103 Ok(())
5104 }
5105
5106 fn collect_cyclic_span_refs(
5111 &self,
5112 schedule: &MixedSchedule,
5113 span_refs_by_id: &BTreeMap<FormulaSpanId, FormulaSpanRef>,
5114 ) -> Vec<FormulaSpanRef> {
5115 let mut refs = Vec::new();
5116 for fallback in &schedule.fallbacks {
5117 if fallback.reason != MixedScheduleFallbackReason::CycleDetected {
5118 continue;
5119 }
5120 if let FormulaProducerId::Span(span_id) = fallback.producer
5121 && let Some(span_ref) = span_refs_by_id.get(&span_id)
5122 && !refs.contains(span_ref)
5123 {
5124 refs.push(*span_ref);
5125 }
5126 }
5127 refs
5128 }
5129
5130 fn demote_cyclic_spans(&mut self, span_refs: &[FormulaSpanRef]) -> Result<(), ExcelError> {
5138 let mut regions: Vec<Region> = Vec::new();
5139 {
5140 let authority = self.graph.formula_authority();
5141 for span_ref in span_refs {
5142 if let Some(span) = authority.plane.spans.get(*span_ref) {
5143 regions.push(Region::from_domain(&span.domain));
5144 }
5145 }
5146 }
5147 for region in regions {
5148 self.demote_spans_preserving_computed_overlays(region.sheet_id(), region)
5149 .map_err(|err| {
5150 ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
5151 "FormulaPlane cycle-member span demotion failed: {err:?}"
5152 ))
5153 })?;
5154 }
5155 self.formula_plane_cycle_member_span_demotions = self
5156 .formula_plane_cycle_member_span_demotions
5157 .saturating_add(span_refs.len() as u64);
5158 Self::record_shadow_fallback_reason(
5162 &mut self.formula_ingest_report_total,
5163 PlacementFallbackReason::CycleMember,
5164 span_refs.len() as u64,
5165 );
5166 Ok(())
5167 }
5168
5169 fn evaluate_legacy_cycle_prepass(&mut self) -> Result<usize, ExcelError> {
5184 let dirty = self.graph.get_evaluation_vertices();
5185 if dirty.is_empty() {
5186 return Ok(0);
5187 }
5188 let (schedule, _vdeps, _meta) = self.create_evaluation_schedule(&dirty)?;
5189 let dirty_set: FxHashSet<VertexId> = dirty.iter().copied().collect();
5190 let mut cycle_errors = 0usize;
5191 let mut stamped_vertices: Vec<VertexId> = Vec::new();
5192 for &unit in &schedule.units {
5193 let ScheduleUnit::Cycle(i) = unit else {
5194 continue;
5195 };
5196 let members = schedule.unit_cycle(i);
5197 let stamped = self.handle_cycle_unit(members, None, Some(&dirty_set), None)?;
5198 if stamped > 0 {
5199 cycle_errors += 1;
5200 }
5201 stamped_vertices.extend(members.iter().copied());
5202 }
5203 if !stamped_vertices.is_empty() {
5208 self.graph.clear_dirty_flags(&stamped_vertices);
5209 }
5210 Ok(cycle_errors)
5211 }
5212
5213 pub fn insert_rows(
5215 &mut self,
5216 sheet: &str,
5217 before: u32,
5218 count: u32,
5219 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5220 {
5221 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5222 let sheet_id = self.ensure_known_sheet_id(sheet)?;
5223 let before0 = before.saturating_sub(1);
5224 let affected_region = Self::structural_row_region(sheet_id, before0);
5225 let op = StructuralOp::InsertRows {
5226 sheet_id,
5227 before: before0,
5228 count,
5229 };
5230 self.demote_spans_for_structural_op(op, affected_region)?;
5231 let summary = {
5232 let mut editor = VertexEditor::new(&mut self.graph);
5233 editor.insert_rows(sheet_id, before0, count)?
5234 };
5235 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5236 let before0 = before0 as usize;
5237 asheet.insert_rows(before0, count as usize);
5238 }
5239 self.mark_moved_formula_vertices_dirty(&summary);
5240 self.clear_computed_overlay_after_row(sheet, before0 as usize);
5241 self.shift_row_visibility_insert(sheet_id, before0, count);
5242 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5243 self.mark_topology_edited();
5244 Ok(summary)
5245 }
5246
5247 pub fn delete_rows(
5249 &mut self,
5250 sheet: &str,
5251 start: u32,
5252 count: u32,
5253 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5254 {
5255 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5256 let sheet_id = self.ensure_known_sheet_id(sheet)?;
5257 let start0 = start.saturating_sub(1);
5258 let affected_region = Self::structural_row_region(sheet_id, start0);
5259 let op = StructuralOp::DeleteRows {
5260 sheet_id,
5261 start: start0,
5262 count,
5263 };
5264 self.demote_spans_for_structural_op(op, affected_region)?;
5265 let summary = {
5266 let mut editor = VertexEditor::new(&mut self.graph);
5267 editor.delete_rows(sheet_id, start0, count)?
5268 };
5269 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5270 let start0 = start0 as usize;
5271 asheet.delete_rows(start0, count as usize);
5272 }
5273 self.mark_moved_formula_vertices_dirty(&summary);
5274 self.clear_computed_overlay_after_row(sheet, start0 as usize);
5275 self.shift_row_visibility_delete(sheet_id, start0, count);
5276 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5277 self.mark_topology_edited();
5278 Ok(summary)
5279 }
5280
5281 pub fn insert_columns(
5283 &mut self,
5284 sheet: &str,
5285 before: u32,
5286 count: u32,
5287 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5288 {
5289 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5290 let sheet_id = self.graph.sheet_id(sheet).ok_or(
5291 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
5292 name: sheet.to_string(),
5293 reason: "Unknown sheet".to_string(),
5294 },
5295 )?;
5296 let before0 = before.saturating_sub(1);
5297 let affected_region = Self::structural_col_region(sheet_id, before0);
5298 let op = StructuralOp::InsertColumns {
5299 sheet_id,
5300 before: before0,
5301 count,
5302 };
5303 self.demote_spans_for_structural_op(op, affected_region)?;
5304 let summary = {
5305 let mut editor = VertexEditor::new(&mut self.graph);
5306 editor.insert_columns(sheet_id, before0, count)?
5307 };
5308 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5309 let before0 = before0 as usize;
5310 asheet.insert_columns(before0, count as usize);
5311 }
5312 self.mark_moved_formula_vertices_dirty(&summary);
5313 self.clear_computed_overlay_after_col(sheet, before0 as usize);
5314 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5315 self.mark_topology_edited();
5316 Ok(summary)
5317 }
5318
5319 pub fn delete_columns(
5321 &mut self,
5322 sheet: &str,
5323 start: u32,
5324 count: u32,
5325 ) -> Result<crate::engine::graph::editor::vertex_editor::ShiftSummary, crate::engine::EditorError>
5326 {
5327 use crate::engine::graph::editor::vertex_editor::VertexEditor;
5328 let sheet_id = self.graph.sheet_id(sheet).ok_or(
5329 crate::engine::graph::editor::vertex_editor::EditorError::InvalidName {
5330 name: sheet.to_string(),
5331 reason: "Unknown sheet".to_string(),
5332 },
5333 )?;
5334 let start0 = start.saturating_sub(1);
5335 let affected_region = Self::structural_col_region(sheet_id, start0);
5336 let op = StructuralOp::DeleteColumns {
5337 sheet_id,
5338 start: start0,
5339 count,
5340 };
5341 self.demote_spans_for_structural_op(op, affected_region)?;
5342 let summary = {
5343 let mut editor = VertexEditor::new(&mut self.graph);
5344 editor.delete_columns(sheet_id, start0, count)?
5345 };
5346 if let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) {
5347 let start0 = start0 as usize;
5348 asheet.delete_columns(start0, count as usize);
5349 }
5350 self.mark_moved_formula_vertices_dirty(&summary);
5351 self.clear_computed_overlay_after_col(sheet, start0 as usize);
5352 self.record_formula_plane_structural_change(StructuralScope::Region(affected_region));
5353 self.mark_topology_edited();
5354 Ok(summary)
5355 }
5356 fn arrow_used_row_bounds(
5358 &self,
5359 sheet: &str,
5360 start_col: u32,
5361 end_col: u32,
5362 ) -> Option<(u32, u32)> {
5363 let a = self.sheet_store().sheet(sheet)?;
5364 if a.columns.is_empty() {
5365 return None;
5366 }
5367 let sc0 = start_col.saturating_sub(1) as usize;
5368 let ec0 = end_col.saturating_sub(1) as usize;
5369 let col_hi = a.columns.len().saturating_sub(1);
5370 if sc0 > col_hi {
5371 return None;
5372 }
5373 let ec0 = ec0.min(col_hi);
5374 let snap = self.data_snapshot_id();
5376 let mut min_r0: Option<usize> = None;
5377 for ci in sc0..=ec0 {
5378 let sheet_id = self.graph.sheet_id(sheet)?;
5379 if let Some((Some(mv), _)) = self.row_bounds_cache.read().ok().and_then(|g| {
5380 g.as_ref()
5381 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
5382 }) {
5383 let mv = mv as usize;
5384 min_r0 = Some(min_r0.map(|m| m.min(mv)).unwrap_or(mv));
5385 continue;
5386 }
5387 let (min_c, max_c) = Self::scan_column_used_bounds(a, ci);
5389 if let Ok(mut g) = self.row_bounds_cache.write() {
5390 g.get_or_insert_with(|| RowBoundsCache::new(snap))
5391 .put_row_bounds(sheet_id, ci, snap, (min_c, max_c));
5392 }
5393 if let Some(m) = min_c {
5394 min_r0 = Some(min_r0.map(|mm| mm.min(m as usize)).unwrap_or(m as usize));
5395 }
5396 }
5397 min_r0?;
5398 let mut max_r0: Option<usize> = None;
5399 for ci in sc0..=ec0 {
5400 let sheet_id = self.graph.sheet_id(sheet)?;
5401 if let Some((_, Some(mv))) = self.row_bounds_cache.read().ok().and_then(|g| {
5402 g.as_ref()
5403 .and_then(|c| c.get_row_bounds(sheet_id, ci, snap))
5404 }) {
5405 let mv = mv as usize;
5406 max_r0 = Some(max_r0.map(|m| m.max(mv)).unwrap_or(mv));
5407 continue;
5408 }
5409 let (_min_c, max_c) = Self::scan_column_used_bounds(a, ci);
5410 if let Ok(mut g) = self.row_bounds_cache.write() {
5411 g.get_or_insert_with(|| RowBoundsCache::new(snap))
5412 .put_row_bounds(sheet_id, ci, snap, (_min_c, max_c));
5413 }
5414 if let Some(m) = max_c {
5415 max_r0 = Some(max_r0.map(|mm| mm.max(m as usize)).unwrap_or(m as usize));
5416 }
5417 }
5418 match (min_r0, max_r0) {
5419 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
5420 _ => None,
5421 }
5422 }
5423
5424 fn scan_column_used_bounds(
5425 a: &crate::arrow_store::ArrowSheet,
5426 ci: usize,
5427 ) -> (Option<u32>, Option<u32>) {
5428 let col = &a.columns[ci];
5429
5430 let mut min_r0: Option<u32> = None;
5432 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5433 let tags = chunk.type_tag.values();
5434 for (off, &t) in tags.iter().enumerate() {
5435 let overlay_non_empty = chunk
5436 .overlay
5437 .get(off)
5438 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5439 .unwrap_or(false)
5440 || chunk
5441 .computed_overlay
5442 .get(off)
5443 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5444 .unwrap_or(false);
5445 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5446 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5447 break;
5448 };
5449 let row0 = chunk_start + off;
5450 min_r0 = Some(row0 as u32);
5451 break;
5452 }
5453 }
5454 if min_r0.is_some() {
5455 break;
5456 }
5457 }
5458 if min_r0.is_none() && !col.sparse_chunks.is_empty() {
5459 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5460 sparse_idxs.sort_unstable();
5461 for chunk_idx in sparse_idxs {
5462 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5463 continue;
5464 };
5465 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5466 continue;
5467 };
5468 let tags = chunk.type_tag.values();
5469 for (off, &t) in tags.iter().enumerate() {
5470 let overlay_non_empty = chunk
5471 .overlay
5472 .get(off)
5473 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5474 .unwrap_or(false)
5475 || chunk
5476 .computed_overlay
5477 .get(off)
5478 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5479 .unwrap_or(false);
5480 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5481 let row0 = chunk_start + off;
5482 min_r0 = Some(row0 as u32);
5483 break;
5484 }
5485 }
5486 if min_r0.is_some() {
5487 break;
5488 }
5489 }
5490 }
5491
5492 let mut max_r0: Option<u32> = None;
5494 if !col.sparse_chunks.is_empty() {
5495 let mut sparse_idxs: Vec<usize> = col.sparse_chunks.keys().copied().collect();
5496 sparse_idxs.sort_unstable_by(|a, b| b.cmp(a));
5497 for chunk_idx in sparse_idxs {
5498 let Some(chunk) = col.sparse_chunks.get(&chunk_idx) else {
5499 continue;
5500 };
5501 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5502 continue;
5503 };
5504 let tags = chunk.type_tag.values();
5505 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5506 let overlay_non_empty = chunk
5507 .overlay
5508 .get(rev_idx)
5509 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5510 .unwrap_or(false)
5511 || chunk
5512 .computed_overlay
5513 .get(rev_idx)
5514 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5515 .unwrap_or(false);
5516 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5517 let row0 = chunk_start + rev_idx;
5518 max_r0 = Some(row0 as u32);
5519 break;
5520 }
5521 }
5522 if max_r0.is_some() {
5523 break;
5524 }
5525 }
5526 }
5527 if max_r0.is_none() {
5528 for (chunk_idx, chunk) in col.chunks.iter().enumerate().rev() {
5529 let tags = chunk.type_tag.values();
5530 for (rev_idx, &t) in tags.iter().enumerate().rev() {
5531 let overlay_non_empty = chunk
5532 .overlay
5533 .get(rev_idx)
5534 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5535 .unwrap_or(false)
5536 || chunk
5537 .computed_overlay
5538 .get(rev_idx)
5539 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5540 .unwrap_or(false);
5541 if overlay_non_empty || t != crate::arrow_store::TypeTag::Empty as u8 {
5542 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5543 break;
5544 };
5545 let row0 = chunk_start + rev_idx;
5546 max_r0 = Some(row0 as u32);
5547 break;
5548 }
5549 }
5550 if max_r0.is_some() {
5551 break;
5552 }
5553 }
5554 }
5555
5556 (min_r0, max_r0)
5557 }
5558
5559 fn arrow_used_col_bounds(
5561 &self,
5562 sheet: &str,
5563 start_row: u32,
5564 end_row: u32,
5565 ) -> Option<(u32, u32)> {
5566 let a = self.sheet_store().sheet(sheet)?;
5567 if a.columns.is_empty() {
5568 return None;
5569 }
5570 let sr0 = start_row.saturating_sub(1) as usize;
5571 let er0 = end_row.saturating_sub(1) as usize;
5572 if sr0 > er0 {
5573 return None;
5574 }
5575 let mut min_c0: Option<usize> = None;
5578 let mut max_c0: Option<usize> = None;
5579 for (ci, col) in a.columns.iter().enumerate() {
5581 let mut any_in_range = false;
5582
5583 let scan_chunk = |chunk_idx: usize, chunk: &crate::arrow_store::ColumnChunk| -> bool {
5584 let Some(&chunk_start) = a.chunk_starts.get(chunk_idx) else {
5585 return false;
5586 };
5587 let chunk_len = chunk.type_tag.len();
5588 if chunk_len == 0 {
5589 return false;
5590 }
5591 let chunk_end = chunk_start + chunk_len.saturating_sub(1);
5592 if sr0 > chunk_end || er0 < chunk_start {
5594 return false;
5595 }
5596 let start_off = sr0.max(chunk_start) - chunk_start;
5597 let end_off = er0.min(chunk_end) - chunk_start;
5598 let tags = chunk.type_tag.values();
5599 for off in start_off..=end_off {
5600 let overlay_non_empty = chunk
5601 .overlay
5602 .get(off)
5603 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5604 .unwrap_or(false)
5605 || chunk
5606 .computed_overlay
5607 .get(off)
5608 .map(|ov| !matches!(ov, crate::arrow_store::OverlayValue::Empty))
5609 .unwrap_or(false);
5610 if overlay_non_empty || tags[off] != crate::arrow_store::TypeTag::Empty as u8 {
5611 return true;
5612 }
5613 }
5614 false
5615 };
5616
5617 for (chunk_idx, chunk) in col.chunks.iter().enumerate() {
5618 if scan_chunk(chunk_idx, chunk) {
5619 any_in_range = true;
5620 break;
5621 }
5622 }
5623
5624 if !any_in_range && !col.sparse_chunks.is_empty() {
5625 for (&chunk_idx, chunk) in col.sparse_chunks.iter() {
5626 if scan_chunk(chunk_idx, chunk) {
5627 any_in_range = true;
5628 break;
5629 }
5630 }
5631 }
5632
5633 if any_in_range {
5634 min_c0 = Some(min_c0.map(|m| m.min(ci)).unwrap_or(ci));
5635 max_c0 = Some(max_c0.map(|m| m.max(ci)).unwrap_or(ci));
5636 }
5637 }
5638 match (min_c0, max_c0) {
5639 (Some(a0), Some(b0)) => Some(((a0 as u32) + 1, (b0 as u32) + 1)),
5640 _ => None,
5641 }
5642 }
5643
5644 fn formula_row_bounds_for_columns(
5645 &self,
5646 sheet: &str,
5647 start_col: u32,
5648 end_col: u32,
5649 ) -> Option<(u32, u32)> {
5650 let sheet_id = self.graph.sheet_id(sheet)?;
5651 let sc0 = start_col.saturating_sub(1);
5652 let ec0 = end_col.saturating_sub(1);
5653 let mut min_r0: Option<u32> = None;
5654 let mut max_r0: Option<u32> = None;
5655
5656 if let Some(index) = self.graph.sheet_index(sheet_id) {
5657 for vid in index.vertices_in_col_range(sc0, ec0) {
5658 if !matches!(
5659 self.graph.get_vertex_kind(vid),
5660 VertexKind::FormulaScalar | VertexKind::FormulaArray
5661 ) {
5662 continue;
5663 }
5664 let row0 = self.graph.vertex_coord(vid).row();
5665 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5666 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5667 }
5668 } else {
5669 for vid in self.graph.vertices_in_sheet(sheet_id) {
5670 if !matches!(
5671 self.graph.get_vertex_kind(vid),
5672 VertexKind::FormulaScalar | VertexKind::FormulaArray
5673 ) {
5674 continue;
5675 }
5676 let coord = self.graph.vertex_coord(vid);
5677 let col0 = coord.col();
5678 if col0 < sc0 || col0 > ec0 {
5679 continue;
5680 }
5681 let row0 = coord.row();
5682 min_r0 = Some(min_r0.map(|m| m.min(row0)).unwrap_or(row0));
5683 max_r0 = Some(max_r0.map(|m| m.max(row0)).unwrap_or(row0));
5684 }
5685 }
5686
5687 match (min_r0, max_r0) {
5688 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5689 _ => None,
5690 }
5691 }
5692
5693 fn formula_col_bounds_for_rows(
5694 &self,
5695 sheet: &str,
5696 start_row: u32,
5697 end_row: u32,
5698 ) -> Option<(u32, u32)> {
5699 let sheet_id = self.graph.sheet_id(sheet)?;
5700 let sr0 = start_row.saturating_sub(1);
5701 let er0 = end_row.saturating_sub(1);
5702 let mut min_c0: Option<u32> = None;
5703 let mut max_c0: Option<u32> = None;
5704
5705 if let Some(index) = self.graph.sheet_index(sheet_id) {
5706 for vid in index.vertices_in_row_range(sr0, er0) {
5707 if !matches!(
5708 self.graph.get_vertex_kind(vid),
5709 VertexKind::FormulaScalar | VertexKind::FormulaArray
5710 ) {
5711 continue;
5712 }
5713 let col0 = self.graph.vertex_coord(vid).col();
5714 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5715 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5716 }
5717 } else {
5718 for vid in self.graph.vertices_in_sheet(sheet_id) {
5719 if !matches!(
5720 self.graph.get_vertex_kind(vid),
5721 VertexKind::FormulaScalar | VertexKind::FormulaArray
5722 ) {
5723 continue;
5724 }
5725 let coord = self.graph.vertex_coord(vid);
5726 let row0 = coord.row();
5727 if row0 < sr0 || row0 > er0 {
5728 continue;
5729 }
5730 let col0 = coord.col();
5731 min_c0 = Some(min_c0.map(|m| m.min(col0)).unwrap_or(col0));
5732 max_c0 = Some(max_c0.map(|m| m.max(col0)).unwrap_or(col0));
5733 }
5734 }
5735
5736 match (min_c0, max_c0) {
5737 (Some(a0), Some(b0)) => Some((a0 + 1, b0 + 1)),
5738 _ => None,
5739 }
5740 }
5741
5742 fn union_used_bounds(
5743 first: Option<(u32, u32)>,
5744 second: Option<(u32, u32)>,
5745 ) -> Option<(u32, u32)> {
5746 match (first, second) {
5747 (Some((a0, b0)), Some((a1, b1))) => Some((a0.min(a1), b0.max(b1))),
5748 (Some(bounds), None) | (None, Some(bounds)) => Some(bounds),
5749 (None, None) => None,
5750 }
5751 }
5752
5753 fn mirror_value_to_overlay(&mut self, sheet: &str, row: u32, col: u32, value: &LiteralValue) {
5756 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5757 return;
5758 }
5759 if self.arrow_sheets.sheet(sheet).is_none() {
5760 self.arrow_sheets
5761 .sheets
5762 .push(crate::arrow_store::ArrowSheet {
5763 name: std::sync::Arc::<str>::from(sheet),
5764 columns: Vec::new(),
5765 nrows: 0,
5766 chunk_starts: Vec::new(),
5767 chunk_rows: 32 * 1024,
5768 });
5769 }
5770
5771 let row0 = row.saturating_sub(1) as usize;
5772 let col0 = col.saturating_sub(1) as usize;
5773
5774 let asheet = self
5775 .arrow_sheets
5776 .sheet_mut(sheet)
5777 .expect("ArrowSheet must exist");
5778
5779 let cur_cols = asheet.columns.len();
5780 if col0 >= cur_cols {
5781 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
5782 }
5783
5784 if row0 >= asheet.nrows as usize {
5785 if asheet.columns.is_empty() {
5786 asheet.insert_columns(0, 1);
5787 }
5788 asheet.ensure_row_capacity(row0 + 1);
5789 }
5790 if let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) {
5791 use crate::arrow_store::OverlayValue;
5792 let ov = match value {
5793 LiteralValue::Empty => OverlayValue::Empty,
5794 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
5795 LiteralValue::Number(n) => OverlayValue::Number(*n),
5796 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
5797 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
5798 LiteralValue::Error(e) => {
5799 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
5800 }
5801 LiteralValue::Date(d) => {
5802 let dt = d.and_hms_opt(0, 0, 0).unwrap();
5803 let serial = crate::builtins::datetime::datetime_to_serial_for(
5804 self.config.date_system,
5805 &dt,
5806 );
5807 OverlayValue::DateTime(serial)
5808 }
5809 LiteralValue::DateTime(dt) => {
5810 let serial = crate::builtins::datetime::datetime_to_serial_for(
5811 self.config.date_system,
5812 dt,
5813 );
5814 OverlayValue::DateTime(serial)
5815 }
5816 LiteralValue::Time(t) => {
5817 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
5818 OverlayValue::DateTime(serial)
5819 }
5820 LiteralValue::Duration(d) => {
5821 let serial = d.num_seconds() as f64 / 86_400.0;
5822 OverlayValue::Duration(serial)
5823 }
5824 LiteralValue::Pending => OverlayValue::Pending,
5825 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
5826 formualizer_common::ExcelErrorKind::Value,
5827 )),
5828 };
5829 let computed_delta = if let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) {
5830 let _ = ch.overlay.set(in_off, ov);
5831 ch.computed_overlay.remove(in_off)
5836 } else {
5837 return;
5838 };
5839 let abs_threshold = 1024usize;
5841 let frac_den = 50usize;
5842 let freed = asheet.maybe_compact_chunk(col0, ch_idx, abs_threshold, frac_den);
5843 if freed > 0 {
5844 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
5845 }
5846 self.adjust_computed_overlay_bytes(computed_delta);
5847 }
5848 }
5849
5850 fn clear_delta_overlay_cell(&mut self, sheet: &str, row: u32, col: u32) {
5855 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
5856 return;
5857 }
5858 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5859 return;
5860 };
5861 let row0 = row.saturating_sub(1) as usize;
5862 let col0 = col.saturating_sub(1) as usize;
5863 if row0 >= asheet.nrows as usize {
5864 return;
5865 }
5866 if col0 >= asheet.columns.len() {
5867 return;
5868 }
5869 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
5870 return;
5871 };
5872 if let Some(ch) = asheet.columns[col0].chunk_mut(ch_idx) {
5873 let _ = ch.overlay.remove(in_off);
5874 }
5875 }
5876
5877 fn clear_computed_overlay_col_row_range(
5878 &mut self,
5879 sheet: &str,
5880 col0: usize,
5881 start_row0: usize,
5882 end_row0_exclusive: usize,
5883 ) {
5884 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
5885 return;
5886 }
5887 if start_row0 >= end_row0_exclusive {
5888 return;
5889 }
5890
5891 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
5892 return;
5893 };
5894 if col0 >= asheet.columns.len() || start_row0 >= asheet.nrows as usize {
5895 return;
5896 }
5897 let end_row0_exclusive = end_row0_exclusive.min(asheet.nrows as usize);
5898 if start_row0 >= end_row0_exclusive {
5899 return;
5900 }
5901
5902 let starts = asheet.chunk_starts.clone();
5903 let nrows = asheet.nrows as usize;
5904 let mut delta = 0isize;
5905 let Some(col) = asheet.columns.get_mut(col0) else {
5906 return;
5907 };
5908 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
5909 let Some(&chunk_start) = starts.get(chunk_idx) else {
5910 continue;
5911 };
5912 let chunk_end = starts
5913 .get(chunk_idx + 1)
5914 .copied()
5915 .unwrap_or(nrows)
5916 .min(chunk_start.saturating_add(ch.len()));
5917 let clear_start = start_row0.max(chunk_start);
5918 let clear_end = end_row0_exclusive.min(chunk_end);
5919 if clear_start >= clear_end {
5920 continue;
5921 }
5922 if clear_start == chunk_start && clear_end == chunk_end {
5923 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5924 } else {
5925 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5926 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5927 delta = delta.saturating_add(
5928 ch.computed_overlay
5929 .remove_range(start_in_chunk..end_in_chunk),
5930 );
5931 }
5932 }
5933 for (chunk_idx, ch) in &mut col.sparse_chunks {
5934 let Some(&chunk_start) = starts.get(*chunk_idx) else {
5935 continue;
5936 };
5937 let chunk_end = starts
5938 .get(*chunk_idx + 1)
5939 .copied()
5940 .unwrap_or(nrows)
5941 .min(chunk_start.saturating_add(ch.len()));
5942 let clear_start = start_row0.max(chunk_start);
5943 let clear_end = end_row0_exclusive.min(chunk_end);
5944 if clear_start >= clear_end {
5945 continue;
5946 }
5947 if clear_start == chunk_start && clear_end == chunk_end {
5948 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
5949 } else {
5950 let start_in_chunk = clear_start.saturating_sub(chunk_start).min(ch.len());
5951 let end_in_chunk = clear_end.saturating_sub(chunk_start).min(ch.len());
5952 delta = delta.saturating_add(
5953 ch.computed_overlay
5954 .remove_range(start_in_chunk..end_in_chunk),
5955 );
5956 }
5957 }
5958 self.adjust_computed_overlay_bytes(delta);
5959 }
5960
5961 fn clear_computed_overlay_cells_in_region(
5962 &mut self,
5963 cells: &[(SheetId, u32, u32)],
5964 affected_region: &Region,
5965 ) {
5966 let mut by_col: BTreeMap<(SheetId, u32), Vec<u32>> = BTreeMap::new();
5967 for (formula_sheet_id, row, col) in cells {
5968 let row0 = row.saturating_sub(1);
5969 let col0 = col.saturating_sub(1);
5970 let placement_region = Region::point(*formula_sheet_id, row0, col0);
5971 if placement_region.intersects(affected_region) {
5972 by_col
5973 .entry((*formula_sheet_id, col0))
5974 .or_default()
5975 .push(row0);
5976 }
5977 }
5978
5979 for ((formula_sheet_id, col0), mut rows) in by_col {
5980 rows.sort_unstable();
5981 rows.dedup();
5982 let sheet_name = self.graph.sheet_name(formula_sheet_id).to_string();
5983 let mut start = rows[0];
5984 let mut prev = rows[0];
5985 for row in rows.into_iter().skip(1) {
5986 if row == prev.saturating_add(1) {
5987 prev = row;
5988 continue;
5989 }
5990 self.clear_computed_overlay_col_row_range(
5991 &sheet_name,
5992 col0 as usize,
5993 start as usize,
5994 prev.saturating_add(1) as usize,
5995 );
5996 start = row;
5997 prev = row;
5998 }
5999 self.clear_computed_overlay_col_row_range(
6000 &sheet_name,
6001 col0 as usize,
6002 start as usize,
6003 prev.saturating_add(1) as usize,
6004 );
6005 }
6006 }
6007
6008 fn clear_computed_overlay_after_row(&mut self, sheet: &str, start_row0: usize) {
6009 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
6010 return;
6011 }
6012
6013 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
6014 return;
6015 };
6016 if start_row0 >= asheet.nrows as usize {
6017 return;
6018 }
6019
6020 let starts = asheet.chunk_starts.clone();
6021 let nrows = asheet.nrows as usize;
6022 let mut delta = 0isize;
6023 for col in &mut asheet.columns {
6024 for (chunk_idx, ch) in col.chunks.iter_mut().enumerate() {
6025 let Some(&chunk_start) = starts.get(chunk_idx) else {
6026 continue;
6027 };
6028 let chunk_end = starts
6029 .get(chunk_idx + 1)
6030 .copied()
6031 .unwrap_or(nrows)
6032 .min(chunk_start.saturating_add(ch.len()));
6033 if chunk_end <= start_row0 {
6034 continue;
6035 }
6036 if chunk_start >= start_row0 {
6037 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6038 } else {
6039 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
6040 delta = delta
6041 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
6042 }
6043 }
6044
6045 for (chunk_idx, ch) in &mut col.sparse_chunks {
6046 let Some(&chunk_start) = starts.get(*chunk_idx) else {
6047 continue;
6048 };
6049 let chunk_end = starts
6050 .get(*chunk_idx + 1)
6051 .copied()
6052 .unwrap_or(nrows)
6053 .min(chunk_start.saturating_add(ch.len()));
6054 if chunk_end <= start_row0 {
6055 continue;
6056 }
6057 if chunk_start >= start_row0 {
6058 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6059 } else {
6060 let start_in_chunk = start_row0.saturating_sub(chunk_start).min(ch.len());
6061 delta = delta
6062 .saturating_add(ch.computed_overlay.remove_range(start_in_chunk..ch.len()));
6063 }
6064 }
6065 }
6066 self.adjust_computed_overlay_bytes(delta);
6067 }
6068
6069 fn clear_computed_overlay_after_col(&mut self, sheet: &str, start_col0: usize) {
6070 if !(self.config.arrow_storage_enabled && self.config.write_formula_overlay_enabled) {
6071 return;
6072 }
6073
6074 let Some(asheet) = self.arrow_sheets.sheet_mut(sheet) else {
6075 return;
6076 };
6077 if start_col0 >= asheet.columns.len() {
6078 return;
6079 }
6080
6081 let mut delta = 0isize;
6082 for col in asheet.columns.iter_mut().skip(start_col0) {
6083 for ch in &mut col.chunks {
6084 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6085 }
6086 for ch in col.sparse_chunks.values_mut() {
6087 delta = delta.saturating_sub(ch.computed_overlay.clear() as isize);
6088 }
6089 }
6090 self.adjust_computed_overlay_bytes(delta);
6091 }
6092
6093 #[inline]
6094 fn literal_to_overlay_value(&self, value: &LiteralValue) -> crate::arrow_store::OverlayValue {
6095 use crate::arrow_store::OverlayValue;
6096 match value {
6097 LiteralValue::Empty => OverlayValue::Empty,
6098 LiteralValue::Int(i) => OverlayValue::Number(*i as f64),
6099 LiteralValue::Number(n) => OverlayValue::Number(*n),
6100 LiteralValue::Boolean(b) => OverlayValue::Boolean(*b),
6101 LiteralValue::Text(s) => OverlayValue::Text(std::sync::Arc::from(s.clone())),
6102 LiteralValue::Error(e) => {
6103 OverlayValue::Error(crate::arrow_store::map_error_code(e.kind))
6104 }
6105 LiteralValue::Date(d) => {
6106 let dt = d.and_hms_opt(0, 0, 0).unwrap();
6107 let serial =
6108 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, &dt);
6109 OverlayValue::DateTime(serial)
6110 }
6111 LiteralValue::DateTime(dt) => {
6112 let serial =
6113 crate::builtins::datetime::datetime_to_serial_for(self.config.date_system, dt);
6114 OverlayValue::DateTime(serial)
6115 }
6116 LiteralValue::Time(t) => {
6117 let serial = t.num_seconds_from_midnight() as f64 / 86_400.0;
6118 OverlayValue::DateTime(serial)
6119 }
6120 LiteralValue::Duration(d) => {
6121 let serial = d.num_seconds() as f64 / 86_400.0;
6122 OverlayValue::Duration(serial)
6123 }
6124 LiteralValue::Pending => OverlayValue::Pending,
6125 LiteralValue::Array(_) => OverlayValue::Error(crate::arrow_store::map_error_code(
6126 formualizer_common::ExcelErrorKind::Value,
6127 )),
6128 }
6129 }
6130
6131 fn read_delta_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6134 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
6135 return None;
6136 }
6137 let asheet = self.arrow_sheets.sheet(sheet)?;
6138 let row0 = row.saturating_sub(1) as usize;
6139 let col0 = col.saturating_sub(1) as usize;
6140 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
6141 return None;
6142 }
6143 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
6144 let ch = asheet.columns[col0].chunk(ch_idx)?;
6145 ch.overlay.get_scalar(in_off).map(|ov| ov.to_literal())
6146 }
6147
6148 fn read_computed_overlay_cell(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
6151 if !(self.config.arrow_storage_enabled
6152 && self.config.delta_overlay_enabled
6153 && self.config.write_formula_overlay_enabled)
6154 {
6155 return None;
6156 }
6157 let asheet = self.arrow_sheets.sheet(sheet)?;
6158 let row0 = row.saturating_sub(1) as usize;
6159 let col0 = col.saturating_sub(1) as usize;
6160 if row0 >= asheet.nrows as usize || col0 >= asheet.columns.len() {
6161 return None;
6162 }
6163 let (ch_idx, in_off) = asheet.chunk_of_row(row0)?;
6164 let ch = asheet.columns[col0].chunk(ch_idx)?;
6165 ch.computed_overlay
6166 .get_scalar(in_off)
6167 .map(|ov| ov.to_literal())
6168 }
6169
6170 fn set_delta_overlay_cell_raw(
6171 &mut self,
6172 sheet: &str,
6173 row: u32,
6174 col: u32,
6175 value: Option<LiteralValue>,
6176 ) {
6177 if !(self.config.arrow_storage_enabled && self.config.delta_overlay_enabled) {
6178 return;
6179 }
6180
6181 self.ensure_arrow_sheet(sheet);
6182 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
6183 let row0 = row.saturating_sub(1) as usize;
6184 let col0 = col.saturating_sub(1) as usize;
6185 let asheet = self
6186 .arrow_sheets
6187 .sheet_mut(sheet)
6188 .expect("ArrowSheet must exist");
6189
6190 let cur_cols = asheet.columns.len();
6191 if col0 >= cur_cols {
6192 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6193 }
6194 if row0 >= asheet.nrows as usize {
6195 if asheet.columns.is_empty() {
6196 asheet.insert_columns(0, 1);
6197 }
6198 asheet.ensure_row_capacity(row0 + 1);
6199 }
6200
6201 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6202 return;
6203 };
6204 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6205 return;
6206 };
6207
6208 if let Some(ov) = ov_opt {
6209 let _ = ch.overlay.set(in_off, ov);
6210 } else {
6211 let _ = ch.overlay.remove(in_off);
6212 }
6213 }
6214
6215 fn set_computed_overlay_cell_raw(
6216 &mut self,
6217 sheet: &str,
6218 row: u32,
6219 col: u32,
6220 value: Option<LiteralValue>,
6221 ) {
6222 if !(self.config.arrow_storage_enabled
6223 && self.config.delta_overlay_enabled
6224 && self.config.write_formula_overlay_enabled)
6225 {
6226 return;
6227 }
6228
6229 self.ensure_arrow_sheet(sheet);
6230 let ov_opt = value.as_ref().map(|v| self.literal_to_overlay_value(v));
6231 let row0 = row.saturating_sub(1) as usize;
6232 let col0 = col.saturating_sub(1) as usize;
6233 let asheet = self
6234 .arrow_sheets
6235 .sheet_mut(sheet)
6236 .expect("ArrowSheet must exist");
6237
6238 let cur_cols = asheet.columns.len();
6239 if col0 >= cur_cols {
6240 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6241 }
6242 if row0 >= asheet.nrows as usize {
6243 if asheet.columns.is_empty() {
6244 asheet.insert_columns(0, 1);
6245 }
6246 asheet.ensure_row_capacity(row0 + 1);
6247 }
6248
6249 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6250 return;
6251 };
6252 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6253 return;
6254 };
6255
6256 let delta = if let Some(ov) = ov_opt {
6257 ch.computed_overlay.set(in_off, ov)
6258 } else {
6259 ch.computed_overlay.remove(in_off)
6260 };
6261 self.adjust_computed_overlay_bytes(delta);
6262 }
6263
6264 fn apply_arrow_undo_batch(&mut self, batch: &crate::engine::ArrowUndoBatch, undo: bool) {
6265 use crate::engine::ArrowOp;
6266
6267 let iter: Box<dyn Iterator<Item = &ArrowOp>> = if undo {
6268 Box::new(batch.ops.iter().rev())
6269 } else {
6270 Box::new(batch.ops.iter())
6271 };
6272
6273 for op in iter {
6274 match op {
6275 ArrowOp::SetDeltaCell {
6276 sheet_id,
6277 row0,
6278 col0,
6279 old,
6280 new,
6281 } => {
6282 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6283 let v = if undo { old.clone() } else { new.clone() };
6284 self.set_delta_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
6285 }
6286 ArrowOp::SetComputedCell {
6287 sheet_id,
6288 row0,
6289 col0,
6290 old,
6291 new,
6292 } => {
6293 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6294 let v = if undo { old.clone() } else { new.clone() };
6295 self.set_computed_overlay_cell_raw(&sheet, row0 + 1, col0 + 1, v);
6296 }
6297 ArrowOp::RestoreComputedRect {
6298 sheet_id,
6299 sr0,
6300 sc0,
6301 er0,
6302 ec0,
6303 old,
6304 new,
6305 } => {
6306 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6307 let vals = if undo { old } else { new };
6308 let height = (*er0).saturating_sub(*sr0) as usize + 1;
6309 let width = (*ec0).saturating_sub(*sc0) as usize + 1;
6310 for r in 0..height {
6311 for c in 0..width {
6312 let v = vals
6313 .get(r)
6314 .and_then(|row| row.get(c))
6315 .cloned()
6316 .unwrap_or(LiteralValue::Empty);
6317 self.set_computed_overlay_cell_raw(
6318 &sheet,
6319 *sr0 + 1 + r as u32,
6320 *sc0 + 1 + c as u32,
6321 Some(v),
6322 );
6323 }
6324 }
6325 }
6326 ArrowOp::InsertRows {
6327 sheet_id,
6328 before0,
6329 count,
6330 } => {
6331 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6332 self.ensure_arrow_sheet(&sheet);
6333 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
6334 if undo {
6335 asheet.delete_rows(*before0 as usize, *count as usize);
6336 } else {
6337 asheet.insert_rows(*before0 as usize, *count as usize);
6338 }
6339 }
6340 }
6341 ArrowOp::InsertCols {
6342 sheet_id,
6343 before0,
6344 count,
6345 } => {
6346 let sheet = self.graph.sheet_name(*sheet_id).to_string();
6347 self.ensure_arrow_sheet(&sheet);
6348 if let Some(asheet) = self.arrow_sheets.sheet_mut(&sheet) {
6349 if undo {
6350 asheet.delete_columns(*before0 as usize, *count as usize);
6351 } else {
6352 asheet.insert_columns(*before0 as usize, *count as usize);
6353 }
6354 }
6355 }
6356 }
6357 }
6358 }
6359
6360 fn record_spill_ops_into_arrow_undo(
6361 &mut self,
6362 undo: &mut crate::engine::ArrowUndoBatch,
6363 events: &[crate::engine::ChangeEvent],
6364 ) {
6365 use crate::engine::ChangeEvent;
6366 use formualizer_common::LiteralValue;
6367
6368 #[allow(clippy::type_complexity)]
6369 let rect_from_snapshot =
6370 |snap: &crate::engine::graph::editor::change_log::SpillSnapshot|
6371 -> Option<(SheetId, u32, u32, u32, u32, Vec<Vec<LiteralValue>>)> {
6372 if snap.target_cells.is_empty() {
6373 return None;
6374 }
6375 let sheet_id = snap.target_cells[0].sheet_id;
6376 let sr0 = snap.target_cells[0].coord.row();
6377 let sc0 = snap.target_cells[0].coord.col();
6378 if snap.values.is_empty() || snap.values[0].is_empty() {
6379 return None;
6380 }
6381 let h = snap.values.len() as u32;
6382 let w = snap.values[0].len() as u32;
6383 let er0 = sr0.saturating_add(h.saturating_sub(1));
6384 let ec0 = sc0.saturating_add(w.saturating_sub(1));
6385 Some((sheet_id, sr0, sc0, er0, ec0, snap.values.clone()))
6386 };
6387
6388 for ev in events {
6389 match ev {
6390 ChangeEvent::SpillCommitted { old, new, .. } => {
6391 if let Some((sid, sr0, sc0, er0, ec0, new_vals)) = rect_from_snapshot(new) {
6392 let old_vals = if let Some(old_snap) = old {
6393 rect_from_snapshot(old_snap)
6394 .map(|(_, _, _, _, _, v)| v)
6395 .unwrap_or_else(|| {
6396 vec![
6397 vec![LiteralValue::Empty; new_vals[0].len()];
6398 new_vals.len()
6399 ]
6400 })
6401 } else {
6402 vec![vec![LiteralValue::Empty; new_vals[0].len()]; new_vals.len()]
6403 };
6404 undo.record_restore_computed_rect(
6405 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
6406 );
6407 }
6408 }
6409 ChangeEvent::SpillCleared { old, .. } => {
6410 if let Some((sid, sr0, sc0, er0, ec0, old_vals)) = rect_from_snapshot(old) {
6411 let new_vals =
6412 vec![vec![LiteralValue::Empty; old_vals[0].len()]; old_vals.len()];
6413 undo.record_restore_computed_rect(
6414 sid, sr0, sc0, er0, ec0, old_vals, new_vals,
6415 );
6416 }
6417 }
6418 _ => {}
6419 }
6420 }
6421 }
6422
6423 fn mirror_value_to_computed_overlay(
6428 &mut self,
6429 sheet: &str,
6430 row: u32,
6431 col: u32,
6432 value: &LiteralValue,
6433 ) {
6434 if !(self.config.arrow_storage_enabled
6435 && self.config.delta_overlay_enabled
6436 && self.config.write_formula_overlay_enabled)
6437 {
6438 return;
6439 }
6440 if self.computed_overlay_mirroring_disabled {
6441 return;
6442 }
6443
6444 let ov = self.literal_to_overlay_value(value);
6445 self.write_computed_overlay_value_0based(
6446 sheet,
6447 row.saturating_sub(1),
6448 col.saturating_sub(1),
6449 ov,
6450 );
6451 }
6452
6453 fn write_computed_overlay_value_0based(
6454 &mut self,
6455 sheet: &str,
6456 row0: u32,
6457 col0: u32,
6458 value: OverlayValue,
6459 ) {
6460 if !(self.config.arrow_storage_enabled
6461 && self.config.delta_overlay_enabled
6462 && self.config.write_formula_overlay_enabled)
6463 {
6464 return;
6465 }
6466 if self.computed_overlay_mirroring_disabled {
6467 return;
6468 }
6469
6470 self.ensure_arrow_sheet(sheet);
6471
6472 let row0 = row0 as usize;
6473 let col0 = col0 as usize;
6474 let asheet = self
6475 .arrow_sheets
6476 .sheet_mut(sheet)
6477 .expect("ArrowSheet must exist");
6478
6479 let cur_cols = asheet.columns.len();
6480 if col0 >= cur_cols {
6481 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6482 }
6483
6484 if row0 >= asheet.nrows as usize {
6485 if asheet.columns.is_empty() {
6486 asheet.insert_columns(0, 1);
6487 }
6488 asheet.ensure_row_capacity(row0 + 1);
6489 }
6490
6491 let Some((ch_idx, in_off)) = asheet.chunk_of_row(row0) else {
6492 return;
6493 };
6494 let Some(ch) = asheet.ensure_column_chunk_mut(col0, ch_idx) else {
6495 return;
6496 };
6497
6498 let delta = ch.computed_overlay.set_scalar(in_off, value);
6499 self.adjust_computed_overlay_bytes(delta);
6500
6501 if let Some(cap) = self.config.max_overlay_memory_bytes
6502 && self.computed_overlay_bytes_estimate > cap
6503 {
6504 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6505 }
6506 }
6507
6508 pub(crate) fn plan_computed_write_coalescing(
6509 &self,
6510 buffer: &ComputedWriteBuffer,
6511 ) -> ComputedWriteCoalescingPlan {
6512 self.plan_computed_write_coalescing_from_writes(buffer.writes().iter().cloned())
6513 }
6514
6515 fn plan_owned_computed_write_coalescing(
6516 &self,
6517 writes: Vec<ComputedWrite>,
6518 ) -> ComputedWriteCoalescingPlan {
6519 self.plan_computed_write_coalescing_from_writes(writes)
6520 }
6521
6522 fn plan_computed_write_coalescing_from_writes(
6523 &self,
6524 writes: impl IntoIterator<Item = ComputedWrite>,
6525 ) -> ComputedWriteCoalescingPlan {
6526 let mut groups: BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>> =
6527 BTreeMap::new();
6528 let mut input_cells = 0usize;
6529
6530 for write in writes {
6531 match write {
6532 ComputedWrite::Cell {
6533 seq,
6534 sheet_id,
6535 row0,
6536 col0,
6537 value,
6538 } => {
6539 input_cells = input_cells.saturating_add(1);
6540 self.push_computed_write_plan_entry(
6541 &mut groups,
6542 seq,
6543 sheet_id,
6544 row0,
6545 col0,
6546 value,
6547 );
6548 }
6549 ComputedWrite::Rect {
6550 seq,
6551 sheet_id,
6552 sr0,
6553 sc0,
6554 values,
6555 } => {
6556 for (r_off, row) in values.into_iter().enumerate() {
6557 for (c_off, value) in row.into_iter().enumerate() {
6558 input_cells = input_cells.saturating_add(1);
6559 self.push_computed_write_plan_entry(
6560 &mut groups,
6561 seq,
6562 sheet_id,
6563 sr0.saturating_add(r_off as u32),
6564 sc0.saturating_add(c_off as u32),
6565 value,
6566 );
6567 }
6568 }
6569 }
6570 }
6571 }
6572
6573 let mut plan = ComputedWriteCoalescingPlan {
6574 chunks: Vec::with_capacity(groups.len()),
6575 input_cells,
6576 coalesced_cells: 0,
6577 overwritten_cells: 0,
6578 };
6579 for (key, entries) in groups {
6580 let (chunk_plan, overwritten) = ComputedWriteChunkPlan::from_group(key, entries);
6581 plan.coalesced_cells = plan
6582 .coalesced_cells
6583 .saturating_add(chunk_plan.entries.len());
6584 plan.overwritten_cells = plan.overwritten_cells.saturating_add(overwritten);
6585 plan.chunks.push(chunk_plan);
6586 }
6587 debug_assert_eq!(
6588 plan.input_cells,
6589 plan.coalesced_cells.saturating_add(plan.overwritten_cells)
6590 );
6591 plan
6592 }
6593
6594 fn push_computed_write_plan_entry(
6595 &self,
6596 groups: &mut BTreeMap<ComputedWriteChunkKey, Vec<ComputedWriteChunkEntryPlan>>,
6597 seq: u64,
6598 sheet_id: SheetId,
6599 row0: u32,
6600 col0: u32,
6601 value: OverlayValue,
6602 ) {
6603 let (chunk_idx, chunk_start_row0, row_in_chunk) =
6604 self.locate_computed_write_chunk(sheet_id, row0);
6605 let key = ComputedWriteChunkKey {
6606 sheet_id,
6607 col0,
6608 chunk_idx,
6609 chunk_start_row0,
6610 };
6611 groups
6612 .entry(key)
6613 .or_default()
6614 .push(ComputedWriteChunkEntryPlan {
6615 row_in_chunk,
6616 seq,
6617 value,
6618 });
6619 }
6620
6621 fn locate_computed_write_chunk(&self, sheet_id: SheetId, row0: u32) -> (usize, u32, usize) {
6622 let sheet_name = self.graph.sheet_name(sheet_id);
6623 if let Some(sheet) = self.arrow_sheets.sheet(sheet_name) {
6624 return Self::locate_row_in_sheet_for_computed_write_plan(sheet, row0 as usize);
6625 }
6626 Self::locate_row_in_empty_sheet_for_computed_write_plan(row0 as usize, 32 * 1024)
6627 }
6628
6629 fn locate_row_in_sheet_for_computed_write_plan(
6630 sheet: &crate::arrow_store::ArrowSheet,
6631 row0: usize,
6632 ) -> (usize, u32, usize) {
6633 if row0 < sheet.nrows as usize
6634 && let Some((chunk_idx, row_in_chunk)) = sheet.chunk_of_row(row0)
6635 {
6636 let chunk_start = sheet.chunk_starts.get(chunk_idx).copied().unwrap_or(0);
6637 return (chunk_idx, chunk_start as u32, row_in_chunk);
6638 }
6639
6640 let chunk_rows = sheet.chunk_rows.max(1);
6641 if sheet.chunk_starts.is_empty() {
6642 return Self::locate_row_in_empty_sheet_for_computed_write_plan(row0, chunk_rows);
6643 }
6644
6645 let mut chunk_idx = sheet.chunk_starts.len().saturating_sub(1);
6646 let mut chunk_start = sheet.chunk_starts[chunk_idx];
6647 while chunk_start.saturating_add(chunk_rows) <= row0 {
6648 chunk_idx = chunk_idx.saturating_add(1);
6649 chunk_start = chunk_start.saturating_add(chunk_rows);
6650 }
6651 (
6652 chunk_idx,
6653 chunk_start as u32,
6654 row0.saturating_sub(chunk_start),
6655 )
6656 }
6657
6658 fn locate_row_in_empty_sheet_for_computed_write_plan(
6659 row0: usize,
6660 chunk_rows: usize,
6661 ) -> (usize, u32, usize) {
6662 let chunk_rows = chunk_rows.max(1);
6663 let chunk_idx = row0 / chunk_rows;
6664 let chunk_start = chunk_idx.saturating_mul(chunk_rows);
6665 (
6666 chunk_idx,
6667 chunk_start as u32,
6668 row0.saturating_sub(chunk_start),
6669 )
6670 }
6671
6672 #[cfg(test)]
6673 pub(crate) fn debug_plan_computed_write_coalescing(
6674 &self,
6675 buffer: &ComputedWriteBuffer,
6676 ) -> ComputedWriteCoalescingPlan {
6677 self.plan_computed_write_coalescing(buffer)
6678 }
6679
6680 pub(crate) fn flush_computed_write_buffer(
6681 &mut self,
6682 buffer: &mut ComputedWriteBuffer,
6683 ) -> Result<(), ExcelError> {
6684 if buffer.is_empty() {
6685 return Ok(());
6686 }
6687
6688 let plan = self.plan_owned_computed_write_coalescing(buffer.take_writes());
6689 self.flush_computed_write_plan(plan);
6690
6691 Ok(())
6692 }
6693
6694 fn flush_computed_write_plan(&mut self, plan: ComputedWriteCoalescingPlan) {
6695 for chunk in plan.chunks {
6696 self.flush_computed_write_chunk_plan(chunk);
6697 }
6698 }
6699
6700 fn flush_computed_write_chunk_plan(&mut self, chunk: ComputedWriteChunkPlan) {
6701 match &chunk.shape {
6702 ComputedWriteChunkPlanShape::Point => {
6703 self.flush_computed_write_chunk_plan_as_points(chunk);
6704 }
6705 ComputedWriteChunkPlanShape::SparseOffsets { .. } => {
6706 self.flush_computed_write_chunk_plan_as_sparse_fragment_or_points(chunk);
6707 }
6708 ComputedWriteChunkPlanShape::DenseRange { .. } => {
6709 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6710 }
6711 ComputedWriteChunkPlanShape::RunRange { len, runs, .. } => {
6712 if Self::should_emit_computed_run_fragment(*len, *runs) {
6713 self.flush_computed_write_chunk_plan_as_run_fragment(chunk);
6714 } else {
6715 self.flush_computed_write_chunk_plan_as_dense_fragment(chunk);
6716 }
6717 }
6718 }
6719 }
6720
6721 #[inline]
6722 fn should_emit_computed_run_fragment(len: usize, runs: usize) -> bool {
6723 runs <= len / 2
6724 }
6725
6726 fn flush_computed_write_chunk_plan_as_points(&mut self, chunk: ComputedWriteChunkPlan) {
6727 let sheet_name = self.graph.sheet_name(chunk.sheet_id).to_string();
6728 for entry in chunk.entries {
6729 let row0 = chunk
6730 .chunk_start_row0
6731 .saturating_add(entry.row_in_chunk as u32);
6732 self.write_computed_overlay_value_0based(&sheet_name, row0, chunk.col0, entry.value);
6733 }
6734 }
6735
6736 fn flush_computed_write_chunk_plan_as_sparse_fragment_or_points(
6737 &mut self,
6738 chunk: ComputedWriteChunkPlan,
6739 ) {
6740 let point_estimate = Self::computed_write_chunk_plan_point_estimate(&chunk);
6741 let sheet_id = chunk.sheet_id;
6742 let col0 = chunk.col0;
6743 let chunk_idx = chunk.chunk_idx;
6744 let chunk_start_row0 = chunk.chunk_start_row0;
6745 let items: Vec<(usize, OverlayValue)> = chunk
6746 .entries
6747 .into_iter()
6748 .map(|entry| (entry.row_in_chunk, entry.value))
6749 .collect();
6750 match OverlayFragment::sparse_offsets_if_estimated_smaller_than_points(
6751 items,
6752 point_estimate,
6753 ) {
6754 Some(Ok(fragment)) => {
6755 self.apply_computed_overlay_fragment(sheet_id, col0, chunk_idx, fragment);
6756 }
6757 Some(Err(cells)) => {
6758 self.flush_computed_overlay_cells_as_points(
6759 sheet_id,
6760 col0,
6761 chunk_start_row0,
6762 cells,
6763 );
6764 }
6765 None => {}
6766 }
6767 }
6768
6769 #[inline]
6770 fn computed_write_chunk_plan_point_estimate(chunk: &ComputedWriteChunkPlan) -> usize {
6771 chunk
6772 .entries
6773 .iter()
6774 .map(|entry| ComputedWriteBuffer::estimate_value_bytes(&entry.value))
6775 .fold(0usize, usize::saturating_add)
6776 }
6777
6778 fn flush_computed_overlay_cells_as_points(
6779 &mut self,
6780 sheet_id: SheetId,
6781 col0: u32,
6782 chunk_start_row0: u32,
6783 cells: Vec<(usize, OverlayValue)>,
6784 ) {
6785 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6786 for (row_in_chunk, value) in cells {
6787 let row0 = chunk_start_row0.saturating_add(row_in_chunk as u32);
6788 self.write_computed_overlay_value_0based(&sheet_name, row0, col0, value);
6789 }
6790 }
6791
6792 fn flush_computed_write_chunk_plan_as_dense_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6793 if chunk.entries.is_empty() {
6794 return;
6795 }
6796 let start = chunk.entries[0].row_in_chunk;
6797 let values: Vec<OverlayValue> =
6798 chunk.entries.into_iter().map(|entry| entry.value).collect();
6799 if let Some(fragment) = OverlayFragment::dense_range(start, values) {
6800 self.apply_computed_overlay_fragment(
6801 chunk.sheet_id,
6802 chunk.col0,
6803 chunk.chunk_idx,
6804 fragment,
6805 );
6806 }
6807 }
6808
6809 fn flush_computed_write_chunk_plan_as_run_fragment(&mut self, chunk: ComputedWriteChunkPlan) {
6810 if chunk.entries.is_empty() {
6811 return;
6812 }
6813 let start = chunk.entries[0].row_in_chunk;
6814 let values: Vec<OverlayValue> =
6815 chunk.entries.into_iter().map(|entry| entry.value).collect();
6816 if let Some(fragment) = OverlayFragment::run_range(start, values) {
6817 self.apply_computed_overlay_fragment(
6818 chunk.sheet_id,
6819 chunk.col0,
6820 chunk.chunk_idx,
6821 fragment,
6822 );
6823 }
6824 }
6825
6826 fn apply_computed_overlay_fragment(
6827 &mut self,
6828 sheet_id: SheetId,
6829 col0: u32,
6830 chunk_idx: usize,
6831 fragment: OverlayFragment,
6832 ) {
6833 if !(self.config.arrow_storage_enabled
6834 && self.config.delta_overlay_enabled
6835 && self.config.write_formula_overlay_enabled)
6836 {
6837 return;
6838 }
6839 if self.computed_overlay_mirroring_disabled {
6840 return;
6841 }
6842
6843 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
6844 self.ensure_arrow_sheet(&sheet_name);
6845
6846 let col0 = col0 as usize;
6847 let asheet = self
6848 .arrow_sheets
6849 .sheet_mut(&sheet_name)
6850 .expect("ArrowSheet must exist");
6851
6852 let cur_cols = asheet.columns.len();
6853 if col0 >= cur_cols {
6854 asheet.insert_columns(cur_cols, (col0 + 1) - cur_cols);
6855 }
6856
6857 let start_row0 = asheet
6858 .chunk_starts
6859 .get(chunk_idx)
6860 .copied()
6861 .unwrap_or_else(|| chunk_idx.saturating_mul(asheet.chunk_rows.max(1)));
6862 let required_rows =
6863 start_row0.saturating_add(fragment.max_covered_offset().saturating_add(1));
6864 if required_rows > asheet.nrows as usize {
6865 if asheet.columns.is_empty() {
6866 asheet.insert_columns(0, 1);
6867 }
6868 asheet.ensure_row_capacity(required_rows);
6869 }
6870
6871 let Some(ch) = asheet.ensure_column_chunk_mut(col0, chunk_idx) else {
6872 return;
6873 };
6874 let delta = ch.computed_overlay.apply_fragment(fragment);
6875 self.adjust_computed_overlay_bytes(delta);
6876
6877 if let Some(cap) = self.config.max_overlay_memory_bytes
6878 && self.computed_overlay_bytes_estimate > cap
6879 {
6880 self.disable_computed_overlay_mirroring_due_to_budget(cap);
6881 }
6882 }
6883
6884 #[inline]
6885 fn adjust_computed_overlay_bytes(&mut self, delta: isize) {
6886 if delta >= 0 {
6887 self.computed_overlay_bytes_estimate = self
6888 .computed_overlay_bytes_estimate
6889 .saturating_add(delta as usize);
6890 } else {
6891 self.computed_overlay_bytes_estimate = self
6892 .computed_overlay_bytes_estimate
6893 .saturating_sub((-delta) as usize);
6894 }
6895 }
6896
6897 fn clear_all_computed_overlays(&mut self) {
6898 let mut freed_total = 0usize;
6899 for sh in self.arrow_sheets.sheets.iter_mut() {
6900 for col in sh.columns.iter_mut() {
6901 for ch in col.chunks.iter_mut() {
6902 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6903 }
6904 for ch in col.sparse_chunks.values_mut() {
6905 freed_total = freed_total.saturating_add(ch.computed_overlay.clear());
6906 }
6907 }
6908 }
6909 self.computed_overlay_bytes_estimate = self
6910 .computed_overlay_bytes_estimate
6911 .saturating_sub(freed_total);
6912 }
6913
6914 fn disable_computed_overlay_mirroring_due_to_budget(&mut self, _cap: usize) {
6915 self.compact_all_computed_overlays();
6918 }
6919
6920 fn compact_all_computed_overlays(&mut self) {
6923 let mut freed_total = 0usize;
6924 for sheet in self.arrow_sheets.sheets.iter_mut() {
6925 for col_idx in 0..sheet.columns.len() {
6926 let num_dense = sheet.columns[col_idx].chunks.len();
6928 for ch_idx in 0..num_dense {
6929 freed_total += sheet.compact_computed_overlay_chunk(col_idx, ch_idx);
6930 }
6931 let sparse_keys: Vec<usize> = sheet.columns[col_idx]
6933 .sparse_chunks
6934 .keys()
6935 .copied()
6936 .collect();
6937 for ch_idx in sparse_keys {
6938 freed_total += sheet.compact_computed_overlay_sparse_chunk(col_idx, ch_idx);
6939 }
6940 }
6941 }
6942 self.computed_overlay_bytes_estimate = self
6943 .computed_overlay_bytes_estimate
6944 .saturating_sub(freed_total);
6945 self.overlay_compactions = self.overlay_compactions.saturating_add(1);
6946 }
6947
6948 fn mirror_vertex_value_to_overlay(&mut self, vertex_id: VertexId, value: &LiteralValue) {
6949 let _ = self.record_vertex_value_to_overlay(vertex_id, value, None);
6950 }
6951
6952 fn record_vertex_value_to_overlay(
6953 &mut self,
6954 vertex_id: VertexId,
6955 value: &LiteralValue,
6956 computed_writes: Option<&mut ComputedWriteBuffer>,
6957 ) -> Result<(), ExcelError> {
6958 if !(self.config.arrow_storage_enabled
6959 && self.config.delta_overlay_enabled
6960 && self.config.write_formula_overlay_enabled)
6961 {
6962 return Ok(());
6963 }
6964 if self.computed_overlay_mirroring_disabled {
6965 return Ok(());
6966 }
6967 if !matches!(
6968 self.graph.get_vertex_kind(vertex_id),
6969 VertexKind::FormulaScalar | VertexKind::FormulaArray
6970 ) {
6971 return Ok(());
6972 }
6973 let Some(cell) = self.graph.get_cell_ref(vertex_id) else {
6974 return Ok(());
6975 };
6976 let ov = self.literal_to_overlay_value(value);
6977 if let Some(buffer) = computed_writes {
6978 buffer.push_cell(cell.sheet_id, cell.coord.row(), cell.coord.col(), ov);
6979 if self.should_flush_computed_write_buffer(buffer) {
6980 self.flush_computed_write_buffer(buffer)?;
6981 }
6982 } else {
6983 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
6984 self.write_computed_overlay_value_0based(
6985 &sheet_name,
6986 cell.coord.row(),
6987 cell.coord.col(),
6988 ov,
6989 );
6990 }
6991 Ok(())
6992 }
6993
6994 #[inline]
6995 fn should_flush_computed_write_buffer(&self, buffer: &ComputedWriteBuffer) -> bool {
6996 self.config.max_overlay_memory_bytes.is_some_and(|cap| {
6997 if cap == 0 {
6998 return false;
6999 }
7000 self.computed_overlay_bytes_estimate
7001 .saturating_add(buffer.estimated_bytes())
7002 > cap
7003 })
7004 }
7005
7006 pub fn overlay_memory_usage(&self) -> usize {
7008 self.computed_overlay_bytes_estimate
7009 }
7010
7011 #[cfg(test)]
7012 pub(crate) fn debug_overlay_compactions(&self) -> u64 {
7013 self.overlay_compactions
7014 }
7015
7016 #[cfg(test)]
7017 pub(crate) fn debug_recompute_computed_overlay_bytes(&mut self) -> usize {
7018 let mut total = 0usize;
7019 for sheet in &self.arrow_sheets.sheets {
7020 for column in &sheet.columns {
7021 for chunk in &column.chunks {
7022 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
7023 }
7024 for chunk in column.sparse_chunks.values() {
7025 total = total.saturating_add(chunk.computed_overlay.estimated_bytes());
7026 }
7027 }
7028 }
7029 self.computed_overlay_bytes_estimate = total;
7030 total
7031 }
7032
7033 fn resolve_sheet_locator_for_write(
7034 &mut self,
7035 loc: formualizer_common::SheetLocator<'_>,
7036 current_sheet: &str,
7037 ) -> Result<SheetId, ExcelError> {
7038 Ok(match loc {
7039 formualizer_common::SheetLocator::Id(id) => id,
7040 formualizer_common::SheetLocator::Name(name) => self.graph.sheet_id_mut(name.as_ref()),
7041 formualizer_common::SheetLocator::Current => self.graph.sheet_id_mut(current_sheet),
7042 })
7043 }
7044
7045 fn resolve_sheet_locator_for_read(
7046 &self,
7047 loc: formualizer_common::SheetLocator<'_>,
7048 current_sheet: &str,
7049 ) -> Result<SheetId, ExcelError> {
7050 match loc {
7051 formualizer_common::SheetLocator::Id(id) => Ok(id),
7052 formualizer_common::SheetLocator::Name(name) => self
7053 .graph
7054 .sheet_id(name.as_ref())
7055 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
7056 formualizer_common::SheetLocator::Current => self
7057 .graph
7058 .sheet_id(current_sheet)
7059 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref)),
7060 }
7061 }
7062
7063 pub fn set_cell_value(
7065 &mut self,
7066 sheet: &str,
7067 row: u32,
7068 col: u32,
7069 value: LiteralValue,
7070 ) -> Result<(), ExcelError> {
7071 let sheet_id = self.graph.sheet_id_mut(sheet);
7072 self.demote_span_containing_cell_for_write(
7073 sheet_id,
7074 row.saturating_sub(1),
7075 col.saturating_sub(1),
7076 )
7077 .map_err(Self::editor_error_to_excel)?;
7078 self.graph.set_cell_value(sheet, row, col, value.clone())?;
7079 self.record_formula_plane_changed_cell(sheet, row, col);
7080 self.mirror_value_to_overlay(sheet, row, col, &value);
7082 self.snapshot_id
7084 .fetch_add(1, std::sync::atomic::Ordering::Relaxed);
7085 self.has_edited = true;
7086 Ok(())
7087 }
7088
7089 fn record_formula_plane_changed_cell(&mut self, sheet: &str, row: u32, col: u32) {
7094 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7095 return;
7096 }
7097 let sheet_id = self.graph.sheet_id_mut(sheet);
7098 self.record_formula_plane_structural_change(StructuralScope::Cell {
7099 sheet: sheet_id,
7100 row: row.saturating_sub(1),
7101 col: col.saturating_sub(1),
7102 });
7103 }
7104
7105 fn record_formula_plane_change_for_event(&mut self, event: &ChangeEvent) {
7106 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7107 return;
7108 }
7109
7110 match event {
7111 ChangeEvent::SetValue { addr, .. } | ChangeEvent::SetFormula { addr, .. } => {
7112 self.record_formula_plane_structural_change(StructuralScope::Cell {
7113 sheet: addr.sheet_id,
7114 row: addr.coord.row(),
7115 col: addr.coord.col(),
7116 });
7117 }
7118 ChangeEvent::SpillCommitted { new, .. } => {
7119 if let Some(scope) = Self::formula_plane_region_from_cells(&new.target_cells) {
7120 self.record_formula_plane_structural_change(scope);
7121 }
7122 }
7123 ChangeEvent::SpillCleared { old, .. } => {
7124 if let Some(scope) = Self::formula_plane_region_from_cells(&old.target_cells) {
7125 self.record_formula_plane_structural_change(scope);
7126 }
7127 }
7128 ChangeEvent::DefineName { .. }
7129 | ChangeEvent::UpdateName { .. }
7130 | ChangeEvent::DeleteName { .. }
7131 | ChangeEvent::VertexMoved { .. }
7132 | ChangeEvent::FormulaAdjusted { .. }
7133 | ChangeEvent::NamedRangeAdjusted { .. } => {
7134 self.record_formula_plane_structural_change(StructuralScope::AllSheets);
7135 }
7136 ChangeEvent::SetRowVisibility { sheet_id, row0, .. } => {
7137 self.record_formula_plane_structural_change(StructuralScope::Region(
7138 Region::whole_row(*sheet_id, *row0),
7139 ));
7140 }
7141 ChangeEvent::AddVertex { .. }
7142 | ChangeEvent::RemoveVertex { .. }
7143 | ChangeEvent::EdgeAdded { .. }
7144 | ChangeEvent::EdgeRemoved { .. }
7145 | ChangeEvent::CompoundStart { .. }
7146 | ChangeEvent::CompoundEnd { .. }
7147 | ChangeEvent::StagedFormulaCellChanged { .. } => {}
7148 }
7149 }
7150
7151 fn record_formula_plane_structural_change(&mut self, scope: StructuralScope) {
7152 if self.config.formula_plane_mode == FormulaPlaneMode::Off {
7153 return;
7154 }
7155
7156 match scope {
7157 StructuralScope::Cell { sheet, row, col } => {
7158 self.graph
7159 .formula_authority_mut()
7160 .record_changed_region(Region::point(sheet, row, col));
7161 }
7162 StructuralScope::Region(region) => {
7163 self.graph
7164 .formula_authority_mut()
7165 .record_changed_region(region);
7166 }
7167 StructuralScope::Sheet(sheet_id) => {
7168 self.graph
7169 .formula_authority_mut()
7170 .record_changed_region(Region::whole_sheet(sheet_id));
7171 }
7172 StructuralScope::RemovedSheet(sheet_id) => {
7173 let removed_refs = {
7174 let authority = self.graph.formula_authority();
7175 authority
7176 .active_span_refs()
7177 .into_iter()
7178 .filter(|span_ref| {
7179 authority
7180 .plane
7181 .spans
7182 .get(*span_ref)
7183 .map(|span| span.sheet_id == sheet_id)
7184 .unwrap_or(false)
7185 })
7186 .collect::<Vec<_>>()
7187 };
7188
7189 let authority = self.graph.formula_authority_mut();
7190 for span_ref in removed_refs {
7191 authority.plane.remove_span(span_ref);
7192 }
7193 authority.mark_all_active_spans_dirty();
7194 let _ = authority.rebuild_indexes();
7195 }
7196 StructuralScope::AllSheets => {
7197 let authority = self.graph.formula_authority_mut();
7198 authority.mark_all_active_spans_dirty();
7199 let _ = authority.rebuild_indexes();
7200 }
7201 }
7202 }
7203
7204 fn formula_plane_region_from_cells(cells: &[CellRef]) -> Option<StructuralScope> {
7205 let first = cells.first()?;
7206 let sheet_id = first.sheet_id;
7207 if cells.iter().any(|cell| cell.sheet_id != sheet_id) {
7208 return Some(StructuralScope::AllSheets);
7209 }
7210 let mut row_start = first.coord.row();
7211 let mut row_end = row_start;
7212 let mut col_start = first.coord.col();
7213 let mut col_end = col_start;
7214 for cell in cells.iter().skip(1) {
7215 row_start = row_start.min(cell.coord.row());
7216 row_end = row_end.max(cell.coord.row());
7217 col_start = col_start.min(cell.coord.col());
7218 col_end = col_end.max(cell.coord.col());
7219 }
7220 Some(StructuralScope::Region(Region::rect(
7221 sheet_id, row_start, row_end, col_start, col_end,
7222 )))
7223 }
7224
7225 pub fn set_cell_value_ref(
7226 &mut self,
7227 cell: formualizer_common::SheetCellRef<'_>,
7228 current_sheet: &str,
7229 value: LiteralValue,
7230 ) -> Result<(), ExcelError> {
7231 let owned = cell.into_owned();
7232 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
7233 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
7234 self.set_cell_value(
7235 &sheet_name,
7236 owned.coord.row() + 1,
7237 owned.coord.col() + 1,
7238 value,
7239 )
7240 }
7241
7242 pub fn set_cell_formula_ref(
7243 &mut self,
7244 cell: formualizer_common::SheetCellRef<'_>,
7245 current_sheet: &str,
7246 ast: ASTNode,
7247 ) -> Result<(), ExcelError> {
7248 let owned = cell.into_owned();
7249 let sheet_id = self.resolve_sheet_locator_for_write(owned.sheet, current_sheet)?;
7250 let sheet_name = self.graph.sheet_name(sheet_id).to_string();
7251 self.set_cell_formula(
7252 &sheet_name,
7253 owned.coord.row() + 1,
7254 owned.coord.col() + 1,
7255 ast,
7256 )
7257 }
7258
7259 pub fn get_cell_value_ref(
7260 &self,
7261 cell: formualizer_common::SheetCellRef<'_>,
7262 current_sheet: &str,
7263 ) -> Result<Option<LiteralValue>, ExcelError> {
7264 let owned = cell.into_owned();
7265 let sheet_id = self.resolve_sheet_locator_for_read(owned.sheet, current_sheet)?;
7266 let sheet_name = self.graph.sheet_name(sheet_id);
7267 Ok(self.get_cell_value(sheet_name, owned.coord.row() + 1, owned.coord.col() + 1))
7268 }
7269
7270 pub fn resolve_range_view_sheet_ref<'c>(
7271 &'c self,
7272 r: &formualizer_common::SheetRef<'_>,
7273 current_sheet: &str,
7274 ) -> Result<RangeView<'c>, ExcelError> {
7275 use formualizer_common::SheetLocator;
7276
7277 let sheet_to_opt_name = |loc: SheetLocator<'_>| -> Result<Option<String>, ExcelError> {
7278 match loc {
7279 SheetLocator::Current => Ok(None),
7280 SheetLocator::Name(name) => Ok(Some(name.as_ref().to_string())),
7281 SheetLocator::Id(id) => Ok(Some(self.graph.sheet_name(id).to_string())),
7282 }
7283 };
7284
7285 let rt = match r {
7286 formualizer_common::SheetRef::Cell(cell) => ReferenceType::Cell {
7287 sheet: sheet_to_opt_name(cell.sheet.clone())?,
7288 row: cell.coord.row() + 1,
7289 col: cell.coord.col() + 1,
7290 row_abs: cell.coord.row_abs(),
7291 col_abs: cell.coord.col_abs(),
7292 },
7293 formualizer_common::SheetRef::Range(range) => ReferenceType::Range {
7294 sheet: sheet_to_opt_name(range.sheet.clone())?,
7295 start_row: range.start_row.map(|b| b.index + 1),
7296 start_col: range.start_col.map(|b| b.index + 1),
7297 end_row: range.end_row.map(|b| b.index + 1),
7298 end_col: range.end_col.map(|b| b.index + 1),
7299 start_row_abs: range.start_row.map(|b| b.abs).unwrap_or(false),
7300 start_col_abs: range.start_col.map(|b| b.abs).unwrap_or(false),
7301 end_row_abs: range.end_row.map(|b| b.abs).unwrap_or(false),
7302 end_col_abs: range.end_col.map(|b| b.abs).unwrap_or(false),
7303 },
7304 };
7305
7306 crate::traits::EvaluationContext::resolve_range_view(self, &rt, current_sheet)
7307 }
7308
7309 pub fn set_cell_formula(
7311 &mut self,
7312 sheet: &str,
7313 row: u32,
7314 col: u32,
7315 ast: ASTNode,
7316 ) -> Result<(), ExcelError> {
7317 let sheet_id = self.graph.sheet_id_mut(sheet);
7318 self.demote_span_containing_cell_for_write(
7319 sheet_id,
7320 row.saturating_sub(1),
7321 col.saturating_sub(1),
7322 )
7323 .map_err(Self::editor_error_to_excel)?;
7324 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
7325 let ingested = {
7326 let mut pipeline = self.ingest_pipeline();
7327 pipeline.ingest_formula(FormulaAstInput::Tree(ast), placement, None)?
7328 };
7329 self.graph.set_cell_formula_with_plan(
7330 sheet,
7331 row,
7332 col,
7333 ingested.ast_id,
7334 &ingested.dep_plan,
7335 ingested.dep_plan.volatile,
7336 ingested.dep_plan.dynamic,
7337 )?;
7338 self.record_formula_plane_changed_cell(sheet, row, col);
7339
7340 self.clear_delta_overlay_cell(sheet, row, col);
7345
7346 self.mark_topology_edited();
7348 Ok(())
7349 }
7350
7351 pub fn bulk_set_formulas<I>(&mut self, sheet: &str, items: I) -> Result<usize, ExcelError>
7353 where
7354 I: IntoIterator<Item = (u32, u32, ASTNode)>,
7355 {
7356 let collected: Vec<(u32, u32, ASTNode)> = items.into_iter().collect();
7357 let edited_cells: Vec<(u32, u32)> = collected.iter().map(|(r, c, _)| (*r, *c)).collect();
7358 let sheet_id = self.graph.sheet_id_mut(sheet);
7359 let writes_inside_active_span = edited_cells.iter().any(|(row, col)| {
7360 let placement =
7361 PlacementCoord::new(sheet_id, row.saturating_sub(1), col.saturating_sub(1));
7362 self.graph
7363 .formula_authority()
7364 .plane
7365 .spans
7366 .find_at(placement)
7367 .is_some()
7368 });
7369 if writes_inside_active_span {
7370 self.demote_spans_preserving_computed_overlays(sheet_id, Region::whole_sheet(sheet_id))
7371 .map_err(Self::editor_error_to_excel)?;
7372 }
7373 let ingested = {
7374 let mut pipeline = self.ingest_pipeline();
7375 let inputs = collected.into_iter().map(|(row, col, ast)| {
7376 let placement = CellRef::new(sheet_id, Coord::from_excel(row, col, true, true));
7377 (FormulaAstInput::Tree(ast), placement, None)
7378 });
7379 pipeline.ingest_batch(inputs)?
7380 };
7381 let planned = ingested
7382 .into_iter()
7383 .map(|formula| {
7384 (
7385 formula.placement.coord.row() + 1,
7386 formula.placement.coord.col() + 1,
7387 formula.ast_id,
7388 formula.dep_plan,
7389 )
7390 })
7391 .collect();
7392 let n = self.graph.bulk_set_formulas_with_plans(sheet, planned)?;
7393 for (row, col) in edited_cells {
7394 self.record_formula_plane_changed_cell(sheet, row, col);
7395 }
7396 if n > 0 {
7398 self.mark_topology_edited();
7399 }
7400 Ok(n)
7401 }
7402
7403 #[inline]
7404 fn normalize_public_cell_read(v: LiteralValue) -> Option<LiteralValue> {
7405 match v {
7406 LiteralValue::Empty => None,
7407 LiteralValue::Int(i) => Some(LiteralValue::Number(i as f64)),
7408 other => Some(other),
7409 }
7410 }
7411
7412 pub fn get_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
7414 self.read_cell_value(sheet, row, col)
7415 .and_then(Self::normalize_public_cell_read)
7416 }
7417
7418 pub(crate) fn read_cell_value(&self, sheet: &str, row: u32, col: u32) -> Option<LiteralValue> {
7420 let asheet = self.sheet_store().sheet(sheet)?;
7421 let r0 = row.saturating_sub(1) as usize;
7422 let c0 = col.saturating_sub(1) as usize;
7423 let v = asheet.get_cell_value(r0, c0);
7424 if matches!(v, LiteralValue::Empty) {
7425 None
7426 } else {
7427 Some(v)
7428 }
7429 }
7430
7431 pub(crate) fn read_range_values(
7433 &self,
7434 sheet: &str,
7435 sr: u32,
7436 sc: u32,
7437 er: u32,
7438 ec: u32,
7439 ) -> RangeView<'_> {
7440 let Some(asheet) = self.sheet_store().sheet(sheet) else {
7441 return RangeView::from_owned_rows(Vec::new(), self.config.date_system);
7442 };
7443 if er < sr || ec < sc {
7444 return asheet.range_view(1, 1, 0, 0);
7445 }
7446 let sr0 = sr.saturating_sub(1) as usize;
7447 let sc0 = sc.saturating_sub(1) as usize;
7448 let er0 = er.saturating_sub(1) as usize;
7449 let ec0 = ec.saturating_sub(1) as usize;
7450 asheet.range_view(sr0, sc0, er0, ec0)
7451 }
7452
7453 pub fn get_cell(
7455 &self,
7456 sheet: &str,
7457 row: u32,
7458 col: u32,
7459 ) -> Option<(Option<formualizer_parse::ASTNode>, Option<LiteralValue>)> {
7460 let v = self.get_cell_value(sheet, row, col);
7461 let sheet_id = self.graph.sheet_id(sheet)?;
7462 let coord = Coord::from_excel(row, col, true, true);
7463 let cell = CellRef::new(sheet_id, coord);
7464 if let Some(vid) = self.graph.get_vertex_for_cell(&cell) {
7465 let ast = self.graph.get_formula_id(vid).and_then(|ast_id| {
7466 self.graph
7467 .data_store()
7468 .retrieve_ast(ast_id, self.graph.sheet_reg())
7469 });
7470 return Some((ast, v));
7471 }
7472
7473 let placement =
7474 crate::formula_plane::runtime::PlacementCoord::new(sheet_id, coord.row(), coord.col());
7475 let handle = self
7476 .graph
7477 .formula_authority()
7478 .plane
7479 .resolve_formula_at(placement, None);
7480 let template_id = match handle.resolution {
7481 crate::formula_plane::runtime::FormulaResolution::SpanPlacement {
7482 template_id, ..
7483 } => Some(template_id),
7484 crate::formula_plane::runtime::FormulaResolution::Overlay(overlay_ref) => self
7485 .graph
7486 .formula_authority()
7487 .plane
7488 .formula_overlay
7489 .get(overlay_ref)
7490 .and_then(|overlay| match overlay.kind {
7491 crate::formula_plane::runtime::FormulaOverlayEntryKind::FormulaOverride(
7492 template_id,
7493 ) => Some(template_id),
7494 _ => None,
7495 }),
7496 _ => None,
7497 };
7498 let ast = template_id.and_then(|template_id| {
7499 let ast_id = self
7500 .graph
7501 .formula_authority()
7502 .plane
7503 .templates
7504 .get(template_id)?
7505 .ast_id;
7506 self.graph
7507 .data_store()
7508 .retrieve_ast(ast_id, self.graph.sheet_reg())
7509 });
7510 if let Some(ast) = ast {
7511 Some((Some(ast), v))
7512 } else if v.is_some() {
7513 Some((None, v))
7514 } else {
7515 None
7516 }
7517 }
7518
7519 pub fn begin_batch(&mut self) {
7521 self.graph.begin_batch();
7522 }
7523
7524 pub fn end_batch(&mut self) {
7526 self.graph.end_batch();
7527 }
7528
7529 pub fn begin_deferred_dirty(&mut self) {
7538 self.graph.begin_deferred_dirty();
7539 }
7540
7541 pub fn end_deferred_dirty(&mut self) {
7544 let _ = self.graph.end_deferred_dirty();
7545 }
7546
7547 pub fn dirty_propagation_visits(&self) -> u64 {
7551 self.graph.dirty_propagation_visits()
7552 }
7553
7554 #[inline]
7557 fn record_cell_if_changed(
7558 delta: &mut DeltaCollector,
7559 cell: &CellRef,
7560 old: &LiteralValue,
7561 new: &LiteralValue,
7562 ) {
7563 if old != new {
7564 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
7565 }
7566 }
7567
7568 pub fn evaluate_vertex(&mut self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
7569 if self.graph.formula_authority().active_span_count() > 0 {
7570 let _ = self.evaluate_authoritative_formula_plane_all()?;
7571 }
7572 self.evaluate_vertex_impl(vertex_id, None)
7573 }
7574
7575 fn evaluate_vertex_impl(
7576 &mut self,
7577 vertex_id: VertexId,
7578 delta: Option<&mut DeltaCollector>,
7579 ) -> Result<LiteralValue, ExcelError> {
7580 let mut delta = delta;
7581 if !self.graph.vertex_exists(vertex_id) {
7583 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
7584 .with_message(format!("Vertex not found: {vertex_id:?}")));
7585 }
7586
7587 let kind = self.graph.get_vertex_kind(vertex_id);
7589 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
7590
7591 let ast_id = match kind {
7592 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
7593 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
7594 ast_id
7595 } else {
7596 return Ok(LiteralValue::Number(0.0));
7597 }
7598 }
7599 VertexKind::Empty | VertexKind::Cell => {
7600 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
7601 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
7602 let row = cell_ref.coord.row() + 1;
7603 let col = cell_ref.coord.col() + 1;
7604 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
7605 return Ok(v);
7606 }
7607 }
7608 return Ok(LiteralValue::Number(0.0));
7609 }
7610 VertexKind::NamedScalar => {
7611 let value = self.evaluate_named_scalar(vertex_id, sheet_id)?;
7612 return Ok(value);
7613 }
7614 VertexKind::NamedArray => {
7615 let value = self.evaluate_named_array(vertex_id, sheet_id)?;
7616 return Ok(value);
7617 }
7618 VertexKind::InfiniteRange
7619 | VertexKind::Range
7620 | VertexKind::External
7621 | VertexKind::Table => {
7622 return Ok(LiteralValue::Number(0.0));
7624 }
7625 };
7626
7627 let sheet_name = self.graph.sheet_name(sheet_id);
7629 let cell_ref = self
7630 .graph
7631 .get_cell_ref(vertex_id)
7632 .expect("cell ref for vertex");
7633 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
7634
7635 let result =
7636 interpreter.evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg());
7637
7638 match result {
7640 Ok(cv) => {
7641 let result_literal = cv.into_literal();
7642 match result_literal {
7643 LiteralValue::Array(rows) => {
7644 self.graph
7646 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
7647 let anchor = self
7649 .graph
7650 .get_cell_ref(vertex_id)
7651 .expect("cell ref for vertex");
7652 let sheet_id = anchor.sheet_id;
7653 let h = rows.len() as u32;
7654 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
7655
7656 let spill_cells = (h as u64).saturating_mul(w as u64);
7658 if spill_cells > self.config.spill.max_spill_cells as u64 {
7659 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7660 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7661 .with_message("SpillTooLarge")
7662 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7663 expected_rows: h,
7664 expected_cols: w,
7665 });
7666 let spill_val = LiteralValue::Error(spill_err.clone());
7667 if let Some(d) = delta.as_deref_mut() {
7668 let old = self
7669 .read_cell_value(
7670 self.graph.sheet_name(anchor.sheet_id),
7671 anchor.coord.row() + 1,
7672 anchor.coord.col() + 1,
7673 )
7674 .unwrap_or(LiteralValue::Empty);
7675 if old != spill_val {
7676 d.record_cell(
7677 anchor.sheet_id,
7678 anchor.coord.row(),
7679 anchor.coord.col(),
7680 );
7681 }
7682 }
7683 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7684 if self.config.arrow_storage_enabled
7685 && self.config.delta_overlay_enabled
7686 && self.config.write_formula_overlay_enabled
7687 {
7688 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7689 self.mirror_value_to_computed_overlay(
7690 &sheet_name,
7691 anchor.coord.row() + 1,
7692 anchor.coord.col() + 1,
7693 &spill_val,
7694 );
7695 }
7696 return Ok(spill_val);
7697 }
7698 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);
7702 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
7703 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
7704 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
7705 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7706 .with_message("Spill exceeds sheet bounds")
7707 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7708 expected_rows: h,
7709 expected_cols: w,
7710 });
7711 let spill_val = LiteralValue::Error(spill_err.clone());
7712 if let Some(d) = delta.as_deref_mut() {
7713 let old = self
7714 .read_cell_value(
7715 self.graph.sheet_name(anchor.sheet_id),
7716 anchor.coord.row() + 1,
7717 anchor.coord.col() + 1,
7718 )
7719 .unwrap_or(LiteralValue::Empty);
7720 if old != spill_val {
7721 d.record_cell(
7722 anchor.sheet_id,
7723 anchor.coord.row(),
7724 anchor.coord.col(),
7725 );
7726 }
7727 }
7728 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7729 if self.config.arrow_storage_enabled
7730 && self.config.delta_overlay_enabled
7731 && self.config.write_formula_overlay_enabled
7732 {
7733 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7734 self.mirror_value_to_computed_overlay(
7735 &sheet_name,
7736 anchor.coord.row() + 1,
7737 anchor.coord.col() + 1,
7738 &spill_val,
7739 );
7740 }
7741 return Ok(spill_val);
7742 }
7743 let mut targets = Vec::new();
7744 for r in 0..h {
7745 for c in 0..w {
7746 targets.push(self.graph.make_cell_ref_internal(
7747 sheet_id,
7748 anchor.coord.row() + r,
7749 anchor.coord.col() + c,
7750 ));
7751 }
7752 }
7753
7754 match self.spill_mgr.reserve(
7756 vertex_id,
7757 anchor,
7758 SpillShape { rows: h, cols: w },
7759 SpillMeta {
7760 epoch: self.recalc_epoch,
7761 config: self.config.spill,
7762 },
7763 ) {
7764 Ok(()) => {
7765 if let Err(e) = self.commit_spill_and_mirror(
7769 vertex_id,
7770 &targets,
7771 rows.clone(),
7772 delta.as_deref_mut(),
7773 None,
7774 ) {
7775 self.clear_spill_projection_and_mirror(
7777 vertex_id,
7778 delta.as_deref_mut(),
7779 );
7780 if let Some(d) = delta.as_deref_mut() {
7781 let old = self
7782 .read_cell_value(
7783 self.graph.sheet_name(anchor.sheet_id),
7784 anchor.coord.row() + 1,
7785 anchor.coord.col() + 1,
7786 )
7787 .unwrap_or(LiteralValue::Empty);
7788 let new = LiteralValue::Error(e.clone());
7789 if old != new {
7790 d.record_cell(
7791 anchor.sheet_id,
7792 anchor.coord.row(),
7793 anchor.coord.col(),
7794 );
7795 }
7796 }
7797 let err_val = LiteralValue::Error(e.clone());
7798 self.graph.update_vertex_value(vertex_id, err_val.clone());
7799 if self.config.arrow_storage_enabled
7800 && self.config.delta_overlay_enabled
7801 && self.config.write_formula_overlay_enabled
7802 {
7803 let sheet_name =
7804 self.graph.sheet_name(anchor.sheet_id).to_string();
7805 self.mirror_value_to_computed_overlay(
7806 &sheet_name,
7807 anchor.coord.row() + 1,
7808 anchor.coord.col() + 1,
7809 &err_val,
7810 );
7811 }
7812 return Ok(err_val);
7813 }
7814 let top_left = rows
7816 .first()
7817 .and_then(|r| r.first())
7818 .cloned()
7819 .unwrap_or(LiteralValue::Empty);
7820 self.graph.update_vertex_value(vertex_id, top_left.clone());
7821 Ok(top_left)
7822 }
7823 Err(e) => {
7824 self.clear_spill_projection_and_mirror(
7825 vertex_id,
7826 delta.as_deref_mut(),
7827 );
7828 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
7829 .with_message(
7830 e.message.unwrap_or_else(|| "Spill blocked".to_string()),
7831 )
7832 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
7833 expected_rows: h,
7834 expected_cols: w,
7835 });
7836 let spill_val = LiteralValue::Error(spill_err.clone());
7837 if let Some(d) = delta.as_deref_mut() {
7838 let old = self
7839 .read_cell_value(
7840 self.graph.sheet_name(anchor.sheet_id),
7841 anchor.coord.row() + 1,
7842 anchor.coord.col() + 1,
7843 )
7844 .unwrap_or(LiteralValue::Empty);
7845 if old != spill_val {
7846 d.record_cell(
7847 anchor.sheet_id,
7848 anchor.coord.row(),
7849 anchor.coord.col(),
7850 );
7851 }
7852 }
7853 self.graph.update_vertex_value(vertex_id, spill_val.clone());
7854 if self.config.arrow_storage_enabled
7855 && self.config.delta_overlay_enabled
7856 && self.config.write_formula_overlay_enabled
7857 {
7858 let sheet_name =
7859 self.graph.sheet_name(anchor.sheet_id).to_string();
7860 self.mirror_value_to_computed_overlay(
7861 &sheet_name,
7862 anchor.coord.row() + 1,
7863 anchor.coord.col() + 1,
7864 &spill_val,
7865 );
7866 }
7867 Ok(spill_val)
7868 }
7869 }
7870 }
7871 other => {
7872 let spill_cells = self
7874 .graph
7875 .spill_cells_for_anchor(vertex_id)
7876 .map(|cells| cells.to_vec())
7877 .unwrap_or_default();
7878 if let Some(d) = delta.as_deref_mut()
7879 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7880 {
7881 if spill_cells.is_empty() {
7882 let old = self
7883 .read_cell_value(
7884 self.graph.sheet_name(anchor.sheet_id),
7885 anchor.coord.row() + 1,
7886 anchor.coord.col() + 1,
7887 )
7888 .unwrap_or(LiteralValue::Empty);
7889 if old != other {
7890 d.record_cell(
7891 anchor.sheet_id,
7892 anchor.coord.row(),
7893 anchor.coord.col(),
7894 );
7895 }
7896 } else {
7897 for cell in spill_cells.iter() {
7898 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7899 let old = self
7900 .get_cell_value(
7901 sheet_name,
7902 cell.coord.row() + 1,
7903 cell.coord.col() + 1,
7904 )
7905 .unwrap_or(LiteralValue::Empty);
7906 let new = if cell.sheet_id == anchor.sheet_id
7907 && cell.coord.row() == anchor.coord.row()
7908 && cell.coord.col() == anchor.coord.col()
7909 {
7910 other.clone()
7911 } else {
7912 LiteralValue::Empty
7913 };
7914 Self::record_cell_if_changed(d, cell, &old, &new);
7915 }
7916 }
7917 }
7918 self.graph.clear_spill_region(vertex_id);
7919 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
7920 self.record_formula_plane_structural_change(scope);
7921 }
7922 if self.config.arrow_storage_enabled
7923 && self.config.delta_overlay_enabled
7924 && self.config.write_formula_overlay_enabled
7925 {
7926 let empty = LiteralValue::Empty;
7927 for cell in spill_cells.iter() {
7928 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
7929 self.mirror_value_to_computed_overlay(
7930 &sheet_name,
7931 cell.coord.row() + 1,
7932 cell.coord.col() + 1,
7933 &empty,
7934 );
7935 }
7936 }
7937 self.graph.update_vertex_value(vertex_id, other.clone());
7938 if self.config.arrow_storage_enabled
7940 && self.config.delta_overlay_enabled
7941 && self.config.write_formula_overlay_enabled
7942 {
7943 let anchor = self
7944 .graph
7945 .get_cell_ref(vertex_id)
7946 .expect("cell ref for vertex");
7947 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
7948 self.mirror_value_to_computed_overlay(
7949 &sheet_name,
7950 anchor.coord.row() + 1,
7951 anchor.coord.col() + 1,
7952 &other,
7953 );
7954 }
7955 Ok(other)
7956 }
7957 }
7958 }
7959 Err(e) => {
7960 let spill_cells = self
7963 .graph
7964 .spill_cells_for_anchor(vertex_id)
7965 .map(|cells| cells.to_vec())
7966 .unwrap_or_default();
7967 let err_val = LiteralValue::Error(e.clone());
7968 if let Some(d) = delta
7969 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
7970 {
7971 if spill_cells.is_empty() {
7972 let old = self
7973 .read_cell_value(
7974 self.graph.sheet_name(anchor.sheet_id),
7975 anchor.coord.row() + 1,
7976 anchor.coord.col() + 1,
7977 )
7978 .unwrap_or(LiteralValue::Empty);
7979 if old != err_val {
7980 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
7981 }
7982 } else {
7983 for cell in spill_cells.iter() {
7984 let sheet_name = self.graph.sheet_name(cell.sheet_id);
7985 let old = self
7986 .get_cell_value(
7987 sheet_name,
7988 cell.coord.row() + 1,
7989 cell.coord.col() + 1,
7990 )
7991 .unwrap_or(LiteralValue::Empty);
7992 let new = if cell.sheet_id == anchor.sheet_id
7993 && cell.coord.row() == anchor.coord.row()
7994 && cell.coord.col() == anchor.coord.col()
7995 {
7996 err_val.clone()
7997 } else {
7998 LiteralValue::Empty
7999 };
8000 Self::record_cell_if_changed(d, cell, &old, &new);
8001 }
8002 }
8003 }
8004 self.graph.clear_spill_region(vertex_id);
8005 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
8006 self.record_formula_plane_structural_change(scope);
8007 }
8008 if self.config.arrow_storage_enabled
8009 && self.config.delta_overlay_enabled
8010 && self.config.write_formula_overlay_enabled
8011 {
8012 let empty = LiteralValue::Empty;
8013 for cell in spill_cells.iter() {
8014 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
8015 self.mirror_value_to_computed_overlay(
8016 &sheet_name,
8017 cell.coord.row() + 1,
8018 cell.coord.col() + 1,
8019 &empty,
8020 );
8021 }
8022 }
8023 self.graph.update_vertex_value(vertex_id, err_val.clone());
8024 if self.config.arrow_storage_enabled
8025 && self.config.delta_overlay_enabled
8026 && self.config.write_formula_overlay_enabled
8027 {
8028 let anchor = self
8029 .graph
8030 .get_cell_ref(vertex_id)
8031 .expect("cell ref for vertex");
8032 let sheet_name = self.graph.sheet_name(anchor.sheet_id).to_string();
8033 self.mirror_value_to_computed_overlay(
8034 &sheet_name,
8035 anchor.coord.row() + 1,
8036 anchor.coord.col() + 1,
8037 &err_val,
8038 );
8039 }
8040 Ok(err_val)
8041 }
8042 }
8043 }
8044
8045 fn evaluate_named_scalar(
8046 &mut self,
8047 vertex_id: VertexId,
8048 sheet_id: SheetId,
8049 ) -> Result<LiteralValue, ExcelError> {
8050 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
8051 ExcelError::new(ExcelErrorKind::Name)
8052 .with_message("Named range metadata missing".to_string())
8053 })?;
8054
8055 match &named_range.definition {
8056 NamedDefinition::Cell(cell_ref) => {
8057 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8058 let row = cell_ref.coord.row() + 1;
8059 let col = cell_ref.coord.col() + 1;
8060
8061 if let Some(dep_vertex) = self.graph.get_vertex_for_cell(cell_ref)
8062 && matches!(
8063 self.graph.get_vertex_kind(dep_vertex),
8064 VertexKind::FormulaScalar | VertexKind::FormulaArray
8065 )
8066 {
8067 let value = self.evaluate_vertex(dep_vertex)?;
8069 self.graph.update_vertex_value(vertex_id, value.clone());
8070 Ok(value)
8071 } else {
8072 let value = self
8073 .get_cell_value(sheet_name, row, col)
8074 .unwrap_or(LiteralValue::Empty);
8075 self.graph.update_vertex_value(vertex_id, value.clone());
8076 Ok(value)
8077 }
8078 }
8079 NamedDefinition::Literal(v) => {
8080 let out = v.clone();
8081 self.graph.update_vertex_value(vertex_id, out.clone());
8082 Ok(out)
8083 }
8084 NamedDefinition::Formula { ast, .. } => {
8085 let context_sheet = match named_range.scope {
8086 NameScope::Sheet(id) => id,
8087 NameScope::Workbook => sheet_id,
8088 };
8089 let sheet_name = self.graph.sheet_name(context_sheet);
8090 let cell_ref = self
8091 .graph
8092 .get_cell_ref(vertex_id)
8093 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
8094 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
8095 match interpreter.evaluate_ast(ast) {
8096 Ok(cv) => {
8097 let value = cv.into_literal();
8098 match value {
8099 LiteralValue::Array(_) => {
8100 let err = ExcelError::new(ExcelErrorKind::NImpl)
8101 .with_message("Array result in scalar named range".to_string());
8102 let err_val = LiteralValue::Error(err.clone());
8103 self.graph.update_vertex_value(vertex_id, err_val.clone());
8104 Ok(err_val)
8105 }
8106 other => {
8107 self.graph.update_vertex_value(vertex_id, other.clone());
8108 Ok(other)
8109 }
8110 }
8111 }
8112 Err(err) => {
8113 let err_val = LiteralValue::Error(err.clone());
8114 self.graph.update_vertex_value(vertex_id, err_val.clone());
8115 Ok(err_val)
8116 }
8117 }
8118 }
8119 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
8120 .with_message("Range-valued name evaluated as scalar".to_string())),
8121 }
8122 }
8123
8124 fn evaluate_named_array(
8125 &mut self,
8126 vertex_id: VertexId,
8127 sheet_id: SheetId,
8128 ) -> Result<LiteralValue, ExcelError> {
8129 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
8130 ExcelError::new(ExcelErrorKind::Name)
8131 .with_message("Named range metadata missing".to_string())
8132 })?;
8133
8134 let out = match &named_range.definition {
8135 NamedDefinition::Range(range_ref) => {
8136 if range_ref.start.sheet_id != range_ref.end.sheet_id {
8137 return Err(ExcelError::new(ExcelErrorKind::Ref)
8138 .with_message("Named range cannot span sheets".to_string()));
8139 }
8140
8141 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
8142 let sr0 = range_ref.start.coord.row();
8143 let sc0 = range_ref.start.coord.col();
8144 let er0 = range_ref.end.coord.row();
8145 let ec0 = range_ref.end.coord.col();
8146 if sr0 > er0 || sc0 > ec0 {
8147 return Err(ExcelError::new(ExcelErrorKind::Ref)
8148 .with_message("Invalid named range bounds".to_string()));
8149 }
8150
8151 let h = (er0 - sr0 + 1) as usize;
8152 let w = (ec0 - sc0 + 1) as usize;
8153 let cell_count = (h as u64).saturating_mul(w as u64);
8154 if cell_count > self.config.spill.max_spill_cells as u64 {
8155 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
8156 "Named range too large to materialize as an array".to_string(),
8157 ));
8158 }
8159
8160 let mut rows = Vec::with_capacity(h);
8161 for r0 in sr0..=er0 {
8162 let mut row = Vec::with_capacity(w);
8163 for c0 in sc0..=ec0 {
8164 let v = self
8165 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
8166 .unwrap_or(LiteralValue::Empty);
8167 row.push(v);
8168 }
8169 rows.push(row);
8170 }
8171 LiteralValue::Array(rows)
8172 }
8173 NamedDefinition::Cell(cell_ref) => {
8174 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
8175 let row = cell_ref.coord.row() + 1;
8176 let col = cell_ref.coord.col() + 1;
8177 let v = self
8178 .get_cell_value(sheet_name, row, col)
8179 .unwrap_or(LiteralValue::Empty);
8180 LiteralValue::Array(vec![vec![v]])
8181 }
8182 NamedDefinition::Literal(v) => LiteralValue::Array(vec![vec![v.clone()]]),
8183 NamedDefinition::Formula { ast, .. } => {
8184 let context_sheet = match named_range.scope {
8185 NameScope::Sheet(id) => id,
8186 NameScope::Workbook => sheet_id,
8187 };
8188 let sheet_name = self.graph.sheet_name(context_sheet);
8189 let cell_ref = self
8190 .graph
8191 .get_cell_ref(vertex_id)
8192 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
8193 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
8194 match interpreter.evaluate_ast(ast) {
8195 Ok(cv) => {
8196 let v = cv.into_literal();
8197 match v {
8198 LiteralValue::Array(_) => v,
8199 other => LiteralValue::Array(vec![vec![other]]),
8200 }
8201 }
8202 Err(err) => LiteralValue::Error(err),
8203 }
8204 }
8205 };
8206
8207 self.graph.update_vertex_value(vertex_id, out.clone());
8208 Ok(out)
8209 }
8210
8211 pub fn evaluate_until(
8213 &mut self,
8214 targets: &[(&str, u32, u32)],
8215 ) -> Result<EvalResult, ExcelError> {
8216 #[cfg(feature = "tracing")]
8217 let _span_eval = tracing::info_span!("evaluate_until", targets = targets.len()).entered();
8218 let start = crate::instant::FzInstant::now();
8219 self.begin_evaluation_request();
8220 self.graph.flush_pending_edge_deltas();
8223 let _source_cache = self.source_cache_session();
8224 if self.graph.formula_authority().active_span_count() > 0 {
8225 return self.evaluate_authoritative_formula_plane_all();
8226 }
8227
8228 let mut target_addrs = Vec::new();
8230 for (sheet, row, col) in targets {
8231 let sheet_id = self.graph.sheet_id_mut(sheet);
8234 let coord = Coord::from_excel(*row, *col, true, true);
8235 target_addrs.push(CellRef::new(sheet_id, coord));
8236 }
8237
8238 let mut target_vertex_ids = Vec::new();
8240 for addr in &target_addrs {
8241 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
8242 target_vertex_ids.push(*vertex_id);
8243 }
8244 }
8245
8246 if target_vertex_ids.is_empty() {
8247 return Ok(EvalResult {
8248 computed_vertices: 0,
8249 cycle_errors: 0,
8250 elapsed: start.elapsed(),
8251 });
8252 }
8253
8254 #[cfg(feature = "tracing")]
8256 let _span_sub = tracing::info_span!("demand_subgraph_build").entered();
8257 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
8258 #[cfg(feature = "tracing")]
8259 drop(_span_sub);
8260
8261 if precedents_to_eval.is_empty() {
8262 return Ok(EvalResult {
8263 computed_vertices: 0,
8264 cycle_errors: 0,
8265 elapsed: start.elapsed(),
8266 });
8267 }
8268
8269 let scheduler = Scheduler::new(&self.graph);
8271 #[cfg(feature = "tracing")]
8272 let _span_sched =
8273 tracing::info_span!("schedule_build", vertices = precedents_to_eval.len()).entered();
8274 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
8275 #[cfg(feature = "tracing")]
8276 drop(_span_sched);
8277
8278 let mut cycle_errors = 0;
8282 let mut computed_vertices = 0;
8283 for &unit in &schedule.units {
8284 match unit {
8285 ScheduleUnit::Cycle(i) => {
8286 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
8287 cycle_errors += 1;
8288 }
8289 }
8290 ScheduleUnit::Layer(i) => {
8291 let layer = schedule.unit_layer(i);
8292 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8293 computed_vertices += self.evaluate_layer_parallel(layer)?;
8294 } else {
8295 computed_vertices += self.evaluate_layer_sequential(layer)?;
8296 }
8297 }
8298 }
8299 }
8300
8301 self.graph.clear_dirty_flags(&precedents_to_eval);
8305
8306 self.redirty_for_next_recalc();
8308
8309 Ok(EvalResult {
8310 computed_vertices,
8311 cycle_errors,
8312 elapsed: start.elapsed(),
8313 })
8314 }
8315
8316 fn evaluate_until_with_delta_collector(
8317 &mut self,
8318 targets: &[(&str, u32, u32)],
8319 delta: &mut DeltaCollector,
8320 ) -> Result<EvalResult, ExcelError> {
8321 #[cfg(feature = "tracing")]
8322 let _span_eval =
8323 tracing::info_span!("evaluate_until_with_delta", targets = targets.len()).entered();
8324 let start = crate::instant::FzInstant::now();
8325 self.begin_evaluation_request();
8326 self.graph.flush_pending_edge_deltas();
8327 let _source_cache = self.source_cache_session();
8328
8329 let mut target_addrs = Vec::new();
8330 for (sheet, row, col) in targets {
8331 let sheet_id = self.graph.sheet_id_mut(sheet);
8332 let coord = Coord::from_excel(*row, *col, true, true);
8333 target_addrs.push(CellRef::new(sheet_id, coord));
8334 }
8335
8336 let mut target_vertex_ids = Vec::new();
8337 for addr in &target_addrs {
8338 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
8339 target_vertex_ids.push(*vertex_id);
8340 }
8341 }
8342
8343 if target_vertex_ids.is_empty() {
8344 return Ok(EvalResult {
8345 computed_vertices: 0,
8346 cycle_errors: 0,
8347 elapsed: start.elapsed(),
8348 });
8349 }
8350
8351 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
8352
8353 if precedents_to_eval.is_empty() {
8354 return Ok(EvalResult {
8355 computed_vertices: 0,
8356 cycle_errors: 0,
8357 elapsed: start.elapsed(),
8358 });
8359 }
8360
8361 let scheduler = Scheduler::new(&self.graph);
8362 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
8363
8364 let mut cycle_errors = 0;
8365 let mut computed_vertices = 0;
8366 for &unit in &schedule.units {
8367 match unit {
8368 ScheduleUnit::Cycle(i) => {
8369 if self.handle_cycle_unit(schedule.unit_cycle(i), Some(delta), None, None)? > 0
8370 {
8371 cycle_errors += 1;
8372 }
8373 }
8374 ScheduleUnit::Layer(i) => {
8375 let layer = schedule.unit_layer(i);
8376 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
8377 computed_vertices +=
8378 self.evaluate_layer_parallel_with_delta(layer, delta)?;
8379 } else {
8380 computed_vertices +=
8381 self.evaluate_layer_sequential_with_delta(layer, delta)?;
8382 }
8383 }
8384 }
8385 }
8386
8387 self.graph.clear_dirty_flags(&precedents_to_eval);
8388 self.redirty_for_next_recalc();
8389
8390 Ok(EvalResult {
8391 computed_vertices,
8392 cycle_errors,
8393 elapsed: start.elapsed(),
8394 })
8395 }
8396
8397 pub fn build_recalc_plan(&self) -> Result<RecalcPlan, ExcelError> {
8399 let mut vertices: Vec<VertexId> = self.graph.vertices_with_formulas().collect();
8400 vertices.sort_unstable();
8401 if vertices.is_empty() {
8402 return Ok(RecalcPlan {
8403 schedule: crate::engine::Schedule {
8404 units: Vec::new(),
8405 layers: Vec::new(),
8406 cycles: Vec::new(),
8407 },
8408 has_dynamic_refs: false,
8409 });
8410 }
8411
8412 let has_dynamic_refs = vertices.iter().copied().any(|v| self.graph.is_dynamic(v));
8413 let (schedule, _, _) = self.create_evaluation_schedule_uncached(&vertices)?;
8414 Ok(RecalcPlan {
8415 schedule,
8416 has_dynamic_refs,
8417 })
8418 }
8419
8420 pub fn evaluate_recalc_plan(&mut self, plan: &RecalcPlan) -> Result<EvalResult, ExcelError> {
8422 self.begin_evaluation_request();
8423 let _source_cache = self.source_cache_session();
8424 self.validate_deterministic_mode()?;
8425 if self.config.defer_graph_building {
8426 self.build_graph_all()?;
8427 }
8428 if self.graph.formula_authority().active_span_count() > 0 {
8429 return self.evaluate_authoritative_formula_plane_all();
8430 }
8431
8432 let start = crate::instant::FzInstant::now();
8433 let dirty_vertices = self.graph.get_evaluation_vertices();
8434 if dirty_vertices.is_empty() {
8435 return Ok(EvalResult {
8436 computed_vertices: 0,
8437 cycle_errors: 0,
8438 elapsed: start.elapsed(),
8439 });
8440 }
8441
8442 if plan.has_dynamic_refs {
8445 self.virtual_dep_fallback_activations =
8446 self.virtual_dep_fallback_activations.saturating_add(1);
8447 return self.evaluate_all();
8448 }
8449
8450 let dirty_set: FxHashSet<VertexId> = dirty_vertices.iter().copied().collect();
8451 let mut computed_vertices = 0;
8452 let mut cycle_errors = 0;
8453
8454 for &unit in &plan.schedule.units {
8455 match unit {
8456 ScheduleUnit::Cycle(i) => {
8457 let stamped = self.handle_cycle_unit(
8462 plan.schedule.unit_cycle(i),
8463 None,
8464 Some(&dirty_set),
8465 None,
8466 )?;
8467 if stamped > 0 {
8468 cycle_errors += 1;
8469 }
8470 }
8471 ScheduleUnit::Layer(i) => {
8472 let work: Vec<VertexId> = plan
8473 .schedule
8474 .unit_layer(i)
8475 .vertices
8476 .iter()
8477 .copied()
8478 .filter(|v| dirty_set.contains(v))
8479 .collect();
8480 if work.is_empty() {
8481 continue;
8482 }
8483 let temp_layer = crate::engine::scheduler::Layer { vertices: work };
8484 if self.thread_pool.is_some() && temp_layer.vertices.len() > 1 {
8485 computed_vertices += self.evaluate_layer_parallel(&temp_layer)?;
8486 } else {
8487 computed_vertices += self.evaluate_layer_sequential(&temp_layer)?;
8488 }
8489 }
8490 }
8491 }
8492
8493 self.graph.clear_dirty_flags(&dirty_vertices);
8494 self.redirty_for_next_recalc();
8495
8496 Ok(EvalResult {
8497 computed_vertices,
8498 cycle_errors,
8499 elapsed: start.elapsed(),
8500 })
8501 }
8502 fn evaluate_authoritative_formula_plane_all(&mut self) -> Result<EvalResult, ExcelError> {
8503 self.begin_evaluation_request();
8510 if self.graph.formula_authority().active_span_count() == 0 {
8515 #[cfg(test)]
8516 {
8517 self.last_formula_plane_span_eval_report = None;
8518 }
8519 return self.evaluate_all_legacy_impl();
8520 }
8521
8522 let current_indexes_epoch = self.graph.formula_authority().indexes_epoch();
8527 let span_seed_mode = if self.formula_plane_indexes_epoch_seen != current_indexes_epoch {
8528 SpanSeedMode::WholeAll
8529 } else {
8530 SpanSeedMode::DirtyClosure
8531 };
8532 let pending_changed_regions = self
8535 .graph
8536 .formula_authority_mut()
8537 .take_pending_changed_regions();
8538
8539 if matches!(span_seed_mode, SpanSeedMode::DirtyClosure)
8547 && pending_changed_regions.is_empty()
8548 {
8549 #[cfg(test)]
8550 {
8551 self.last_formula_plane_span_eval_report = None;
8552 }
8553 return self.evaluate_all_legacy_impl();
8554 }
8555
8556 let start = crate::instant::FzInstant::now();
8557 let mut span_seed_mode = span_seed_mode;
8558 let mut pending_changed_regions = pending_changed_regions;
8559 let mut prepass_cycle_errors = 0usize;
8562 const MAX_CYCLE_DEMOTE_ITERS: usize = 64;
8563 let mut cycle_demote_iters = 0usize;
8564 let (schedule, span_refs_by_id, plane_epoch, legacy_vertices) = loop {
8565 let (schedule, span_refs_by_id, plane_epoch, legacy_vertices) =
8566 self.build_formula_plane_mixed_schedule(span_seed_mode, &pending_changed_regions)?;
8567
8568 if schedule.is_authoritative_safe() {
8569 break (schedule, span_refs_by_id, plane_epoch, legacy_vertices);
8570 }
8571
8572 let has_cycle_fallback = schedule.stats.cycle_count > 0
8582 || schedule
8583 .fallbacks
8584 .iter()
8585 .any(|fb| fb.reason == MixedScheduleFallbackReason::CycleDetected);
8586 if !has_cycle_fallback {
8587 self.formula_plane_capacity_bailouts =
8588 self.formula_plane_capacity_bailouts.saturating_add(1);
8589 #[cfg(test)]
8590 {
8591 self.last_formula_plane_span_eval_report = None;
8592 }
8593 return self.evaluate_all_legacy_impl();
8594 }
8595
8596 let cyclic_spans = self.collect_cyclic_span_refs(&schedule, &span_refs_by_id);
8606 if !cyclic_spans.is_empty() {
8607 self.demote_cyclic_spans(&cyclic_spans)?;
8608 }
8609
8610 if self.graph.formula_authority().active_span_count() == 0 {
8611 return self.evaluate_all_legacy_impl();
8614 }
8615
8616 prepass_cycle_errors =
8620 prepass_cycle_errors.saturating_add(self.evaluate_legacy_cycle_prepass()?);
8621
8622 span_seed_mode = SpanSeedMode::WholeAll;
8626 pending_changed_regions = self
8627 .graph
8628 .formula_authority_mut()
8629 .take_pending_changed_regions();
8630
8631 cycle_demote_iters += 1;
8632 if cycle_demote_iters >= MAX_CYCLE_DEMOTE_ITERS {
8633 return self.evaluate_all_legacy_impl();
8638 }
8639 };
8640
8641 let mut computed_vertices = 0usize;
8642 #[cfg(test)]
8643 {
8644 self.last_formula_plane_span_eval_report = None;
8645 }
8646 for layer in schedule.layers {
8647 let mut buffer = ComputedWriteBuffer::default();
8648 let mut sink = SpanComputedWriteSink::new(&mut buffer);
8649 let work_items = layer.work;
8650 let mut work_index = 0usize;
8651 while work_index < work_items.len() {
8652 match work_items[work_index].producer {
8653 FormulaProducerId::Span(span_id) => {
8654 let span_ref = *span_refs_by_id.get(&span_id).ok_or_else(|| {
8655 ExcelError::new(ExcelErrorKind::NImpl)
8656 .with_message("FormulaPlane schedule referenced a stale span")
8657 })?;
8658 let sheet_id = {
8659 let authority = self.graph.formula_authority();
8660 let span = authority.plane.spans.get(span_ref).ok_or_else(|| {
8661 ExcelError::new(ExcelErrorKind::NImpl)
8662 .with_message("FormulaPlane schedule referenced a stale span")
8663 })?;
8664 span.sheet_id
8665 };
8666 let current_sheet = self.graph.sheet_name(sheet_id);
8667 let authority = self.graph.formula_authority();
8668 let evaluator = SpanEvaluator::new(
8669 &authority.plane,
8670 self,
8671 current_sheet,
8672 self.graph.data_store(),
8673 self.graph.sheet_reg(),
8674 );
8675 #[cfg(test)]
8676 let mut last_group_report = None;
8677 while work_index < work_items.len() {
8678 let FormulaProducerId::Span(group_span_id) =
8679 work_items[work_index].producer
8680 else {
8681 break;
8682 };
8683 let group_span_ref =
8684 *span_refs_by_id.get(&group_span_id).ok_or_else(|| {
8685 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8686 "FormulaPlane schedule referenced a stale span",
8687 )
8688 })?;
8689 let group_sheet_id = {
8690 let authority = self.graph.formula_authority();
8691 let span =
8692 authority.plane.spans.get(group_span_ref).ok_or_else(|| {
8693 ExcelError::new(ExcelErrorKind::NImpl).with_message(
8694 "FormulaPlane schedule referenced a stale span",
8695 )
8696 })?;
8697 span.sheet_id
8698 };
8699 if group_sheet_id != sheet_id {
8700 break;
8701 }
8702
8703 let dirty = producer_dirty_to_span_dirty(
8704 work_items[work_index].dirty.clone(),
8705 group_span_ref,
8706 );
8707 let task = SpanEvalTask {
8708 span: group_span_ref,
8709 dirty,
8710 plane_epoch,
8711 };
8712 let report =
8713 evaluator.evaluate_task(&task, &mut sink).map_err(|err| {
8714 ExcelError::new(ExcelErrorKind::NImpl).with_message(format!(
8715 "FormulaPlane span evaluation failed: {err:?}"
8716 ))
8717 })?;
8718 #[cfg(test)]
8719 {
8720 last_group_report = Some(report.clone());
8721 }
8722 computed_vertices = computed_vertices
8723 .saturating_add(report.span_eval_placement_count as usize);
8724 work_index = work_index.saturating_add(1);
8725 }
8726 #[cfg(test)]
8727 {
8728 if let Some(report) = last_group_report {
8729 self.last_formula_plane_span_eval_report = Some(report);
8730 }
8731 }
8732 }
8733 FormulaProducerId::Legacy(_) => {
8734 let mut vertices = Vec::new();
8743 while work_index < work_items.len() {
8744 let FormulaProducerId::Legacy(vertex_id) =
8745 work_items[work_index].producer
8746 else {
8747 break;
8748 };
8749 vertices.push(vertex_id);
8750 work_index = work_index.saturating_add(1);
8751 }
8752 let legacy_layer = crate::engine::scheduler::Layer { vertices };
8753 let evaluated =
8754 if self.thread_pool.is_some() && legacy_layer.vertices.len() > 1 {
8755 self.evaluate_layer_parallel(&legacy_layer)?
8756 } else {
8757 self.evaluate_layer_sequential(&legacy_layer)?
8758 };
8759 computed_vertices = computed_vertices.saturating_add(evaluated);
8760 }
8761 }
8762 }
8763 self.flush_computed_write_buffer(&mut buffer)?;
8764 }
8765
8766 self.graph.clear_dirty_flags(&legacy_vertices);
8767 self.redirty_for_next_recalc();
8771 self.formula_plane_indexes_epoch_seen = self.graph.formula_authority().indexes_epoch();
8774 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
8775 Ok(EvalResult {
8776 computed_vertices,
8777 cycle_errors: prepass_cycle_errors,
8778 elapsed: start.elapsed(),
8779 })
8780 }
8781
8782 fn build_formula_plane_mixed_schedule(
8783 &self,
8784 span_seed_mode: SpanSeedMode,
8785 pending_changed_regions: &[Region],
8786 ) -> Result<FormulaPlaneMixedScheduleBuild, ExcelError> {
8787 let authority = self.graph.formula_authority();
8788 let mut producer_results = FormulaProducerResultIndex::default();
8789 let mut consumer_reads = FormulaConsumerReadIndex::default();
8790 let mut work = Vec::new();
8791
8792 let dirty_legacy: rustc_hash::FxHashSet<VertexId> =
8798 self.graph.get_evaluation_vertices().into_iter().collect();
8799
8800 let span_refs = authority.active_span_refs();
8801 let span_refs_by_id = span_refs
8802 .iter()
8803 .copied()
8804 .map(|span_ref| (span_ref.id, span_ref))
8805 .collect::<BTreeMap<_, _>>();
8806 for span_ref in &span_refs {
8807 let span = authority.plane.spans.get(*span_ref).ok_or_else(|| {
8808 ExcelError::new(ExcelErrorKind::NImpl)
8809 .with_message("FormulaPlane active span ref is stale")
8810 })?;
8811 let result_region = Region::from_domain(span.result_region.domain());
8812 producer_results.insert_producer(FormulaProducerId::Span(span.id), result_region);
8813 let Some(read_summary_id) = span.read_summary_id else {
8814 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8815 .with_message("FormulaPlane active span is missing read summary"));
8816 };
8817 let Some(read_summary) = authority.plane.span_read_summaries.get(read_summary_id)
8818 else {
8819 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8820 .with_message("FormulaPlane active span has stale read summary"));
8821 };
8822 if read_summary.result_region != result_region {
8823 return Err(ExcelError::new(ExcelErrorKind::NImpl)
8824 .with_message("FormulaPlane active span read summary is stale"));
8825 }
8826 for dependency in &read_summary.dependencies {
8827 consumer_reads.insert_read(
8828 FormulaProducerId::Span(span.id),
8829 dependency.read_region,
8830 read_summary.result_region,
8831 dependency.projection,
8832 );
8833 }
8834 if matches!(span_seed_mode, SpanSeedMode::WholeAll) {
8835 work.push(FormulaProducerWork {
8836 producer: FormulaProducerId::Span(span.id),
8837 dirty: ProducerDirtyDomain::Whole,
8838 });
8839 }
8840 }
8841
8842 let legacy_vertices = self.graph.formula_vertices();
8843 let mut scheduled_legacy_vertices = Vec::new();
8844 for vertex in &legacy_vertices {
8845 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8846 continue;
8847 };
8848 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8849 producer_results.insert_producer(FormulaProducerId::Legacy(*vertex), result_region);
8850 if dirty_legacy.contains(vertex) {
8851 scheduled_legacy_vertices.push(*vertex);
8852 work.push(FormulaProducerWork {
8853 producer: FormulaProducerId::Legacy(*vertex),
8854 dirty: ProducerDirtyDomain::Whole,
8855 });
8856 }
8857 }
8858
8859 for vertex in &legacy_vertices {
8860 let Some(cell) = self.graph.get_cell_ref_for_vertex(*vertex) else {
8861 continue;
8862 };
8863 let result_region = Region::point(cell.sheet_id, cell.coord.row(), cell.coord.col());
8864 let mut seen = rustc_hash::FxHashSet::default();
8865 for dep in self.graph.get_dependencies(*vertex) {
8866 let Some(dep_cell) = self.graph.get_cell_ref_for_vertex(dep) else {
8867 continue;
8868 };
8869 let read_region = Region::point(
8870 dep_cell.sheet_id,
8871 dep_cell.coord.row(),
8872 dep_cell.coord.col(),
8873 );
8874 if seen.insert(read_region) {
8875 consumer_reads.insert_read(
8876 FormulaProducerId::Legacy(*vertex),
8877 read_region,
8878 result_region,
8879 DirtyProjectionRule::WholeResult,
8880 );
8881 }
8882 }
8883 if let Some(ranges) = self.graph.get_range_dependencies(*vertex) {
8884 for range in ranges {
8885 let Some(read_region) = self.shared_range_to_region_pattern(range)? else {
8886 continue;
8887 };
8888 if seen.insert(read_region) {
8889 consumer_reads.insert_read(
8890 FormulaProducerId::Legacy(*vertex),
8891 read_region,
8892 result_region,
8893 DirtyProjectionRule::WholeResult,
8894 );
8895 }
8896 }
8897 }
8898 }
8899
8900 if matches!(span_seed_mode, SpanSeedMode::DirtyClosure)
8905 && !pending_changed_regions.is_empty()
8906 {
8907 use crate::formula_plane::producer::compute_dirty_closure;
8908 let producer_results_ref = &producer_results;
8909 let closure = compute_dirty_closure(
8910 &consumer_reads,
8911 pending_changed_regions.iter().copied(),
8912 |producer| producer_results_ref.producer_result_region(producer),
8913 );
8914 for fallback_work in closure.work {
8915 work.push(fallback_work);
8916 }
8917 if !closure.fallbacks.is_empty() {
8920 let mut already_whole: rustc_hash::FxHashSet<_> = work
8921 .iter()
8922 .filter_map(|w| match (w.producer, &w.dirty) {
8923 (FormulaProducerId::Span(id), ProducerDirtyDomain::Whole) => Some(id),
8924 _ => None,
8925 })
8926 .collect();
8927 for fb in &closure.fallbacks {
8928 if let FormulaProducerId::Span(id) = fb.consumer
8929 && already_whole.insert(id)
8930 {
8931 work.push(FormulaProducerWork {
8932 producer: FormulaProducerId::Span(id),
8933 dirty: ProducerDirtyDomain::Whole,
8934 });
8935 }
8936 }
8937 }
8938 }
8939
8940 let schedule = build_mixed_schedule(work, &producer_results, &consumer_reads);
8941 Ok((
8942 schedule,
8943 span_refs_by_id,
8944 authority.plane.epoch().0,
8945 scheduled_legacy_vertices,
8946 ))
8947 }
8948}
8949
8950#[derive(Clone, Copy, Debug)]
8954enum SpanSeedMode {
8955 WholeAll,
8956 DirtyClosure,
8957}
8958
8959impl<R> Engine<R>
8960where
8961 R: EvaluationContext,
8962{
8963 fn shared_range_to_region_pattern(
8964 &self,
8965 range: &crate::reference::SharedRangeRef<'static>,
8966 ) -> Result<Option<Region>, ExcelError> {
8967 use crate::reference::SharedSheetLocator;
8968 let sheet_id = match range.sheet {
8969 SharedSheetLocator::Id(id) => id,
8970 SharedSheetLocator::Current => self.graph.default_sheet_id(),
8971 SharedSheetLocator::Name(_) => return Ok(None),
8972 };
8973 match (
8974 range.start_row,
8975 range.end_row,
8976 range.start_col,
8977 range.end_col,
8978 ) {
8979 (Some(sr), Some(er), Some(sc), Some(ec)) => Ok(Some(Region::rect(
8980 sheet_id, sr.index, er.index, sc.index, ec.index,
8981 ))),
8982 (None, None, Some(sc), Some(ec)) if sc.index == ec.index => {
8983 Ok(Some(Region::whole_col(sheet_id, sc.index)))
8984 }
8985 (Some(sr), Some(er), None, None) if sr.index == er.index => {
8986 Ok(Some(Region::whole_row(sheet_id, sr.index)))
8987 }
8988 _ => Ok(None),
8989 }
8990 }
8991
8992 pub fn evaluate_all(&mut self) -> Result<EvalResult, ExcelError> {
8994 debug_assert!(
8995 !self.graph.deferred_dirty_active(),
8996 "deferred-dirty scope leaked into evaluate_all: a begin_deferred_dirty \
8997 was not balanced by end_deferred_dirty"
8998 );
8999 self.lookup_index_cache.reset_counters();
9000 let _source_cache = self.source_cache_session();
9001 self.validate_deterministic_mode()?;
9002 if self.config.defer_graph_building {
9003 self.build_graph_all()?;
9005 }
9006 self.evaluate_all_coordinator()
9007 }
9008
9009 fn evaluate_all_coordinator(&mut self) -> Result<EvalResult, ExcelError> {
9014 self.begin_evaluation_request();
9015 if self.config.formula_plane_mode == FormulaPlaneMode::AuthoritativeExperimental {
9016 return self.evaluate_authoritative_formula_plane_all();
9017 }
9018 self.evaluate_all_legacy_impl()
9019 }
9020
9021 fn legacy_pass_run_units(
9027 &mut self,
9028 schedule: &crate::engine::scheduler::Schedule,
9029 ) -> Result<(usize, usize), ExcelError> {
9030 let mut computed_vertices = 0;
9031 let mut cycle_count = 0;
9032 for &unit in &schedule.units {
9033 match unit {
9034 ScheduleUnit::Cycle(i) => {
9035 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
9036 cycle_count += 1;
9037 }
9038 }
9039 ScheduleUnit::Layer(i) => {
9040 let layer = schedule.unit_layer(i);
9041 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9042 computed_vertices += self.evaluate_layer_parallel(layer)?;
9043 } else {
9044 computed_vertices += self.evaluate_layer_sequential(layer)?;
9045 }
9046 }
9047 }
9048 }
9049 Ok((computed_vertices, cycle_count))
9050 }
9051
9052 fn evaluate_all_legacy_impl(&mut self) -> Result<EvalResult, ExcelError> {
9064 self.reset_virtual_dep_telemetry_if_disabled();
9065 #[cfg(feature = "tracing")]
9066 let _span_eval = tracing::info_span!("evaluate_all").entered();
9067 let start = crate::instant::FzInstant::now();
9068 let mut computed_vertices = 0;
9069 let mut cycle_errors = 0;
9070 let mut replan_iterations = 0;
9071 const MAX_REPLAN: usize = 5;
9072 let mut telemetry = self
9073 .config
9074 .enable_virtual_dep_telemetry
9075 .then(|| self.start_virtual_dep_telemetry());
9076
9077 loop {
9078 let to_evaluate = self.graph.get_evaluation_vertices();
9079 if to_evaluate.is_empty() {
9080 if let Some(t) = telemetry.as_mut()
9081 && t.bailout_reason.is_none()
9082 {
9083 t.bailout_reason = Some("no_work");
9084 }
9085 break;
9086 }
9087
9088 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9089 if let Some(t) = telemetry.as_mut() {
9090 Self::accumulate_schedule_meta(t, &meta);
9091 }
9092
9093 let (pass_computed, pass_cycles) = self.legacy_pass_run_units(&schedule)?;
9094 computed_vertices += pass_computed;
9095 cycle_errors += pass_cycles;
9096
9097 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9099 if let Some(t) = telemetry.as_mut() {
9100 t.changed_vdeps_total += changed_vertices.len();
9101 }
9102
9103 self.graph.clear_dirty_flags(&to_evaluate);
9104 for v in &changed_vertices {
9105 self.graph.set_dirty(*v, true);
9106 }
9107
9108 if changed_vertices.is_empty() {
9109 if let Some(t) = telemetry.as_mut() {
9110 t.bailout_reason = Some("converged");
9111 }
9112 break;
9113 }
9114 if replan_iterations >= MAX_REPLAN {
9115 if let Some(t) = telemetry.as_mut() {
9116 t.bailout_reason = Some("max_replan");
9117 }
9118 break;
9119 }
9120
9121 replan_iterations += 1;
9122 }
9123
9124 if let Some(mut t) = telemetry {
9125 t.replan_iterations = replan_iterations;
9126 self.last_virtual_dep_telemetry = t;
9127 }
9128
9129 self.redirty_for_next_recalc();
9131
9132 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9134
9135 Ok(EvalResult {
9136 computed_vertices,
9137 cycle_errors,
9138 elapsed: start.elapsed(),
9139 })
9140 }
9141
9142 pub fn evaluate_all_with_delta(&mut self) -> Result<(EvalResult, EvalDelta), ExcelError> {
9143 let mut collector = DeltaCollector::new(DeltaMode::Cells);
9144 let result = self.evaluate_all_with_delta_collector(&mut collector)?;
9145 Ok((result, collector.finish()))
9146 }
9147
9148 fn evaluate_all_with_delta_collector(
9149 &mut self,
9150 delta: &mut DeltaCollector,
9151 ) -> Result<EvalResult, ExcelError> {
9152 self.begin_evaluation_request();
9153 let _source_cache = self.source_cache_session();
9154 if self.config.defer_graph_building {
9155 self.build_graph_all()?;
9156 }
9157 if self.graph.formula_authority().active_span_count() > 0 {
9158 let _ = delta;
9159 return self.evaluate_authoritative_formula_plane_all();
9160 }
9161 self.reset_virtual_dep_telemetry_if_disabled();
9162 #[cfg(feature = "tracing")]
9163 let _span_eval = tracing::info_span!("evaluate_all_with_delta").entered();
9164 let start = crate::instant::FzInstant::now();
9165 let mut computed_vertices = 0;
9166 let mut cycle_errors = 0;
9167
9168 let mut replan_iterations = 0;
9169 const MAX_REPLAN: usize = 5;
9170 let mut telemetry = self
9171 .config
9172 .enable_virtual_dep_telemetry
9173 .then(|| self.start_virtual_dep_telemetry());
9174
9175 loop {
9176 let to_evaluate = self.graph.get_evaluation_vertices();
9177 if to_evaluate.is_empty() {
9178 if let Some(t) = telemetry.as_mut()
9179 && t.bailout_reason.is_none()
9180 {
9181 t.bailout_reason = Some("no_work");
9182 }
9183 break;
9184 }
9185
9186 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9187 if let Some(t) = telemetry.as_mut() {
9188 Self::accumulate_schedule_meta(t, &meta);
9189 }
9190
9191 for &unit in &schedule.units {
9192 match unit {
9193 ScheduleUnit::Cycle(i) => {
9194 if self.handle_cycle_unit(
9195 schedule.unit_cycle(i),
9196 Some(delta),
9197 None,
9198 None,
9199 )? > 0
9200 {
9201 cycle_errors += 1;
9202 }
9203 }
9204 ScheduleUnit::Layer(i) => {
9205 let layer = schedule.unit_layer(i);
9206 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9207 computed_vertices +=
9208 self.evaluate_layer_parallel_with_delta(layer, delta)?;
9209 } else {
9210 computed_vertices +=
9211 self.evaluate_layer_sequential_with_delta(layer, delta)?;
9212 }
9213 }
9214 }
9215 }
9216
9217 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9218 if let Some(t) = telemetry.as_mut() {
9219 t.changed_vdeps_total += changed_vertices.len();
9220 }
9221 self.graph.clear_dirty_flags(&to_evaluate);
9222 for v in &changed_vertices {
9223 self.graph.set_dirty(*v, true);
9224 }
9225
9226 if changed_vertices.is_empty() {
9227 if let Some(t) = telemetry.as_mut() {
9228 t.bailout_reason = Some("converged");
9229 }
9230 break;
9231 }
9232 if replan_iterations >= MAX_REPLAN {
9233 if let Some(t) = telemetry.as_mut() {
9234 t.bailout_reason = Some("max_replan");
9235 }
9236 break;
9237 }
9238 replan_iterations += 1;
9239 }
9240
9241 if let Some(mut t) = telemetry {
9242 t.replan_iterations = replan_iterations;
9243 self.last_virtual_dep_telemetry = t;
9244 }
9245
9246 self.redirty_for_next_recalc();
9247 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9248
9249 Ok(EvalResult {
9250 computed_vertices,
9251 cycle_errors,
9252 elapsed: start.elapsed(),
9253 })
9254 }
9255
9256 pub fn evaluate_cell(
9266 &mut self,
9267 sheet: &str,
9268 row: u32,
9269 col: u32,
9270 ) -> Result<Option<LiteralValue>, ExcelError> {
9271 if row == 0 || col == 0 {
9272 return Err(ExcelError::new(ExcelErrorKind::Ref)
9273 .with_message("Row and column must be >= 1".to_string()));
9274 }
9275
9276 if self.config.defer_graph_building {
9284 self.build_graph_all()?;
9285 }
9286
9287 let result = self.evaluate_cells(&[(sheet, row, col)])?;
9288
9289 match result.len() {
9290 0 => Ok(None),
9291 1 => {
9292 let v = result.into_iter().next().unwrap();
9293 Ok(v)
9294 }
9295 _ => unreachable!("evaluate_cells returned unexpected length"),
9296 }
9297 }
9298
9299 pub fn evaluate_cells(
9306 &mut self,
9307 targets: &[(&str, u32, u32)],
9308 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9309 debug_assert!(
9310 !self.graph.deferred_dirty_active(),
9311 "deferred-dirty scope leaked into evaluate_cells: a begin_deferred_dirty \
9312 was not balanced by end_deferred_dirty"
9313 );
9314 self.validate_deterministic_mode()?;
9315 if targets.is_empty() {
9316 return Ok(Vec::new());
9317 }
9318 if self.config.defer_graph_building {
9322 self.build_graph_all()?;
9323 }
9324 if self.graph.formula_authority().active_span_count() > 0 {
9325 let _ = self.evaluate_authoritative_formula_plane_all()?;
9326 } else {
9327 self.evaluate_until(targets)?;
9328 }
9329 Ok(targets
9330 .iter()
9331 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9332 .collect())
9333 }
9334
9335 pub fn evaluate_cells_cancellable(
9336 &mut self,
9337 targets: &[(&str, u32, u32)],
9338 cancel_flag: Arc<AtomicBool>,
9339 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9340 self.active_cancel_flag = Some(cancel_flag.clone());
9341 let res = self.evaluate_cells_cancellable_impl(targets, &cancel_flag);
9342 self.active_cancel_flag = None;
9343 res
9344 }
9345
9346 fn evaluate_cells_cancellable_impl(
9347 &mut self,
9348 targets: &[(&str, u32, u32)],
9349 cancel_flag: &AtomicBool,
9350 ) -> Result<Vec<Option<LiteralValue>>, ExcelError> {
9351 self.validate_deterministic_mode()?;
9352 if targets.is_empty() {
9353 return Ok(Vec::new());
9354 }
9355 if self.config.defer_graph_building {
9359 self.build_graph_all()?;
9360 }
9361 if self.graph.formula_authority().active_span_count() > 0 {
9362 if cancel_flag.load(Ordering::Relaxed) {
9363 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9364 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9365 ));
9366 }
9367 let _ = self.evaluate_authoritative_formula_plane_all()?;
9368 return Ok(targets
9369 .iter()
9370 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9371 .collect());
9372 }
9373
9374 let a1_targets: Vec<String> = targets
9377 .iter()
9378 .map(|(s, r, c)| {
9379 format!("{}!{}", s, col_letters_from_1based(*c).unwrap()) + &r.to_string()
9380 })
9381 .collect();
9382 let a1_refs: Vec<&str> = a1_targets.iter().map(|s| s.as_str()).collect();
9383
9384 self.evaluate_until_cancellable_impl(&a1_refs, cancel_flag)?;
9385
9386 Ok(targets
9387 .iter()
9388 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9389 .collect())
9390 }
9391
9392 pub fn evaluate_cells_with_delta(
9393 &mut self,
9394 targets: &[(&str, u32, u32)],
9395 ) -> Result<(Vec<Option<LiteralValue>>, EvalDelta), ExcelError> {
9396 self.validate_deterministic_mode()?;
9397 if targets.is_empty() {
9398 return Ok((Vec::new(), EvalDelta::default()));
9399 }
9400 if self.config.defer_graph_building {
9401 let mut sheets: rustc_hash::FxHashSet<&str> = rustc_hash::FxHashSet::default();
9402 for (s, _, _) in targets.iter() {
9403 sheets.insert(*s);
9404 }
9405 self.build_graph_for_sheets(sheets.iter().cloned())?;
9406 }
9407 if self.graph.formula_authority().active_span_count() > 0 {
9408 let _ = self.evaluate_authoritative_formula_plane_all()?;
9409 let values = targets
9410 .iter()
9411 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9412 .collect();
9413 return Ok((values, EvalDelta::default()));
9414 }
9415 let mut collector = DeltaCollector::new(DeltaMode::Cells);
9416 self.evaluate_until_with_delta_collector(targets, &mut collector)?;
9417 let values = targets
9418 .iter()
9419 .map(|(s, r, c)| self.get_cell_value(s, *r, *c))
9420 .collect();
9421 Ok((values, collector.finish()))
9422 }
9423
9424 pub fn get_eval_plan(&self, targets: &[(&str, u32, u32)]) -> Result<EvalPlan, ExcelError> {
9426 if targets.is_empty() {
9427 return Ok(EvalPlan {
9428 total_vertices_to_evaluate: 0,
9429 layers: Vec::new(),
9430 cycles_detected: 0,
9431 dirty_count: 0,
9432 volatile_count: 0,
9433 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9434 estimated_parallel_layers: 0,
9435 target_cells: Vec::new(),
9436 });
9437 }
9438 if self.config.defer_graph_building && self.has_staged_formulas() {
9439 return Err(ExcelError::new(ExcelErrorKind::Value).with_message(
9440 "Evaluation plan requested with deferred graph; build first or call evaluate_*",
9441 ));
9442 }
9443
9444 let addresses: Vec<String> = targets
9446 .iter()
9447 .map(|(s, r, c)| format!("{}!{}{}", s, Self::col_to_letters(*c), r))
9448 .collect();
9449
9450 let mut target_addrs = Vec::new();
9452 for (sheet, row, col) in targets {
9453 if let Some(sheet_id) = self.graph.sheet_id(sheet) {
9454 let coord = Coord::from_excel(*row, *col, true, true);
9455 target_addrs.push(CellRef::new(sheet_id, coord));
9456 }
9457 }
9458
9459 let mut target_vertex_ids = Vec::new();
9461 for addr in &target_addrs {
9462 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
9463 target_vertex_ids.push(*vertex_id);
9464 }
9465 }
9466
9467 if target_vertex_ids.is_empty() {
9468 return Ok(EvalPlan {
9469 total_vertices_to_evaluate: 0,
9470 layers: Vec::new(),
9471 cycles_detected: 0,
9472 dirty_count: 0,
9473 volatile_count: 0,
9474 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9475 estimated_parallel_layers: 0,
9476 target_cells: addresses,
9477 });
9478 }
9479
9480 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
9482
9483 if precedents_to_eval.is_empty() {
9484 return Ok(EvalPlan {
9485 total_vertices_to_evaluate: 0,
9486 layers: Vec::new(),
9487 cycles_detected: 0,
9488 dirty_count: 0,
9489 volatile_count: 0,
9490 parallel_enabled: self.config.enable_parallel && self.thread_pool.is_some(),
9491 estimated_parallel_layers: 0,
9492 target_cells: addresses,
9493 });
9494 }
9495
9496 let mut dirty_count = 0;
9498 let mut volatile_count = 0;
9499 for &vertex_id in &precedents_to_eval {
9500 if self.graph.is_dirty(vertex_id) {
9501 dirty_count += 1;
9502 }
9503 if self.graph.is_volatile(vertex_id) {
9504 volatile_count += 1;
9505 }
9506 }
9507
9508 let scheduler = Scheduler::new(&self.graph);
9510 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
9511
9512 let mut layers = Vec::new();
9514 let mut estimated_parallel_layers = 0;
9515 let parallel_enabled = self.config.enable_parallel && self.thread_pool.is_some();
9516
9517 for layer in &schedule.layers {
9518 let parallel_eligible = parallel_enabled && layer.vertices.len() > 1;
9519 if parallel_eligible {
9520 estimated_parallel_layers += 1;
9521 }
9522
9523 let sample_cells: Vec<String> = layer
9525 .vertices
9526 .iter()
9527 .take(5)
9528 .filter_map(|&vertex_id| {
9529 self.graph
9530 .get_cell_ref_for_vertex(vertex_id)
9531 .map(|cell_ref| {
9532 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
9533 format!(
9534 "{}!{}{}",
9535 sheet_name,
9536 Self::col_to_letters(cell_ref.coord.col()),
9537 cell_ref.coord.row() + 1
9538 )
9539 })
9540 })
9541 .collect();
9542
9543 layers.push(LayerInfo {
9544 vertex_count: layer.vertices.len(),
9545 parallel_eligible,
9546 sample_cells,
9547 });
9548 }
9549
9550 Ok(EvalPlan {
9551 total_vertices_to_evaluate: precedents_to_eval.len(),
9552 layers,
9553 cycles_detected: schedule.cycles.len(),
9554 dirty_count,
9555 volatile_count,
9556 parallel_enabled,
9557 estimated_parallel_layers,
9558 target_cells: addresses,
9559 })
9560 }
9561 fn create_evaluation_schedule(
9563 &mut self,
9564 to_evaluate: &[VertexId],
9565 ) -> Result<ScheduleBuildOutput, ExcelError> {
9566 self.graph.flush_pending_edge_deltas();
9569 if self.can_use_static_schedule_cache(to_evaluate) {
9570 if let Some(cached) = self.cached_static_schedule.as_ref()
9571 && cached.topology_epoch == self.topology_epoch
9572 && cached.candidate_vertices.as_slice() == to_evaluate
9573 {
9574 let meta = ScheduleBuildMeta {
9575 candidate_vertices: to_evaluate.len(),
9576 vdeps_vertices: 0,
9577 vdeps_edges: 0,
9578 builder_elapsed_ms: 0,
9579 used_virtual_schedule: false,
9580 schedule_cache_hit: true,
9581 schedule_cache_eligible: true,
9582 };
9583 return Ok((cached.schedule.clone(), FxHashMap::default(), meta));
9584 }
9585
9586 let (schedule, vdeps, mut meta) =
9587 self.create_evaluation_schedule_uncached(to_evaluate)?;
9588 meta.schedule_cache_hit = false;
9589 meta.schedule_cache_eligible = true;
9590 if vdeps.is_empty() {
9591 self.cached_static_schedule = Some(CachedScheduleEntry {
9592 topology_epoch: self.topology_epoch,
9593 candidate_vertices: to_evaluate.to_vec(),
9594 schedule: schedule.clone(),
9595 });
9596 }
9597 return Ok((schedule, vdeps, meta));
9598 }
9599
9600 let (schedule, vdeps, mut meta) = self.create_evaluation_schedule_uncached(to_evaluate)?;
9601 meta.schedule_cache_hit = false;
9602 meta.schedule_cache_eligible = false;
9603 Ok((schedule, vdeps, meta))
9604 }
9605
9606 fn create_evaluation_schedule_uncached(
9607 &self,
9608 to_evaluate: &[VertexId],
9609 ) -> Result<ScheduleBuildOutput, ExcelError> {
9610 let builder = VirtualDepBuilder::new(self);
9611 let (vdeps, augmented, builder_elapsed_ms, vdeps_edges) =
9612 if self.config.enable_virtual_dep_telemetry {
9613 let build_started = crate::instant::FzInstant::now();
9614 let (vdeps, augmented) = builder.build(to_evaluate);
9615 let builder_elapsed_ms = build_started.elapsed().as_millis();
9616 let vdeps_edges = vdeps.values().map(|deps| deps.len()).sum::<usize>();
9617 (vdeps, augmented, builder_elapsed_ms, vdeps_edges)
9618 } else {
9619 let (vdeps, augmented) = builder.build(to_evaluate);
9620 (vdeps, augmented, 0, 0)
9621 };
9622
9623 let mut final_evaluate = to_evaluate.to_vec();
9624 if !augmented.is_empty() {
9625 final_evaluate.extend(augmented);
9626 final_evaluate.sort_unstable();
9627 final_evaluate.dedup();
9628 }
9629
9630 let use_virtual = !vdeps.is_empty();
9631
9632 let scheduler = Scheduler::new(&self.graph);
9633 let schedule = if use_virtual {
9634 scheduler.create_schedule_with_virtual(&final_evaluate, &vdeps)?
9635 } else {
9636 scheduler.create_schedule(&final_evaluate)?
9637 };
9638
9639 let meta = ScheduleBuildMeta {
9640 candidate_vertices: to_evaluate.len(),
9641 vdeps_vertices: vdeps.len(),
9642 vdeps_edges,
9643 builder_elapsed_ms,
9644 used_virtual_schedule: use_virtual,
9645 schedule_cache_hit: false,
9646 schedule_cache_eligible: false,
9647 };
9648
9649 Ok((schedule, vdeps, meta))
9650 }
9651
9652 fn can_use_static_schedule_cache(&self, to_evaluate: &[VertexId]) -> bool {
9653 !to_evaluate.is_empty()
9654 && to_evaluate.iter().copied().all(|v| {
9655 !self.graph.is_dynamic(v) && self.graph.get_range_dependencies(v).is_none()
9656 })
9657 }
9658
9659 fn start_virtual_dep_telemetry(&self) -> VirtualDepTelemetry {
9660 VirtualDepTelemetry {
9661 fallback_mode_activations: self.virtual_dep_fallback_activations,
9662 ..VirtualDepTelemetry::default()
9663 }
9664 }
9665
9666 fn accumulate_schedule_meta(telemetry: &mut VirtualDepTelemetry, meta: &ScheduleBuildMeta) {
9667 telemetry.candidate_vertices_total += meta.candidate_vertices;
9668 telemetry.vdeps_vertices_total += meta.vdeps_vertices;
9669 telemetry.vdeps_edges_total += meta.vdeps_edges;
9670 telemetry.builder_elapsed_ms_total += meta.builder_elapsed_ms;
9671 if meta.schedule_cache_eligible {
9672 if meta.schedule_cache_hit {
9673 telemetry.schedule_cache_hits += 1;
9674 telemetry.reused_schedule_vertices_total += meta.candidate_vertices;
9675 } else {
9676 telemetry.schedule_cache_misses += 1;
9677 }
9678 }
9679 if meta.used_virtual_schedule {
9680 telemetry.schedule_virtual_passes += 1;
9681 } else {
9682 telemetry.schedule_static_passes += 1;
9683 }
9684 }
9685
9686 fn changed_virtual_dep_vertices(
9687 &self,
9688 to_evaluate: &[VertexId],
9689 old_vdeps: &FxHashMap<VertexId, Vec<VertexId>>,
9690 ) -> Vec<VertexId> {
9691 if !to_evaluate
9692 .iter()
9693 .copied()
9694 .any(|v| self.graph.is_dynamic(v))
9695 {
9696 return Vec::new();
9697 }
9698
9699 let builder = VirtualDepBuilder::new(self);
9700 let (new_vdeps, _) = builder.build(to_evaluate);
9701
9702 let mut candidates = FxHashSet::default();
9703 candidates.extend(old_vdeps.keys().copied());
9704 candidates.extend(new_vdeps.keys().copied());
9705
9706 let mut changed = Vec::new();
9707 for v in candidates {
9708 if old_vdeps.get(&v) != new_vdeps.get(&v) {
9709 changed.push(v);
9710 }
9711 }
9712 changed
9713 }
9714
9715 fn build_demand_subgraph(
9718 &self,
9719 target_vertices: &[VertexId],
9720 ) -> (
9721 Vec<VertexId>,
9722 rustc_hash::FxHashMap<VertexId, Vec<VertexId>>,
9723 ) {
9724 #[cfg(feature = "tracing")]
9725 let _span =
9726 tracing::info_span!("demand_subgraph", targets = target_vertices.len()).entered();
9727 use rustc_hash::{FxHashMap, FxHashSet};
9728
9729 let mut to_evaluate: FxHashSet<VertexId> = FxHashSet::default();
9730 let mut visited: FxHashSet<VertexId> = FxHashSet::default();
9731 let mut stack: Vec<VertexId> = Vec::new();
9732 let mut vdeps: FxHashMap<VertexId, Vec<VertexId>> = FxHashMap::default(); for &t in target_vertices {
9735 stack.push(t);
9736 }
9737
9738 while let Some(v) = stack.pop() {
9739 if !visited.insert(v) {
9740 continue;
9741 }
9742 if !self.graph.vertex_exists(v) {
9743 continue;
9744 }
9745 match self.graph.get_vertex_kind(v) {
9753 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
9754 if self.graph.is_dirty(v) || self.graph.is_volatile(v) {
9755 to_evaluate.insert(v);
9756 }
9757 }
9758 VertexKind::NamedScalar
9759 | VertexKind::NamedArray
9760 | VertexKind::Range
9761 | VertexKind::InfiniteRange => {
9762 to_evaluate.insert(v);
9763 }
9764 _ => {}
9765 }
9766
9767 if let Some(dependencies) = self.graph.dependencies_slice(v) {
9778 for &dep in dependencies {
9779 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9780 stack.push(dep);
9781 }
9782 }
9783 } else {
9784 for dep in self.graph.get_dependencies(v) {
9785 if self.graph.vertex_exists(dep) && !visited.contains(&dep) {
9786 stack.push(dep);
9787 }
9788 }
9789 } let builder = VirtualDepBuilder::new(self);
9791 let (vdeps_map, _) = builder.build(&[v]);
9792 if let Some(deps) = vdeps_map.get(&v) {
9793 for &u in deps {
9794 vdeps.entry(v).or_default().push(u);
9795 if !visited.contains(&u) {
9796 stack.push(u);
9797 }
9798 }
9799 }
9800 }
9801
9802 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
9803 result.sort_unstable();
9804 for deps in vdeps.values_mut() {
9806 deps.sort_unstable();
9807 deps.dedup();
9808 }
9809 (result, vdeps)
9810 }
9811
9812 fn col_to_letters(col: u32) -> String {
9814 col_letters_from_1based(col).expect("column index must be >= 1")
9815 }
9816
9817 pub fn evaluate_all_cancellable(
9819 &mut self,
9820 cancel_flag: Arc<AtomicBool>,
9821 ) -> Result<EvalResult, ExcelError> {
9822 self.active_cancel_flag = Some(cancel_flag.clone());
9823 let res = self.evaluate_all_cancellable_impl(&cancel_flag);
9824 self.active_cancel_flag = None;
9825 res
9826 }
9827
9828 fn evaluate_all_cancellable_impl(
9829 &mut self,
9830 cancel_flag: &AtomicBool,
9831 ) -> Result<EvalResult, ExcelError> {
9832 self.begin_evaluation_request();
9833 let _source_cache = self.source_cache_session();
9834 self.validate_deterministic_mode()?;
9835 if self.config.defer_graph_building {
9836 self.build_graph_all()?;
9837 }
9838 if self.graph.formula_authority().active_span_count() > 0 {
9839 if cancel_flag.load(Ordering::Relaxed) {
9840 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9841 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
9842 ));
9843 }
9844 return self.evaluate_authoritative_formula_plane_all();
9845 }
9846 self.reset_virtual_dep_telemetry_if_disabled();
9847 let start = crate::instant::FzInstant::now();
9848 let mut computed_vertices = 0;
9849 let mut cycle_errors = 0;
9850
9851 let mut replan_iterations = 0;
9852 const MAX_REPLAN: usize = 5;
9853 let mut telemetry = self
9854 .config
9855 .enable_virtual_dep_telemetry
9856 .then(|| self.start_virtual_dep_telemetry());
9857
9858 loop {
9859 if cancel_flag.load(Ordering::Relaxed) {
9860 if let Some(mut t) = telemetry {
9861 t.bailout_reason = Some("cancelled");
9862 t.replan_iterations = replan_iterations;
9863 self.last_virtual_dep_telemetry = t;
9864 }
9865 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9866 .with_message("Evaluation cancelled before scheduling".to_string()));
9867 }
9868
9869 let to_evaluate = self.graph.get_evaluation_vertices();
9870 if to_evaluate.is_empty() {
9871 if let Some(t) = telemetry.as_mut()
9872 && t.bailout_reason.is_none()
9873 {
9874 t.bailout_reason = Some("no_work");
9875 }
9876 break;
9877 }
9878
9879 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
9880 if let Some(t) = telemetry.as_mut() {
9881 Self::accumulate_schedule_meta(t, &meta);
9882 }
9883
9884 for &unit in &schedule.units {
9887 match unit {
9888 ScheduleUnit::Cycle(i) => {
9889 if cancel_flag.load(Ordering::Relaxed) {
9891 if let Some(mut t) = telemetry {
9892 t.bailout_reason = Some("cancelled");
9893 t.replan_iterations = replan_iterations;
9894 self.last_virtual_dep_telemetry = t;
9895 }
9896 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9897 "Evaluation cancelled during cycle handling".to_string(),
9898 ));
9899 }
9900
9901 if self.handle_cycle_unit(
9902 schedule.unit_cycle(i),
9903 None,
9904 None,
9905 Some(cancel_flag),
9906 )? > 0
9907 {
9908 cycle_errors += 1;
9909 }
9910 }
9911 ScheduleUnit::Layer(i) => {
9912 let layer = schedule.unit_layer(i);
9913 if cancel_flag.load(Ordering::Relaxed) {
9915 if let Some(mut t) = telemetry {
9916 t.bailout_reason = Some("cancelled");
9917 t.replan_iterations = replan_iterations;
9918 self.last_virtual_dep_telemetry = t;
9919 }
9920 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
9921 .with_message("Evaluation cancelled between layers".to_string()));
9922 }
9923
9924 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
9926 computed_vertices +=
9927 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
9928 } else {
9929 computed_vertices +=
9930 self.evaluate_layer_sequential_cancellable(layer, cancel_flag)?;
9931 }
9932 }
9933 }
9934 }
9935
9936 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
9937 if let Some(t) = telemetry.as_mut() {
9938 t.changed_vdeps_total += changed_vertices.len();
9939 }
9940 self.graph.clear_dirty_flags(&to_evaluate);
9941 for v in &changed_vertices {
9942 self.graph.set_dirty(*v, true);
9943 }
9944
9945 if changed_vertices.is_empty() {
9946 if let Some(t) = telemetry.as_mut() {
9947 t.bailout_reason = Some("converged");
9948 }
9949 break;
9950 }
9951 if replan_iterations >= MAX_REPLAN {
9952 if let Some(t) = telemetry.as_mut() {
9953 t.bailout_reason = Some("max_replan");
9954 }
9955 break;
9956 }
9957 replan_iterations += 1;
9958 }
9959
9960 if let Some(mut t) = telemetry {
9961 t.replan_iterations = replan_iterations;
9962 self.last_virtual_dep_telemetry = t;
9963 }
9964
9965 self.redirty_for_next_recalc();
9967 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
9968
9969 Ok(EvalResult {
9970 computed_vertices,
9971 cycle_errors,
9972 elapsed: start.elapsed(),
9973 })
9974 }
9975
9976 pub fn evaluate_until_cancellable(
9978 &mut self,
9979 targets: &[&str],
9980 cancel_flag: Arc<AtomicBool>,
9981 ) -> Result<EvalResult, ExcelError> {
9982 self.active_cancel_flag = Some(cancel_flag.clone());
9983 let res = self.evaluate_until_cancellable_impl(targets, &cancel_flag);
9984 self.active_cancel_flag = None;
9985 res
9986 }
9987
9988 fn evaluate_until_cancellable_impl(
9989 &mut self,
9990 targets: &[&str],
9991 cancel_flag: &AtomicBool,
9992 ) -> Result<EvalResult, ExcelError> {
9993 let start = crate::instant::FzInstant::now();
9994 self.begin_evaluation_request();
9995 self.graph.flush_pending_edge_deltas();
9996 if self.graph.formula_authority().active_span_count() > 0 {
9997 if cancel_flag.load(Ordering::Relaxed) {
9998 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
9999 "Evaluation cancelled before FormulaPlane scheduling".to_string(),
10000 ));
10001 }
10002 return self.evaluate_authoritative_formula_plane_all();
10003 }
10004
10005 let mut target_addrs = Vec::new();
10007 for target in targets {
10008 let (sheet, row, col) = self.parse_a1_notation(target)?;
10009 let sheet_id = self.graph.sheet_id_mut(&sheet);
10010 let coord = Coord::from_excel(row, col, true, true);
10011 target_addrs.push(CellRef::new(sheet_id, coord));
10012 }
10013
10014 let mut target_vertex_ids = Vec::new();
10016 for addr in &target_addrs {
10017 if let Some(vertex_id) = self.graph.get_vertex_id_for_address(addr) {
10018 target_vertex_ids.push(*vertex_id);
10019 }
10020 }
10021
10022 if target_vertex_ids.is_empty() {
10023 return Ok(EvalResult {
10024 computed_vertices: 0,
10025 cycle_errors: 0,
10026 elapsed: start.elapsed(),
10027 });
10028 }
10029
10030 let (precedents_to_eval, vdeps) = self.build_demand_subgraph(&target_vertex_ids);
10032
10033 if precedents_to_eval.is_empty() {
10034 return Ok(EvalResult {
10035 computed_vertices: 0,
10036 cycle_errors: 0,
10037 elapsed: start.elapsed(),
10038 });
10039 }
10040
10041 let scheduler = Scheduler::new(&self.graph);
10043 let schedule = scheduler.create_schedule_with_virtual(&precedents_to_eval, &vdeps)?;
10044
10045 let mut cycle_errors = 0;
10048 let mut computed_vertices = 0;
10049 for &unit in &schedule.units {
10050 match unit {
10051 ScheduleUnit::Cycle(i) => {
10052 if cancel_flag.load(Ordering::Relaxed) {
10054 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
10055 "Demand-driven evaluation cancelled during cycle handling".to_string(),
10056 ));
10057 }
10058
10059 if self.handle_cycle_unit(
10060 schedule.unit_cycle(i),
10061 None,
10062 None,
10063 Some(cancel_flag),
10064 )? > 0
10065 {
10066 cycle_errors += 1;
10067 }
10068 }
10069 ScheduleUnit::Layer(i) => {
10070 let layer = schedule.unit_layer(i);
10071 if cancel_flag.load(Ordering::Relaxed) {
10073 return Err(ExcelError::new(ExcelErrorKind::Cancelled).with_message(
10074 "Demand-driven evaluation cancelled between layers".to_string(),
10075 ));
10076 }
10077
10078 if self.thread_pool.is_some() && layer.vertices.len() > 1 {
10080 computed_vertices +=
10081 self.evaluate_layer_parallel_cancellable(layer, cancel_flag)?;
10082 } else {
10083 computed_vertices += self
10084 .evaluate_layer_sequential_cancellable_demand_driven(
10085 layer,
10086 cancel_flag,
10087 )?;
10088 }
10089 }
10090 }
10091 }
10092
10093 self.graph.clear_dirty_flags(&precedents_to_eval);
10095
10096 self.redirty_for_next_recalc();
10098
10099 Ok(EvalResult {
10100 computed_vertices,
10101 cycle_errors,
10102 elapsed: start.elapsed(),
10103 })
10104 }
10105
10106 fn parse_a1_notation(&self, address: &str) -> Result<(String, u32, u32), ExcelError> {
10107 let mut parts = address.splitn(2, '!');
10108 let first = parts.next().unwrap_or_default();
10109 let remainder = parts.next();
10110
10111 let (sheet, cell_part) = match remainder {
10112 Some(cell) => (first.to_string(), cell),
10113 None => (self.default_sheet_name().to_string(), first),
10114 };
10115
10116 let (row, col, _, _) = parse_a1_1based(cell_part).map_err(|err| {
10117 ExcelError::new(ExcelErrorKind::Ref)
10118 .with_message(format!("Invalid cell reference `{cell_part}`: {err}"))
10119 })?;
10120
10121 Ok((sheet, row, col))
10122 }
10123
10124 fn is_ast_volatile_with_provider(&self, ast: &ASTNode) -> bool {
10126 use formualizer_parse::parser::ASTNodeType;
10127 match &ast.node_type {
10128 ASTNodeType::Function { name, args, .. } => {
10129 if let Some(func) = self
10130 .get_function("", name)
10131 .or_else(|| crate::function_registry::get("", name))
10132 && func.caps().contains(crate::function::FnCaps::VOLATILE)
10133 {
10134 return true;
10135 }
10136 args.iter()
10137 .any(|arg| self.is_ast_volatile_with_provider(arg))
10138 }
10139 ASTNodeType::BinaryOp { left, right, .. } => {
10140 self.is_ast_volatile_with_provider(left)
10141 || self.is_ast_volatile_with_provider(right)
10142 }
10143 ASTNodeType::UnaryOp { expr, .. } => self.is_ast_volatile_with_provider(expr),
10144 ASTNodeType::Array(rows) => rows.iter().any(|row| {
10145 row.iter()
10146 .any(|cell| self.is_ast_volatile_with_provider(cell))
10147 }),
10148 _ => false,
10149 }
10150 }
10151
10152 fn find_dirty_precedents(&self, target_vertices: &[VertexId]) -> Vec<VertexId> {
10154 let mut to_evaluate = FxHashSet::default();
10155 let mut visited = FxHashSet::default();
10156 let mut stack = Vec::new();
10157
10158 for &target in target_vertices {
10160 stack.push(target);
10161 }
10162
10163 while let Some(vertex_id) = stack.pop() {
10164 if !visited.insert(vertex_id) {
10165 continue; }
10167
10168 if self.graph.vertex_exists(vertex_id) {
10169 let kind = self.graph.get_vertex_kind(vertex_id);
10171 let needs_eval = match kind {
10172 super::vertex::VertexKind::FormulaScalar
10173 | super::vertex::VertexKind::FormulaArray => {
10174 self.graph.is_dirty(vertex_id) || self.graph.is_volatile(vertex_id)
10175 }
10176 _ => false, };
10178
10179 if needs_eval {
10180 to_evaluate.insert(vertex_id);
10181 }
10182
10183 if let Some(dependencies) = self.graph.dependencies_slice(vertex_id) {
10185 for &dep_id in dependencies {
10186 if !visited.contains(&dep_id) {
10187 stack.push(dep_id);
10188 }
10189 }
10190 } else {
10191 let dependencies = self.graph.get_dependencies(vertex_id);
10192 for dep_id in dependencies {
10193 if !visited.contains(&dep_id) {
10194 stack.push(dep_id);
10195 }
10196 }
10197 }
10198 }
10199 }
10200
10201 let mut result: Vec<VertexId> = to_evaluate.into_iter().collect();
10202 result.sort_unstable();
10203 result
10204 }
10205
10206 fn evaluate_layer_sequential(
10208 &mut self,
10209 layer: &super::scheduler::Layer,
10210 ) -> Result<usize, ExcelError> {
10211 self.evaluate_layer_sequential_effects(layer)
10212 }
10213
10214 fn update_vertex_value_with_delta(
10215 &mut self,
10216 vertex_id: VertexId,
10217 new_value: LiteralValue,
10218 delta: &mut DeltaCollector,
10219 ) {
10220 if delta.mode != DeltaMode::Off
10221 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
10222 {
10223 let sheet_name = self.graph.sheet_name(cell.sheet_id);
10224 let old = self
10225 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
10226 .unwrap_or(LiteralValue::Empty);
10227 if old != new_value {
10228 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
10229 }
10230 }
10231 self.graph.update_vertex_value(vertex_id, new_value.clone());
10232 self.mirror_vertex_value_to_overlay(vertex_id, &new_value);
10233 }
10234
10235 fn evaluate_layer_sequential_with_delta(
10236 &mut self,
10237 layer: &super::scheduler::Layer,
10238 delta: &mut DeltaCollector,
10239 ) -> Result<usize, ExcelError> {
10240 self.evaluate_layer_sequential_with_delta_effects(layer, delta)
10241 }
10242
10243 fn evaluate_layer_sequential_cancellable(
10245 &mut self,
10246 layer: &super::scheduler::Layer,
10247 cancel_flag: &AtomicBool,
10248 ) -> Result<usize, ExcelError> {
10249 self.evaluate_layer_sequential_cancellable_effects(layer, cancel_flag)
10250 }
10251
10252 fn evaluate_layer_sequential_cancellable_demand_driven(
10254 &mut self,
10255 layer: &super::scheduler::Layer,
10256 cancel_flag: &AtomicBool,
10257 ) -> Result<usize, ExcelError> {
10258 self.evaluate_layer_sequential_cancellable_demand_driven_effects(layer, cancel_flag)
10259 }
10260
10261 fn evaluate_layer_parallel(
10263 &mut self,
10264 layer: &super::scheduler::Layer,
10265 ) -> Result<usize, ExcelError> {
10266 self.evaluate_layer_parallel_effects(layer)
10267 }
10268
10269 fn evaluate_layer_parallel_with_delta(
10270 &mut self,
10271 layer: &super::scheduler::Layer,
10272 delta: &mut DeltaCollector,
10273 ) -> Result<usize, ExcelError> {
10274 self.evaluate_layer_parallel_with_delta_effects(layer, delta)
10275 }
10276
10277 fn evaluate_layer_parallel_cancellable(
10279 &mut self,
10280 layer: &super::scheduler::Layer,
10281 cancel_flag: &AtomicBool,
10282 ) -> Result<usize, ExcelError> {
10283 self.evaluate_layer_parallel_cancellable_effects(layer, cancel_flag)
10284 }
10285
10286 fn apply_parallel_vertex_result(
10291 &mut self,
10292 vertex_id: VertexId,
10293 result: LiteralValue,
10294 mut delta: Option<&mut DeltaCollector>,
10295 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10296 ) -> Result<(), ExcelError> {
10297 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
10300 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
10301 && owner != vertex_id
10302 {
10303 return Ok(());
10304 }
10305
10306 let kind = self.graph.get_vertex_kind(vertex_id);
10307
10308 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
10310 if is_formula {
10311 match result {
10312 LiteralValue::Array(rows) => {
10313 self.apply_array_result_from_parallel(
10314 vertex_id,
10315 rows,
10316 delta.as_deref_mut(),
10317 overwritable_formulas,
10318 )?;
10319 }
10320 other => {
10321 self.apply_non_array_result_from_parallel(
10322 vertex_id,
10323 other,
10324 delta.as_deref_mut(),
10325 );
10326 }
10327 }
10328 return Ok(());
10329 }
10330
10331 if let Some(d) = delta {
10333 self.update_vertex_value_with_delta(vertex_id, result, d);
10334 } else {
10335 self.graph.update_vertex_value(vertex_id, result.clone());
10336 self.mirror_vertex_value_to_overlay(vertex_id, &result);
10337 }
10338 Ok(())
10339 }
10340
10341 fn apply_non_array_result_from_parallel(
10342 &mut self,
10343 vertex_id: VertexId,
10344 value: LiteralValue,
10345 delta: Option<&mut DeltaCollector>,
10346 ) {
10347 let spill_cells = self
10350 .graph
10351 .spill_cells_for_anchor(vertex_id)
10352 .map(|cells| cells.to_vec())
10353 .unwrap_or_default();
10354
10355 if let Some(d) = delta
10356 && d.mode != DeltaMode::Off
10357 && let Some(anchor) = self.graph.get_cell_ref_for_vertex(vertex_id)
10358 {
10359 if spill_cells.is_empty() {
10360 let old = self
10361 .read_cell_value(
10362 self.graph.sheet_name(anchor.sheet_id),
10363 anchor.coord.row() + 1,
10364 anchor.coord.col() + 1,
10365 )
10366 .unwrap_or(LiteralValue::Empty);
10367 if old != value {
10368 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10369 }
10370 } else {
10371 for cell in spill_cells.iter() {
10372 let sheet_name = self.graph.sheet_name(cell.sheet_id);
10373 let old = self
10374 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
10375 .unwrap_or(LiteralValue::Empty);
10376 let new = if cell.sheet_id == anchor.sheet_id
10377 && cell.coord.row() == anchor.coord.row()
10378 && cell.coord.col() == anchor.coord.col()
10379 {
10380 value.clone()
10381 } else {
10382 LiteralValue::Empty
10383 };
10384 Self::record_cell_if_changed(d, cell, &old, &new);
10385 }
10386 }
10387 }
10388
10389 self.graph.clear_spill_region(vertex_id);
10390 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
10391 self.record_formula_plane_structural_change(scope);
10392 }
10393
10394 if self.config.arrow_storage_enabled
10395 && self.config.delta_overlay_enabled
10396 && self.config.write_formula_overlay_enabled
10397 {
10398 let empty = LiteralValue::Empty;
10399 for cell in spill_cells.iter() {
10400 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
10401 self.mirror_value_to_computed_overlay(
10402 &sheet_name,
10403 cell.coord.row() + 1,
10404 cell.coord.col() + 1,
10405 &empty,
10406 );
10407 }
10408 }
10409
10410 self.graph.update_vertex_value(vertex_id, value.clone());
10411 self.mirror_vertex_value_to_overlay(vertex_id, &value);
10412 }
10413
10414 fn apply_array_result_from_parallel(
10415 &mut self,
10416 vertex_id: VertexId,
10417 rows: Vec<Vec<LiteralValue>>,
10418 mut delta: Option<&mut DeltaCollector>,
10419 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10420 ) -> Result<(), ExcelError> {
10421 self.graph
10423 .set_kind(vertex_id, crate::engine::vertex::VertexKind::FormulaArray);
10424
10425 let anchor = self
10426 .graph
10427 .get_cell_ref(vertex_id)
10428 .expect("cell ref for vertex");
10429 let sheet_id = anchor.sheet_id;
10430 let h = rows.len() as u32;
10431 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
10432
10433 let spill_cells = (h as u64).saturating_mul(w as u64);
10435 if spill_cells > self.config.spill.max_spill_cells as u64 {
10436 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10437 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10438 .with_message("SpillTooLarge")
10439 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10440 expected_rows: h,
10441 expected_cols: w,
10442 });
10443 let spill_val = LiteralValue::Error(spill_err.clone());
10444 if let Some(d) = delta.as_deref_mut()
10445 && d.mode != DeltaMode::Off
10446 {
10447 let old = self
10448 .read_cell_value(
10449 self.graph.sheet_name(anchor.sheet_id),
10450 anchor.coord.row() + 1,
10451 anchor.coord.col() + 1,
10452 )
10453 .unwrap_or(LiteralValue::Empty);
10454 if old != spill_val {
10455 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10456 }
10457 }
10458 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10459 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10460 return Ok(());
10461 }
10462
10463 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);
10467 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
10468 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
10469 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10470 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10471 .with_message("Spill exceeds sheet bounds")
10472 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10473 expected_rows: h,
10474 expected_cols: w,
10475 });
10476 let spill_val = LiteralValue::Error(spill_err.clone());
10477 if let Some(d) = delta.as_deref_mut()
10478 && d.mode != DeltaMode::Off
10479 {
10480 let old = self
10481 .read_cell_value(
10482 self.graph.sheet_name(anchor.sheet_id),
10483 anchor.coord.row() + 1,
10484 anchor.coord.col() + 1,
10485 )
10486 .unwrap_or(LiteralValue::Empty);
10487 if old != spill_val {
10488 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10489 }
10490 }
10491 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10492 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10493 return Ok(());
10494 }
10495
10496 let mut targets = Vec::new();
10497 for r in 0..h {
10498 for c in 0..w {
10499 targets.push(self.graph.make_cell_ref_internal(
10500 sheet_id,
10501 anchor.coord.row() + r,
10502 anchor.coord.col() + c,
10503 ));
10504 }
10505 }
10506
10507 match self.spill_mgr.reserve(
10508 vertex_id,
10509 anchor,
10510 SpillShape { rows: h, cols: w },
10511 SpillMeta {
10512 epoch: self.recalc_epoch,
10513 config: self.config.spill,
10514 },
10515 ) {
10516 Ok(()) => {
10517 if let Err(e) = self.commit_spill_and_mirror(
10518 vertex_id,
10519 &targets,
10520 rows.clone(),
10521 delta.as_deref_mut(),
10522 overwritable_formulas,
10523 ) {
10524 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10525 let err_val = LiteralValue::Error(e.clone());
10526 if let Some(d) = delta.as_deref_mut()
10527 && d.mode != DeltaMode::Off
10528 {
10529 let old = self
10530 .read_cell_value(
10531 self.graph.sheet_name(anchor.sheet_id),
10532 anchor.coord.row() + 1,
10533 anchor.coord.col() + 1,
10534 )
10535 .unwrap_or(LiteralValue::Empty);
10536 if old != err_val {
10537 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10538 }
10539 }
10540 self.graph.update_vertex_value(vertex_id, err_val.clone());
10541 self.mirror_vertex_value_to_overlay(vertex_id, &err_val);
10542 return Ok(());
10543 }
10544
10545 let top_left = rows
10547 .first()
10548 .and_then(|r| r.first())
10549 .cloned()
10550 .unwrap_or(LiteralValue::Empty);
10551 self.graph.update_vertex_value(vertex_id, top_left.clone());
10552 self.mirror_vertex_value_to_overlay(vertex_id, &top_left);
10553 Ok(())
10554 }
10555 Err(e) => {
10556 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
10557 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
10558 .with_message(e.message.unwrap_or_else(|| "Spill blocked".to_string()))
10559 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
10560 expected_rows: h,
10561 expected_cols: w,
10562 });
10563 let spill_val = LiteralValue::Error(spill_err.clone());
10564 if let Some(d) = delta
10565 && d.mode != DeltaMode::Off
10566 {
10567 let old = self
10568 .read_cell_value(
10569 self.graph.sheet_name(anchor.sheet_id),
10570 anchor.coord.row() + 1,
10571 anchor.coord.col() + 1,
10572 )
10573 .unwrap_or(LiteralValue::Empty);
10574 if old != spill_val {
10575 d.record_cell(anchor.sheet_id, anchor.coord.row(), anchor.coord.col());
10576 }
10577 }
10578 self.graph.update_vertex_value(vertex_id, spill_val.clone());
10579 self.mirror_vertex_value_to_overlay(vertex_id, &spill_val);
10580 Ok(())
10581 }
10582 }
10583 }
10584
10585 fn evaluate_vertex_immutable(&self, vertex_id: VertexId) -> Result<LiteralValue, ExcelError> {
10587 if !self.graph.vertex_exists(vertex_id) {
10589 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
10590 .with_message(format!("Vertex not found: {vertex_id:?}")));
10591 }
10592
10593 let kind = self.graph.get_vertex_kind(vertex_id);
10595 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
10596
10597 let ast_id = match kind {
10598 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
10599 if let Some(ast_id) = self.graph.get_formula_id(vertex_id) {
10600 ast_id
10601 } else {
10602 return Ok(LiteralValue::Number(0.0));
10603 }
10604 }
10605 VertexKind::Empty | VertexKind::Cell => {
10606 if let Some(cell_ref) = self.graph.get_cell_ref(vertex_id) {
10607 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10608 let row = cell_ref.coord.row() + 1;
10609 let col = cell_ref.coord.col() + 1;
10610 if let Some(v) = self.read_cell_value(sheet_name, row, col) {
10611 return Ok(v);
10612 }
10613 }
10614 return Ok(LiteralValue::Number(0.0));
10615 }
10616 VertexKind::NamedScalar => {
10617 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10618 ExcelError::new(ExcelErrorKind::Name)
10619 .with_message("Named range metadata missing".to_string())
10620 })?;
10621
10622 return match &named_range.definition {
10623 NamedDefinition::Cell(cell_ref) => {
10624 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10625 Ok(self
10626 .get_cell_value(
10627 sheet_name,
10628 cell_ref.coord.row() + 1,
10629 cell_ref.coord.col() + 1,
10630 )
10631 .unwrap_or(LiteralValue::Empty))
10632 }
10633 NamedDefinition::Literal(v) => Ok(v.clone()),
10634 NamedDefinition::Formula { ast, .. } => {
10635 let context_sheet = match named_range.scope {
10636 NameScope::Sheet(id) => id,
10637 NameScope::Workbook => sheet_id,
10638 };
10639 let sheet_name = self.graph.sheet_name(context_sheet);
10640 let cell_ref = self
10641 .graph
10642 .get_cell_ref(vertex_id)
10643 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10644 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10645 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
10646 }
10647 NamedDefinition::Range(_) => Err(ExcelError::new(ExcelErrorKind::Value)
10648 .with_message("Range-valued name evaluated as scalar".to_string())),
10649 };
10650 }
10651 VertexKind::NamedArray => {
10652 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
10653 ExcelError::new(ExcelErrorKind::Name)
10654 .with_message("Named range metadata missing".to_string())
10655 })?;
10656
10657 return match &named_range.definition {
10658 NamedDefinition::Range(range_ref) => {
10659 if range_ref.start.sheet_id != range_ref.end.sheet_id {
10660 return Err(ExcelError::new(ExcelErrorKind::Ref)
10661 .with_message("Named range cannot span sheets".to_string()));
10662 }
10663 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
10664 let sr0 = range_ref.start.coord.row();
10665 let sc0 = range_ref.start.coord.col();
10666 let er0 = range_ref.end.coord.row();
10667 let ec0 = range_ref.end.coord.col();
10668 if sr0 > er0 || sc0 > ec0 {
10669 return Err(ExcelError::new(ExcelErrorKind::Ref)
10670 .with_message("Invalid named range bounds".to_string()));
10671 }
10672
10673 let h = (er0 - sr0 + 1) as usize;
10674 let w = (ec0 - sc0 + 1) as usize;
10675 let cell_count = (h as u64).saturating_mul(w as u64);
10676 if cell_count > self.config.spill.max_spill_cells as u64 {
10677 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
10678 "Named range too large to materialize as an array".to_string(),
10679 ));
10680 }
10681
10682 let mut rows = Vec::with_capacity(h);
10683 for r0 in sr0..=er0 {
10684 let mut row = Vec::with_capacity(w);
10685 for c0 in sc0..=ec0 {
10686 let v = self
10687 .get_cell_value(sheet_name, r0 + 1, c0 + 1)
10688 .unwrap_or(LiteralValue::Empty);
10689 row.push(v);
10690 }
10691 rows.push(row);
10692 }
10693 Ok(LiteralValue::Array(rows))
10694 }
10695 NamedDefinition::Cell(cell_ref) => {
10696 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
10697 let row = cell_ref.coord.row() + 1;
10698 let col = cell_ref.coord.col() + 1;
10699 let v = self
10700 .get_cell_value(sheet_name, row, col)
10701 .unwrap_or(LiteralValue::Empty);
10702 Ok(LiteralValue::Array(vec![vec![v]]))
10703 }
10704 NamedDefinition::Literal(v) => Ok(LiteralValue::Array(vec![vec![v.clone()]])),
10705 NamedDefinition::Formula { ast, .. } => {
10706 let context_sheet = match named_range.scope {
10707 NameScope::Sheet(id) => id,
10708 NameScope::Workbook => sheet_id,
10709 };
10710 let sheet_name = self.graph.sheet_name(context_sheet);
10711 let cell_ref = self
10712 .graph
10713 .get_cell_ref(vertex_id)
10714 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
10715 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10716 match interpreter.evaluate_ast(ast) {
10717 Ok(cv) => {
10718 let v = cv.into_literal();
10719 match v {
10720 LiteralValue::Array(_) => Ok(v),
10721 other => Ok(LiteralValue::Array(vec![vec![other]])),
10722 }
10723 }
10724 Err(err) => Ok(LiteralValue::Error(err)),
10725 }
10726 }
10727 };
10728 }
10729 VertexKind::InfiniteRange
10730 | VertexKind::Range
10731 | VertexKind::External
10732 | VertexKind::Table => {
10733 return Ok(LiteralValue::Number(0.0));
10735 }
10736 };
10737
10738 let sheet_name = self.graph.sheet_name(sheet_id);
10740 let cell_ref = self
10741 .graph
10742 .get_cell_ref(vertex_id)
10743 .expect("cell ref for vertex");
10744 let interpreter = Interpreter::new_with_cell(self, sheet_name, cell_ref);
10745
10746 interpreter
10747 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
10748 .map(|cv| cv.into_literal())
10749 }
10750
10751 pub fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
10753 self.thread_pool.as_ref()
10754 }
10755}
10756
10757#[derive(Default)]
10758struct RowBoundsCache {
10759 snapshot: u64,
10760 map: rustc_hash::FxHashMap<(u32, usize), (Option<u32>, Option<u32>)>,
10762}
10763
10764impl RowBoundsCache {
10765 fn new(snapshot: u64) -> Self {
10766 Self {
10767 snapshot,
10768 map: Default::default(),
10769 }
10770 }
10771 fn get_row_bounds(
10772 &self,
10773 sheet_id: SheetId,
10774 col_idx: usize,
10775 snapshot: u64,
10776 ) -> Option<(Option<u32>, Option<u32>)> {
10777 if self.snapshot != snapshot {
10778 return None;
10779 }
10780 self.map.get(&(sheet_id as u32, col_idx)).copied()
10781 }
10782 fn put_row_bounds(
10783 &mut self,
10784 sheet_id: SheetId,
10785 col_idx: usize,
10786 snapshot: u64,
10787 bounds: (Option<u32>, Option<u32>),
10788 ) {
10789 if self.snapshot != snapshot {
10790 self.snapshot = snapshot;
10791 self.map.clear();
10792 }
10793 self.map.insert((sheet_id as u32, col_idx), bounds);
10794 }
10795}
10796
10797struct UsedAxisBoundsCache {
10798 snapshot: u64,
10799 row_bounds_by_col_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10800 col_bounds_by_row_span: rustc_hash::FxHashMap<(SheetId, u32, u32), Option<(u32, u32)>>,
10801 #[cfg(test)]
10802 row_hits: std::sync::atomic::AtomicUsize,
10803 #[cfg(test)]
10804 row_misses: std::sync::atomic::AtomicUsize,
10805 #[cfg(test)]
10806 col_hits: std::sync::atomic::AtomicUsize,
10807 #[cfg(test)]
10808 col_misses: std::sync::atomic::AtomicUsize,
10809}
10810
10811impl UsedAxisBoundsCache {
10812 fn new(snapshot: u64) -> Self {
10813 Self {
10814 snapshot,
10815 row_bounds_by_col_span: Default::default(),
10816 col_bounds_by_row_span: Default::default(),
10817 #[cfg(test)]
10818 row_hits: std::sync::atomic::AtomicUsize::new(0),
10819 #[cfg(test)]
10820 row_misses: std::sync::atomic::AtomicUsize::new(0),
10821 #[cfg(test)]
10822 col_hits: std::sync::atomic::AtomicUsize::new(0),
10823 #[cfg(test)]
10824 col_misses: std::sync::atomic::AtomicUsize::new(0),
10825 }
10826 }
10827
10828 fn reset_for_snapshot(&mut self, snapshot: u64) {
10829 if self.snapshot != snapshot {
10830 self.snapshot = snapshot;
10831 self.row_bounds_by_col_span.clear();
10832 self.col_bounds_by_row_span.clear();
10833 }
10834 }
10835
10836 fn get_row_bounds(
10837 &self,
10838 sheet_id: SheetId,
10839 start_col: u32,
10840 end_col: u32,
10841 snapshot: u64,
10842 ) -> Option<Option<(u32, u32)>> {
10843 if self.snapshot != snapshot {
10844 return None;
10845 }
10846 let cached = self
10847 .row_bounds_by_col_span
10848 .get(&(sheet_id, start_col, end_col))
10849 .copied();
10850 #[cfg(test)]
10851 if cached.is_some() {
10852 self.row_hits.fetch_add(1, Ordering::Relaxed);
10853 }
10854 cached
10855 }
10856
10857 fn put_row_bounds(
10858 &mut self,
10859 sheet_id: SheetId,
10860 start_col: u32,
10861 end_col: u32,
10862 snapshot: u64,
10863 bounds: Option<(u32, u32)>,
10864 ) {
10865 self.reset_for_snapshot(snapshot);
10866 self.row_bounds_by_col_span
10867 .insert((sheet_id, start_col, end_col), bounds);
10868 #[cfg(test)]
10869 self.row_misses.fetch_add(1, Ordering::Relaxed);
10870 }
10871
10872 fn get_col_bounds(
10873 &self,
10874 sheet_id: SheetId,
10875 start_row: u32,
10876 end_row: u32,
10877 snapshot: u64,
10878 ) -> Option<Option<(u32, u32)>> {
10879 if self.snapshot != snapshot {
10880 return None;
10881 }
10882 let cached = self
10883 .col_bounds_by_row_span
10884 .get(&(sheet_id, start_row, end_row))
10885 .copied();
10886 #[cfg(test)]
10887 if cached.is_some() {
10888 self.col_hits.fetch_add(1, Ordering::Relaxed);
10889 }
10890 cached
10891 }
10892
10893 fn put_col_bounds(
10894 &mut self,
10895 sheet_id: SheetId,
10896 start_row: u32,
10897 end_row: u32,
10898 snapshot: u64,
10899 bounds: Option<(u32, u32)>,
10900 ) {
10901 self.reset_for_snapshot(snapshot);
10902 self.col_bounds_by_row_span
10903 .insert((sheet_id, start_row, end_row), bounds);
10904 #[cfg(test)]
10905 self.col_misses.fetch_add(1, Ordering::Relaxed);
10906 }
10907}
10908
10909#[derive(Default)]
10911pub struct ShimSpillManager {
10912 region_locks: RegionLockManager,
10913 pub(crate) active_locks: rustc_hash::FxHashMap<VertexId, u64>,
10914}
10915
10916impl ShimSpillManager {
10917 pub(crate) fn reserve(
10918 &mut self,
10919 owner: VertexId,
10920 anchor_cell: CellRef,
10921 shape: SpillShape,
10922 _meta: SpillMeta,
10923 ) -> Result<(), ExcelError> {
10924 let region = crate::engine::spill::Region {
10926 sheet_id: anchor_cell.sheet_id as u32,
10927 row_start: anchor_cell.coord.row(),
10928 row_end: anchor_cell
10929 .coord
10930 .row()
10931 .saturating_add(shape.rows)
10932 .saturating_sub(1),
10933 col_start: anchor_cell.coord.col(),
10934 col_end: anchor_cell
10935 .coord
10936 .col()
10937 .saturating_add(shape.cols)
10938 .saturating_sub(1),
10939 };
10940 match self.region_locks.reserve(region, owner) {
10941 Ok(id) => {
10942 if id != 0 {
10943 self.active_locks.insert(owner, id);
10944 }
10945 Ok(())
10946 }
10947 Err(e) => Err(e),
10948 }
10949 }
10950
10951 pub(crate) fn release_owner(&mut self, owner: VertexId) {
10957 if let Some(id) = self.active_locks.remove(&owner) {
10958 self.region_locks.release(id);
10959 }
10960 }
10961
10962 pub(crate) fn commit_array_with_value_probe<F>(
10963 &mut self,
10964 graph: &mut DependencyGraph,
10965 anchor_vertex: VertexId,
10966 targets: &[CellRef],
10967 rows: Vec<Vec<LiteralValue>>,
10968 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
10969 mut value_probe: F,
10970 ) -> Result<(), ExcelError>
10971 where
10972 F: FnMut(&DependencyGraph, &CellRef) -> Option<LiteralValue>,
10973 {
10974 use formualizer_common::{ExcelErrorExtra, ExcelErrorKind};
10975
10976 let plan_res = graph.plan_spill_region_allowing_formula_overwrite(
10980 anchor_vertex,
10981 targets,
10982 overwritable_formulas,
10983 );
10984 if let Err(e) = plan_res {
10985 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
10986 self.region_locks.release(id);
10987 }
10988 return Err(e);
10989 }
10990
10991 if !graph.value_cache_enabled() {
10992 let (expected_rows, expected_cols) = if targets.is_empty() {
10994 (0u32, 0u32)
10995 } else {
10996 let mut min_r = u32::MAX;
10997 let mut max_r = 0u32;
10998 let mut min_c = u32::MAX;
10999 let mut max_c = 0u32;
11000 for cell in targets {
11001 let r = cell.coord.row();
11002 let c = cell.coord.col();
11003 min_r = min_r.min(r);
11004 max_r = max_r.max(r);
11005 min_c = min_c.min(c);
11006 max_c = max_c.max(c);
11007 }
11008 (
11009 max_r.saturating_sub(min_r).saturating_add(1),
11010 max_c.saturating_sub(min_c).saturating_add(1),
11011 )
11012 };
11013
11014 let anchor_cell = graph
11015 .get_cell_ref(anchor_vertex)
11016 .expect("anchor cell ref for spill commit");
11017
11018 for cell in targets {
11019 if *cell == anchor_cell {
11021 continue;
11022 }
11023 if graph.spill_registry_anchor_for_cell(*cell).is_some() {
11025 continue;
11026 }
11027 if let Some(&vid) = graph.get_vertex_id_for_address(cell)
11029 && vid != anchor_vertex
11030 {
11031 match graph.get_vertex_kind(vid) {
11032 crate::engine::vertex::VertexKind::FormulaScalar
11033 | crate::engine::vertex::VertexKind::FormulaArray => {
11034 continue;
11036 }
11037 _ => {}
11038 }
11039 }
11040
11041 if let Some(v) = value_probe(graph, cell)
11042 && !matches!(v, LiteralValue::Empty)
11043 {
11044 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11045 self.region_locks.release(id);
11046 }
11047 return Err(ExcelError::new(ExcelErrorKind::Spill)
11048 .with_message("BlockedByValue")
11049 .with_extra(ExcelErrorExtra::Spill {
11050 expected_rows,
11051 expected_cols,
11052 }));
11053 }
11054 }
11055 }
11056
11057 let commit_res = graph.commit_spill_region_atomic_with_fault(
11058 anchor_vertex,
11059 targets.to_vec(),
11060 rows,
11061 None,
11062 );
11063 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11064 self.region_locks.release(id);
11065 }
11066 commit_res.map(|_| ())
11067 }
11068
11069 pub(crate) fn commit_array_with_overlay<R: EvaluationContext>(
11071 &mut self,
11072 engine: &mut Engine<R>,
11073 anchor_vertex: VertexId,
11074 targets: &[CellRef],
11075 rows: Vec<Vec<LiteralValue>>,
11076 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
11077 ) -> Result<(), ExcelError> {
11078 let plan_res = engine.graph.plan_spill_region_allowing_formula_overwrite(
11080 anchor_vertex,
11081 targets,
11082 overwritable_formulas,
11083 );
11084 if let Err(e) = plan_res {
11085 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11086 self.region_locks.release(id);
11087 }
11088 return Err(e);
11089 }
11090
11091 let commit_res = engine.graph.commit_spill_region_atomic_with_fault(
11092 anchor_vertex,
11093 targets.to_vec(),
11094 rows.clone(),
11095 None,
11096 );
11097 if let Some(id) = self.active_locks.remove(&anchor_vertex) {
11098 self.region_locks.release(id);
11099 }
11100 commit_res.map(|_| ())?;
11101
11102 if engine.config.arrow_storage_enabled
11104 && engine.config.delta_overlay_enabled
11105 && engine.config.write_formula_overlay_enabled
11106 {
11107 for (idx, cell) in targets.iter().enumerate() {
11109 let (r_off, c_off) = {
11110 if rows.is_empty() || rows[0].is_empty() {
11111 (0usize, 0usize)
11112 } else {
11113 let width = rows[0].len();
11114 (idx / width, idx % width)
11115 }
11116 };
11117 let v = rows
11118 .get(r_off)
11119 .and_then(|r| r.get(c_off))
11120 .cloned()
11121 .unwrap_or(LiteralValue::Empty);
11122 let sheet_name = engine.graph.sheet_name(cell.sheet_id).to_string();
11123 engine.mirror_value_to_computed_overlay(
11124 &sheet_name,
11125 cell.coord.row() + 1,
11126 cell.coord.col() + 1,
11127 &v,
11128 );
11129 }
11130 }
11131 Ok(())
11132 }
11133}
11134
11135impl<R> Engine<R>
11136where
11137 R: EvaluationContext,
11138{
11139 fn resolve_shared_ref(
11140 &self,
11141 reference: &ReferenceType,
11142 current_sheet: &str,
11143 ) -> Result<formualizer_common::SheetRef<'static>, ExcelError> {
11144 use formualizer_common::{
11145 SheetCellRef as SharedCellRef, SheetLocator, SheetRangeRef as SharedRangeRef,
11146 SheetRef as SharedRef,
11147 };
11148
11149 let sr = match reference {
11151 ReferenceType::Cell {
11152 sheet,
11153 row,
11154 col,
11155 row_abs,
11156 col_abs,
11157 } => {
11158 let row0 = row
11159 .checked_sub(1)
11160 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11161 let col0 = col
11162 .checked_sub(1)
11163 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11164 let sheet_loc = match sheet.as_deref() {
11165 Some(name) => SheetLocator::from_name(name),
11166 None => SheetLocator::Current,
11167 };
11168 let coord = formualizer_common::RelativeCoord::new(row0, col0, *row_abs, *col_abs);
11169 SharedRef::Cell(SharedCellRef::new(sheet_loc, coord))
11170 }
11171 ReferenceType::Range {
11172 sheet,
11173 start_row,
11174 start_col,
11175 end_row,
11176 end_col,
11177 start_row_abs,
11178 start_col_abs,
11179 end_row_abs,
11180 end_col_abs,
11181 } => {
11182 let sheet_loc = match sheet.as_deref() {
11183 Some(name) => SheetLocator::from_name(name),
11184 None => SheetLocator::Current,
11185 };
11186 let sr = start_row
11187 .map(|r| {
11188 r.checked_sub(1)
11189 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11190 })
11191 .transpose()?;
11192 let sc = start_col
11193 .map(|c| {
11194 c.checked_sub(1)
11195 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11196 })
11197 .transpose()?;
11198 let er = end_row
11199 .map(|r| {
11200 r.checked_sub(1)
11201 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11202 })
11203 .transpose()?;
11204 let ec = end_col
11205 .map(|c| {
11206 c.checked_sub(1)
11207 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11208 })
11209 .transpose()?;
11210 let range = SharedRangeRef::from_parts(
11211 sheet_loc,
11212 sr.map(|idx| formualizer_common::AxisBound::new(idx, *start_row_abs)),
11213 sc.map(|idx| formualizer_common::AxisBound::new(idx, *start_col_abs)),
11214 er.map(|idx| formualizer_common::AxisBound::new(idx, *end_row_abs)),
11215 ec.map(|idx| formualizer_common::AxisBound::new(idx, *end_col_abs)),
11216 )
11217 .map_err(|_| ExcelError::new(ExcelErrorKind::Ref))?;
11218 SharedRef::Range(range)
11219 }
11220 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11221 };
11222
11223 let current_id = self
11224 .graph
11225 .sheet_id(current_sheet)
11226 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11227
11228 let resolve_loc = |loc: SheetLocator<'_>| -> Result<SheetLocator<'static>, ExcelError> {
11229 match loc {
11230 SheetLocator::Current => Ok(SheetLocator::Id(current_id)),
11231 SheetLocator::Id(id) => Ok(SheetLocator::Id(id)),
11232 SheetLocator::Name(name) => {
11233 let n = name.as_ref();
11234 self.graph
11235 .sheet_id(n)
11236 .map(SheetLocator::Id)
11237 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))
11238 }
11239 }
11240 };
11241
11242 match sr {
11243 SharedRef::Cell(cell) => {
11244 let owned = cell.into_owned();
11245 let sheet = resolve_loc(owned.sheet)?;
11246 Ok(SharedRef::Cell(SharedCellRef::new(sheet, owned.coord)))
11247 }
11248 SharedRef::Range(range) => {
11249 let owned = range.into_owned();
11250 let sheet = resolve_loc(owned.sheet)?;
11251 Ok(SharedRef::Range(SharedRangeRef {
11252 sheet,
11253 start_row: owned.start_row,
11254 start_col: owned.start_col,
11255 end_row: owned.end_row,
11256 end_col: owned.end_col,
11257 }))
11258 }
11259 }
11260 }
11261}
11262
11263impl<R> crate::traits::ReferenceResolver for Engine<R>
11266where
11267 R: EvaluationContext,
11268{
11269 fn resolve_cell_reference(
11270 &self,
11271 sheet: Option<&str>,
11272 row: u32,
11273 col: u32,
11274 ) -> Result<LiteralValue, ExcelError> {
11275 let Some(sheet_name) = sheet else {
11286 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
11287 "Unqualified cell reference resolved without sheet context".to_string(),
11288 ));
11289 };
11290 if let Some(v) = self.get_cell_value(sheet_name, row, col) {
11293 Ok(v)
11294 } else {
11295 Ok(LiteralValue::Number(0.0))
11297 }
11298 }
11299}
11300
11301impl<R> crate::traits::RangeResolver for Engine<R>
11302where
11303 R: EvaluationContext,
11304{
11305 fn resolve_range_reference(
11306 &self,
11307 sheet: Option<&str>,
11308 sr: Option<u32>,
11309 sc: Option<u32>,
11310 er: Option<u32>,
11311 ec: Option<u32>,
11312 ) -> Result<Box<dyn crate::traits::Range>, ExcelError> {
11313 self.resolver.resolve_range_reference(sheet, sr, sc, er, ec)
11316 }
11317}
11318
11319impl<R> crate::traits::NamedRangeResolver for Engine<R>
11320where
11321 R: EvaluationContext,
11322{
11323 fn resolve_named_range_reference(
11324 &self,
11325 name: &str,
11326 ) -> Result<Vec<Vec<LiteralValue>>, ExcelError> {
11327 self.resolver.resolve_named_range_reference(name)
11328 }
11329}
11330
11331impl<R> crate::traits::TableResolver for Engine<R>
11332where
11333 R: EvaluationContext,
11334{
11335 fn resolve_table_reference(
11336 &self,
11337 tref: &formualizer_parse::parser::TableReference,
11338 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
11339 self.resolver.resolve_table_reference(tref)
11340 }
11341}
11342
11343impl<R> crate::traits::SourceResolver for Engine<R>
11344where
11345 R: EvaluationContext,
11346{
11347 fn source_scalar_version(&self, name: &str) -> Option<u64> {
11348 self.resolver.source_scalar_version(name)
11349 }
11350
11351 fn resolve_source_scalar(&self, name: &str) -> Result<LiteralValue, ExcelError> {
11352 self.resolver.resolve_source_scalar(name)
11353 }
11354
11355 fn source_table_version(&self, name: &str) -> Option<u64> {
11356 self.resolver.source_table_version(name)
11357 }
11358
11359 fn resolve_source_table(
11360 &self,
11361 name: &str,
11362 ) -> Result<Box<dyn crate::traits::Table>, ExcelError> {
11363 self.resolver.resolve_source_table(name)
11364 }
11365}
11366
11367impl<R> crate::traits::Resolver for Engine<R> where R: EvaluationContext {}
11369
11370impl<R> crate::traits::FunctionProvider for Engine<R>
11372where
11373 R: EvaluationContext,
11374{
11375 fn get_function(
11376 &self,
11377 prefix: &str,
11378 name: &str,
11379 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
11380 self.resolver.get_function(prefix, name)
11381 }
11382}
11383
11384impl<R> crate::traits::EvaluationContext for Engine<R>
11386where
11387 R: EvaluationContext,
11388{
11389 fn clock(&self) -> &dyn crate::timezone::ClockProvider {
11390 &self.clock
11391 }
11392
11393 fn thread_pool(&self) -> Option<&Arc<rayon::ThreadPool>> {
11394 self.thread_pool.as_ref()
11395 }
11396
11397 fn cancellation_token(&self) -> Option<Arc<std::sync::atomic::AtomicBool>> {
11398 self.active_cancel_flag.clone()
11399 }
11400
11401 fn chunk_hint(&self) -> Option<usize> {
11402 let hint =
11404 (self.config.stripe_height as usize).saturating_mul(self.config.stripe_width as usize);
11405 Some(hint.clamp(1024, 1 << 20)) }
11407
11408 fn volatile_level(&self) -> crate::traits::VolatileLevel {
11409 self.config.volatile_level
11410 }
11411
11412 fn workbook_seed(&self) -> u64 {
11413 self.config.workbook_seed
11414 }
11415
11416 fn recalc_epoch(&self) -> u64 {
11417 self.recalc_epoch
11418 }
11419
11420 fn workbook_sheet_count(&self) -> Option<usize> {
11421 Some(self.graph.sheet_reg().active_len())
11422 }
11423
11424 fn sheet_index_by_name(&self, sheet: &str) -> Option<usize> {
11425 self.graph.sheet_reg().active_position(sheet)
11426 }
11427
11428 fn current_sheet_index(&self, current_sheet: &str) -> Option<usize> {
11429 self.sheet_index_by_name(current_sheet)
11430 }
11431
11432 fn inspect_reference(
11433 &self,
11434 reference: &ReferenceType,
11435 current_sheet: &str,
11436 ) -> Result<Option<ReferenceInfo>, ExcelError> {
11437 let sheet_info = |sheet_name: &str| -> Result<(SheetId, usize), ExcelError> {
11438 let sheet_id = self
11439 .graph
11440 .sheet_id(sheet_name)
11441 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11442 let sheet_index = self
11443 .graph
11444 .sheet_reg()
11445 .active_position_by_id(sheet_id)
11446 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11447 Ok((sheet_id, sheet_index))
11448 };
11449
11450 let cell_info =
11451 |sheet_name: &str, row: u32, col: u32| -> Result<ReferenceInfo, ExcelError> {
11452 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
11453 let row0 = row
11454 .checked_sub(1)
11455 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11456 let col0 = col
11457 .checked_sub(1)
11458 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11459 Ok(ReferenceInfo {
11460 first_sheet_index: Some(sheet_index),
11461 sheet_count: Some(1),
11462 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
11463 })
11464 };
11465
11466 let range_info = |sheet_name: &str,
11467 start_row: Option<u32>,
11468 start_col: Option<u32>|
11469 -> Result<ReferenceInfo, ExcelError> {
11470 let (sheet_id, sheet_index) = sheet_info(sheet_name)?;
11471 let row = start_row.unwrap_or(1);
11472 let col = start_col.unwrap_or(1);
11473 let row0 = row
11474 .checked_sub(1)
11475 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11476 let col0 = col
11477 .checked_sub(1)
11478 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11479 Ok(ReferenceInfo {
11480 first_sheet_index: Some(sheet_index),
11481 sheet_count: Some(1),
11482 first_cell: Some(CellRef::new(sheet_id, Coord::new(row0, col0, true, true))),
11483 })
11484 };
11485
11486 let info = match reference {
11487 ReferenceType::Cell {
11488 sheet, row, col, ..
11489 } => {
11490 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
11491 cell_info(sheet_name, *row, *col)?
11492 }
11493 ReferenceType::Range {
11494 sheet,
11495 start_row,
11496 start_col,
11497 ..
11498 } => {
11499 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
11500 range_info(sheet_name, *start_row, *start_col)?
11501 }
11502 ReferenceType::Cell3D {
11503 sheet_first,
11504 sheet_last,
11505 row,
11506 col,
11507 ..
11508 } => {
11509 let first = cell_info(sheet_first, *row, *col)?;
11510 ReferenceInfo {
11511 first_sheet_index: first.first_sheet_index,
11512 sheet_count: self
11513 .graph
11514 .sheet_reg()
11515 .active_span_len(sheet_first, sheet_last),
11516 first_cell: first.first_cell,
11517 }
11518 }
11519 ReferenceType::Range3D {
11520 sheet_first,
11521 sheet_last,
11522 start_row,
11523 start_col,
11524 ..
11525 } => {
11526 let first = range_info(sheet_first, *start_row, *start_col)?;
11527 ReferenceInfo {
11528 first_sheet_index: first.first_sheet_index,
11529 sheet_count: self
11530 .graph
11531 .sheet_reg()
11532 .active_span_len(sheet_first, sheet_last),
11533 first_cell: first.first_cell,
11534 }
11535 }
11536 ReferenceType::NamedRange(name) => {
11537 let current_id = self
11538 .graph
11539 .sheet_id(current_sheet)
11540 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11541 let named = self
11542 .graph
11543 .resolve_name_entry(name, current_id)
11544 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11545 match &named.definition {
11546 NamedDefinition::Cell(cell) => ReferenceInfo {
11547 first_sheet_index: self
11548 .graph
11549 .sheet_reg()
11550 .active_position_by_id(cell.sheet_id),
11551 sheet_count: Some(1),
11552 first_cell: Some(*cell),
11553 },
11554 NamedDefinition::Range(range) => ReferenceInfo {
11555 first_sheet_index: self
11556 .graph
11557 .sheet_reg()
11558 .active_position_by_id(range.start.sheet_id),
11559 sheet_count: Some(1),
11560 first_cell: Some(range.start),
11561 },
11562 NamedDefinition::Literal(_) | NamedDefinition::Formula { .. } => {
11563 ReferenceInfo {
11564 first_sheet_index: None,
11565 sheet_count: None,
11566 first_cell: None,
11567 }
11568 }
11569 }
11570 }
11571 ReferenceType::Table(tref) => {
11572 let table = self
11573 .graph
11574 .resolve_table_entry(&tref.name)
11575 .ok_or_else(|| ExcelError::new(ExcelErrorKind::Ref))?;
11576 ReferenceInfo {
11577 first_sheet_index: self
11578 .graph
11579 .sheet_reg()
11580 .active_position_by_id(table.range.start.sheet_id),
11581 sheet_count: Some(1),
11582 first_cell: Some(table.range.start),
11583 }
11584 }
11585 ReferenceType::External(_) => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11586 };
11587
11588 Ok(Some(info))
11589 }
11590
11591 fn formula_text_at_cell(&self, cell: CellRef) -> Result<Option<String>, ExcelError> {
11592 let sheet_name = self.graph.sheet_name(cell.sheet_id);
11593 if sheet_name.is_empty() {
11594 return Err(ExcelError::new(ExcelErrorKind::Ref));
11595 }
11596 let row = cell.coord.row() + 1;
11597 let col = cell.coord.col() + 1;
11598
11599 if let Some(entries) = self.staged_formulas.get(sheet_name)
11600 && let Some(text) = entries.get(row, col)
11601 {
11602 return Ok(Some(if text.starts_with('=') {
11603 text.to_owned()
11604 } else {
11605 format!("={text}")
11606 }));
11607 }
11608
11609 let Some(vertex) = self.graph.get_vertex_for_cell(&cell) else {
11610 return Ok(None);
11611 };
11612 let Some(ast) = self.graph.get_formula(vertex) else {
11613 return Ok(None);
11614 };
11615 Ok(Some(formualizer_parse::pretty::canonical_formula(&ast)))
11616 }
11617
11618 fn used_rows_for_columns(
11619 &self,
11620 sheet: &str,
11621 start_col: u32,
11622 end_col: u32,
11623 ) -> Option<(u32, u32)> {
11624 let sheet_id = self.graph.sheet_id(sheet)?;
11626 let snap = self.data_snapshot_id();
11627 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11628 guard
11629 .as_ref()
11630 .and_then(|cache| cache.get_row_bounds(sheet_id, start_col, end_col, snap))
11631 }) {
11632 return cached;
11633 }
11634
11635 let arrow_bounds = self
11636 .sheet_store()
11637 .sheet(sheet)
11638 .and_then(|_| self.arrow_used_row_bounds(sheet, start_col, end_col));
11639 let formula_bounds = self.formula_row_bounds_for_columns(sheet, start_col, end_col);
11640 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11641 Some(bounds)
11642 } else {
11643 let sc0 = start_col.saturating_sub(1);
11644 let ec0 = end_col.saturating_sub(1);
11645 self.graph
11646 .used_row_bounds_for_columns(sheet_id, sc0, ec0)
11647 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11648 };
11649
11650 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11651 guard
11652 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11653 .put_row_bounds(sheet_id, start_col, end_col, snap, computed);
11654 }
11655
11656 computed
11657 }
11658
11659 fn used_cols_for_rows(&self, sheet: &str, start_row: u32, end_row: u32) -> Option<(u32, u32)> {
11660 let sheet_id = self.graph.sheet_id(sheet)?;
11662 let snap = self.data_snapshot_id();
11663 if let Some(cached) = self.used_axis_bounds_cache.read().ok().and_then(|guard| {
11664 guard
11665 .as_ref()
11666 .and_then(|cache| cache.get_col_bounds(sheet_id, start_row, end_row, snap))
11667 }) {
11668 return cached;
11669 }
11670
11671 let arrow_bounds = self
11672 .sheet_store()
11673 .sheet(sheet)
11674 .and_then(|_| self.arrow_used_col_bounds(sheet, start_row, end_row));
11675 let formula_bounds = self.formula_col_bounds_for_rows(sheet, start_row, end_row);
11676 let computed = if let Some(bounds) = Self::union_used_bounds(arrow_bounds, formula_bounds) {
11677 Some(bounds)
11678 } else {
11679 let sr0 = start_row.saturating_sub(1);
11680 let er0 = end_row.saturating_sub(1);
11681 self.graph
11682 .used_col_bounds_for_rows(sheet_id, sr0, er0)
11683 .map(|(a0, b0)| (a0 + 1, b0 + 1))
11684 };
11685
11686 if let Ok(mut guard) = self.used_axis_bounds_cache.write() {
11687 guard
11688 .get_or_insert_with(|| UsedAxisBoundsCache::new(snap))
11689 .put_col_bounds(sheet_id, start_row, end_row, snap, computed);
11690 }
11691
11692 computed
11693 }
11694
11695 fn sheet_bounds(&self, sheet: &str) -> Option<(u32, u32)> {
11696 let _ = self.graph.sheet_id(sheet)?;
11697 Some((1_048_576, 16_384)) }
11701
11702 fn data_snapshot_id(&self) -> u64 {
11703 self.snapshot_id.load(std::sync::atomic::Ordering::Relaxed)
11704 }
11705
11706 fn backend_caps(&self) -> crate::traits::BackendCaps {
11707 crate::traits::BackendCaps {
11708 streaming: true,
11709 used_region: true,
11710 write: false,
11711 tables: false,
11712 async_stream: false,
11713 }
11714 }
11715
11716 fn build_lookup_index(
11717 &self,
11718 view: &RangeView<'_>,
11719 axis: LookupAxis,
11720 ) -> Option<Arc<LookupIndex>> {
11721 self.build_lookup_index_impl(view, axis)
11722 }
11723
11724 fn date_system(&self) -> crate::engine::DateSystem {
11727 self.config.date_system
11728 }
11729 fn resolve_range_view<'c>(
11731 &'c self,
11732 reference: &ReferenceType,
11733 current_sheet: &str,
11734 ) -> Result<RangeView<'c>, ExcelError> {
11735 match reference {
11736 ReferenceType::External(ext) => {
11737 let name = ext.raw.as_str();
11738 match ext.kind {
11739 formualizer_parse::parser::ExternalRefKind::Cell { .. } => {
11740 let Some(source) = self.graph.resolve_source_scalar_entry(name) else {
11741 return Err(ExcelError::new(ExcelErrorKind::Name)
11742 .with_message(format!("Undefined name: {name}")));
11743 };
11744 let version = source
11745 .version
11746 .or_else(|| self.resolver.source_scalar_version(name));
11747 let v = self.resolve_source_scalar_cached(name, version)?;
11748 Ok(RangeView::from_owned_rows(
11749 vec![vec![v]],
11750 self.config.date_system,
11751 ))
11752 }
11753 formualizer_parse::parser::ExternalRefKind::Range { .. } => {
11754 let Some(source) = self.graph.resolve_source_table_entry(name) else {
11755 return Err(ExcelError::new(ExcelErrorKind::Name)
11756 .with_message(format!("Undefined table: {name}")));
11757 };
11758 let version = source
11759 .version
11760 .or_else(|| self.resolver.source_table_version(name));
11761 let table = self.resolve_source_table_cached(name, version)?;
11762 let spec = Some(formualizer_parse::parser::TableSpecifier::Data);
11763 self.source_table_to_range_view(table.as_ref(), &spec)
11764 }
11765 }
11766 }
11767 ReferenceType::Range { .. } => {
11768 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11769 let formualizer_common::SheetRef::Range(range) = shared else {
11770 return Err(ExcelError::new(ExcelErrorKind::Ref));
11771 };
11772 let sheet_id = match range.sheet {
11773 formualizer_common::SheetLocator::Id(id) => id,
11774 _ => return Err(ExcelError::new(ExcelErrorKind::Ref)),
11775 };
11776 let sheet_name = self.graph.sheet_name(sheet_id);
11777
11778 let bounded_range = if range.start_row.is_some()
11779 && range.start_col.is_some()
11780 && range.end_row.is_some()
11781 && range.end_col.is_some()
11782 {
11783 Some(RangeRef::try_from_shared(range.as_ref())?)
11784 } else {
11785 None
11786 };
11787
11788 let mut sr = bounded_range
11789 .as_ref()
11790 .map(|r| r.start.coord.row() + 1)
11791 .or_else(|| range.start_row.map(|b| b.index + 1));
11792 let mut sc = bounded_range
11793 .as_ref()
11794 .map(|r| r.start.coord.col() + 1)
11795 .or_else(|| range.start_col.map(|b| b.index + 1));
11796 let mut er = bounded_range
11797 .as_ref()
11798 .map(|r| r.end.coord.row() + 1)
11799 .or_else(|| range.end_row.map(|b| b.index + 1));
11800 let mut ec = bounded_range
11801 .as_ref()
11802 .map(|r| r.end.coord.col() + 1)
11803 .or_else(|| range.end_col.map(|b| b.index + 1));
11804
11805 if sr.is_none() && er.is_none() {
11806 let scv = sc.unwrap_or(1);
11808 let ecv = ec.unwrap_or(scv);
11809 sr = Some(1);
11810 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11811 er = Some(max_r);
11812 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11813 er = Some(self.config.max_open_ended_rows);
11814 }
11815 }
11816 if sc.is_none() && ec.is_none() {
11817 let srv = sr.unwrap_or(1);
11819 let erv = er.unwrap_or(srv);
11820 sc = Some(1);
11821 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11822 ec = Some(max_c);
11823 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11824 ec = Some(self.config.max_open_ended_cols);
11825 }
11826 }
11827 if sr.is_some() && er.is_none() {
11828 let scv = sc.unwrap_or(1);
11829 let ecv = ec.unwrap_or(scv);
11830 if let Some((_, max_r)) = self.used_rows_for_columns(sheet_name, scv, ecv) {
11831 er = Some(max_r);
11832 } else if let Some((max_rows, _)) = self.sheet_bounds(sheet_name) {
11833 er = Some(self.config.max_open_ended_rows);
11834 }
11835 }
11836 if er.is_some() && sr.is_none() {
11837 sr = Some(1);
11839 }
11840 if sc.is_some() && ec.is_none() {
11841 let srv = sr.unwrap_or(1);
11842 let erv = er.unwrap_or(srv);
11843 if let Some((_, max_c)) = self.used_cols_for_rows(sheet_name, srv, erv) {
11844 ec = Some(max_c);
11845 } else if let Some((_, max_cols)) = self.sheet_bounds(sheet_name) {
11846 ec = Some(self.config.max_open_ended_cols);
11847 }
11848 }
11849 if ec.is_some() && sc.is_none() {
11850 sc = Some(1);
11852 }
11853
11854 let sr = sr.unwrap_or(1);
11855 let sc = sc.unwrap_or(1);
11856 let er = er.unwrap_or(sr.saturating_sub(1));
11857 let ec = ec.unwrap_or(sc.saturating_sub(1));
11858
11859 if self.force_materialize_range_views {
11860 if er < sr || ec < sc {
11861 return Ok(RangeView::from_owned_rows(
11862 Vec::new(),
11863 self.config.date_system,
11864 ));
11865 }
11866 let h = (er - sr + 1) as u64;
11867 let w = (ec - sc + 1) as u64;
11868 let cell_count = h.saturating_mul(w);
11869 if cell_count <= self.config.spill.max_spill_cells as u64 {
11870 let mut rows: Vec<Vec<LiteralValue>> = Vec::with_capacity(h as usize);
11871 for r in sr..=er {
11872 let mut rowv: Vec<LiteralValue> = Vec::with_capacity(w as usize);
11873 for c in sc..=ec {
11874 rowv.push(
11875 self.get_cell_value(sheet_name, r, c)
11876 .unwrap_or(LiteralValue::Empty),
11877 );
11878 }
11879 rows.push(rowv);
11880 }
11881 return Ok(RangeView::from_owned_rows(rows, self.config.date_system));
11882 }
11883 }
11884
11885 let Some(asheet) = self.sheet_store().sheet(sheet_name) else {
11886 return Ok(RangeView::from_owned_rows(
11887 Vec::new(),
11888 self.config.date_system,
11889 ));
11890 };
11891
11892 let rv = if er < sr || ec < sc {
11893 asheet.range_view(1, 1, 0, 0)
11894 } else {
11895 let sr0 = sr.saturating_sub(1) as usize;
11896 let sc0 = sc.saturating_sub(1) as usize;
11897 let er0 = er.saturating_sub(1) as usize;
11898 let ec0 = ec.saturating_sub(1) as usize;
11899 asheet.range_view(sr0, sc0, er0, ec0)
11900 };
11901
11902 Ok(rv)
11903 }
11904 ReferenceType::Cell { .. } => {
11905 let shared = self.resolve_shared_ref(reference, current_sheet)?;
11906 let formualizer_common::SheetRef::Cell(cell) = shared else {
11907 return Err(ExcelError::new(ExcelErrorKind::Ref));
11908 };
11909 let addr = CellRef::try_from_shared(cell)?;
11910 let sheet_id = addr.sheet_id;
11911 let sheet_name = self.graph.sheet_name(sheet_id);
11912 let row = addr.coord.row() + 1;
11913 let col = addr.coord.col() + 1;
11914
11915 if self.force_materialize_range_views {
11916 let v = self
11917 .get_cell_value(sheet_name, row, col)
11918 .unwrap_or(LiteralValue::Empty);
11919 return Ok(RangeView::from_owned_rows(
11920 vec![vec![v]],
11921 self.config.date_system,
11922 ));
11923 }
11924
11925 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
11926 let r0 = row.saturating_sub(1) as usize;
11927 let c0 = col.saturating_sub(1) as usize;
11928 let rv = asheet.range_view(r0, c0, r0, c0);
11929 Ok(rv)
11930 } else {
11931 let v = self
11932 .get_cell_value(sheet_name, row, col)
11933 .unwrap_or(LiteralValue::Empty);
11934 Ok(RangeView::from_owned_rows(
11935 vec![vec![v]],
11936 self.config.date_system,
11937 ))
11938 }
11939 }
11940 ReferenceType::NamedRange(name) => {
11941 if let Some(current_id) = self.graph.sheet_id(current_sheet)
11942 && let Some(named) = self.graph.resolve_name_entry(name, current_id)
11943 {
11944 match &named.definition {
11945 NamedDefinition::Cell(cell_ref) => {
11946 let sheet_name = self.graph.sheet_name(cell_ref.sheet_id);
11947 if self.force_materialize_range_views {
11948 let v = self
11949 .get_cell_value(
11950 sheet_name,
11951 cell_ref.coord.row() + 1,
11952 cell_ref.coord.col() + 1,
11953 )
11954 .unwrap_or(LiteralValue::Empty);
11955 return Ok(RangeView::from_owned_rows(
11956 vec![vec![v]],
11957 self.config.date_system,
11958 ));
11959 } else {
11960 let asheet = self
11961 .sheet_store()
11962 .sheet(sheet_name)
11963 .expect("Arrow sheet missing for named cell");
11964 let r0 = cell_ref.coord.row() as usize;
11965 let c0 = cell_ref.coord.col() as usize;
11966 let rv = asheet.range_view(r0, c0, r0, c0);
11967 return Ok(rv);
11968 }
11969 }
11970 NamedDefinition::Range(range_ref) => {
11971 let sheet_name = self.graph.sheet_name(range_ref.start.sheet_id);
11972 let sr = range_ref.start.coord.row() + 1;
11973 let sc = range_ref.start.coord.col() + 1;
11974 let er = range_ref.end.coord.row() + 1;
11975 let ec = range_ref.end.coord.col() + 1;
11976 if self.force_materialize_range_views {
11977 let h = (er.saturating_sub(sr) + 1) as u64;
11978 let w = (ec.saturating_sub(sc) + 1) as u64;
11979 let cell_count = h.saturating_mul(w);
11980 if cell_count <= self.config.spill.max_spill_cells as u64 {
11981 let mut rows: Vec<Vec<LiteralValue>> =
11982 Vec::with_capacity(h as usize);
11983 for r in sr..=er {
11984 let mut rowv: Vec<LiteralValue> =
11985 Vec::with_capacity(w as usize);
11986 for c in sc..=ec {
11987 rowv.push(
11988 self.get_cell_value(sheet_name, r, c)
11989 .unwrap_or(LiteralValue::Empty),
11990 );
11991 }
11992 rows.push(rowv);
11993 }
11994 return Ok(RangeView::from_owned_rows(
11995 rows,
11996 self.config.date_system,
11997 ));
11998 }
11999 }
12000 let asheet = self
12001 .sheet_store()
12002 .sheet(sheet_name)
12003 .expect("Arrow sheet missing for named range");
12004 let sr0 = range_ref.start.coord.row() as usize;
12005 let sc0 = range_ref.start.coord.col() as usize;
12006 let er0 = range_ref.end.coord.row() as usize;
12007 let ec0 = range_ref.end.coord.col() as usize;
12008 let rv = asheet.range_view(sr0, sc0, er0, ec0);
12009 return Ok(rv);
12010 }
12011 NamedDefinition::Literal(v) => {
12012 return Ok(RangeView::from_owned_rows(
12013 vec![vec![v.clone()]],
12014 self.config.date_system,
12015 ));
12016 }
12017 NamedDefinition::Formula { .. } => {
12018 if let Some(value) = self.graph.get_value(named.vertex) {
12019 return Ok(RangeView::from_owned_rows(
12020 vec![vec![value]],
12021 self.config.date_system,
12022 ));
12023 }
12024 }
12025 }
12026 }
12027
12028 if let Some(source) = self.graph.resolve_source_scalar_entry(name) {
12029 let version = source
12030 .version
12031 .or_else(|| self.resolver.source_scalar_version(name));
12032 let v = self.resolve_source_scalar_cached(name, version)?;
12033 return Ok(RangeView::from_owned_rows(
12034 vec![vec![v]],
12035 self.config.date_system,
12036 ));
12037 }
12038
12039 let data = self.resolver.resolve_named_range_reference(name)?;
12040 Ok(RangeView::from_owned_rows(data, self.config.date_system))
12041 }
12042 ReferenceType::Table(tref) => {
12043 if let Some(table) = self.graph.resolve_table_entry(&tref.name) {
12044 let sheet_name = self.graph.sheet_name(table.range.start.sheet_id);
12045 let asheet = self
12046 .sheet_store()
12047 .sheet(sheet_name)
12048 .expect("Arrow sheet missing for table reference");
12049
12050 let sr0 = table.range.start.coord.row() as usize;
12051 let sc0 = table.range.start.coord.col() as usize;
12052 let er0 = table.range.end.coord.row() as usize;
12053 let ec0 = table.range.end.coord.col() as usize;
12054
12055 let has_totals = table.totals_row;
12056 let has_headers = table.header_row;
12057 let data_sr = if has_headers {
12058 sr0.saturating_add(1)
12059 } else {
12060 sr0
12061 };
12062 let data_er = if has_totals {
12063 er0.saturating_sub(1)
12064 } else {
12065 er0
12066 };
12067
12068 let select = |sr: usize, sc: usize, er: usize, ec: usize| {
12069 if sr > er || sc > ec {
12070 asheet.range_view(1, 1, 0, 0)
12071 } else {
12072 asheet.range_view(sr, sc, er, ec)
12073 }
12074 };
12075
12076 let av = match &tref.specifier {
12077 None => {
12078 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12079 "Table reference without specifier is unsupported".to_string(),
12080 ));
12081 }
12082 Some(formualizer_parse::parser::TableSpecifier::Column(col)) => {
12083 let Some(idx) = table.col_index(col) else {
12084 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12085 "Column refers to unknown table column".to_string(),
12086 ));
12087 };
12088 let c0 = sc0 + idx;
12089 select(data_sr, c0, data_er, c0)
12090 }
12091 Some(formualizer_parse::parser::TableSpecifier::ColumnRange(
12092 start,
12093 end,
12094 )) => {
12095 let Some(si) = table.col_index(start) else {
12096 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12097 "Column range refers to unknown column(s)".to_string(),
12098 ));
12099 };
12100 let Some(ei) = table.col_index(end) else {
12101 return Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
12102 "Column range refers to unknown column(s)".to_string(),
12103 ));
12104 };
12105 let (mut a, mut b) = (si, ei);
12106 if a > b {
12107 std::mem::swap(&mut a, &mut b);
12108 }
12109 let c_start = sc0 + a;
12110 let c_end = sc0 + b;
12111 select(data_sr, c_start, data_er, c_end)
12112 }
12113 Some(formualizer_parse::parser::TableSpecifier::All)
12114 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12115 formualizer_parse::parser::SpecialItem::All,
12116 )) => select(sr0, sc0, er0, ec0),
12117 Some(formualizer_parse::parser::TableSpecifier::Data)
12118 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12119 formualizer_parse::parser::SpecialItem::Data,
12120 )) => select(data_sr, sc0, data_er, ec0),
12121 Some(formualizer_parse::parser::TableSpecifier::Headers)
12122 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12123 formualizer_parse::parser::SpecialItem::Headers,
12124 )) => {
12125 if !has_headers {
12126 asheet.range_view(1, 1, 0, 0)
12127 } else {
12128 select(sr0, sc0, sr0, ec0)
12129 }
12130 }
12131 Some(formualizer_parse::parser::TableSpecifier::Totals)
12132 | Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12133 formualizer_parse::parser::SpecialItem::Totals,
12134 )) => {
12135 if !has_totals {
12136 asheet.range_view(1, 1, 0, 0)
12137 } else {
12138 select(er0, sc0, er0, ec0)
12139 }
12140 }
12141 Some(formualizer_parse::parser::TableSpecifier::SpecialItem(
12142 formualizer_parse::parser::SpecialItem::ThisRow,
12143 )) => {
12144 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12145 "@ (This Row) requires table-aware context; not yet supported"
12146 .to_string(),
12147 ));
12148 }
12149 Some(formualizer_parse::parser::TableSpecifier::Row(_))
12150 | Some(formualizer_parse::parser::TableSpecifier::Combination(_)) => {
12151 return Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(
12152 "Complex structured references not yet supported".to_string(),
12153 ));
12154 }
12155 };
12156
12157 return Ok(av);
12158 }
12159
12160 if let Some(source) = self.graph.resolve_source_table_entry(&tref.name) {
12161 let version = source
12162 .version
12163 .or_else(|| self.resolver.source_table_version(&tref.name));
12164 let table = self.resolve_source_table_cached(&tref.name, version)?;
12165 return self.source_table_to_range_view(table.as_ref(), &tref.specifier);
12166 }
12167
12168 let boxed = self.resolve_range_like(&ReferenceType::Table(tref.clone()))?;
12170 let owned = boxed.materialise().into_owned();
12171 Ok(RangeView::from_owned_rows(owned, self.config.date_system))
12172 }
12173 ReferenceType::Cell3D { .. } | ReferenceType::Range3D { .. } => {
12174 Err(ExcelError::new(ExcelErrorKind::NImpl)
12175 .with_message("3D references are not yet supported".to_string()))
12176 }
12177 }
12178 }
12179
12180 fn resolve_cell_reference_value(
12181 &self,
12182 sheet: Option<&str>,
12183 row: u32,
12184 col: u32,
12185 current_sheet: &str,
12186 ) -> Result<LiteralValue, ExcelError> {
12187 let sheet_name = sheet.unwrap_or(current_sheet);
12188 if self.graph.sheet_id(sheet_name).is_none() {
12189 return Err(ExcelError::new(ExcelErrorKind::Ref));
12190 }
12191 Ok(self
12192 .get_cell_value(sheet_name, row, col)
12193 .unwrap_or(LiteralValue::Empty))
12194 }
12195
12196 fn build_criteria_mask(
12197 &self,
12198 view: &RangeView<'_>,
12199 col_in_view: usize,
12200 pred: &crate::args::CriteriaPredicate,
12201 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
12202 if view.dims().1 == 0 {
12203 return None;
12204 }
12205 let sheet_rows = view.sheet().nrows as usize;
12208 if sheet_rows == 0 || view.start_row() >= sheet_rows {
12209 return Some(std::sync::Arc::new(arrow_array::BooleanArray::new_null(0)));
12210 }
12211 compute_criteria_mask(view, col_in_view, pred)
12212 }
12213
12214 fn build_row_visibility_mask(
12215 &self,
12216 view: &RangeView<'_>,
12217 mode: VisibilityMaskMode,
12218 ) -> Option<std::sync::Arc<arrow_array::BooleanArray>> {
12219 self.build_row_visibility_mask_for_view(view, mode)
12220 }
12221}
12222
12223impl<R> Engine<R>
12224where
12225 R: EvaluationContext,
12226{
12227 fn clear_spill_projection_and_mirror(
12228 &mut self,
12229 anchor_vertex: VertexId,
12230 delta: Option<&mut DeltaCollector>,
12231 ) {
12232 let spill_cells = self
12233 .graph
12234 .spill_cells_for_anchor(anchor_vertex)
12235 .map(|cells| cells.to_vec())
12236 .unwrap_or_default();
12237 if spill_cells.is_empty() {
12238 return;
12239 }
12240
12241 if let Some(delta) = delta
12242 && delta.mode != DeltaMode::Off
12243 {
12244 let empty = LiteralValue::Empty;
12245 for cell in spill_cells.iter() {
12246 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12247 let old = self
12248 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12249 .unwrap_or(LiteralValue::Empty);
12250 if old != empty {
12251 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12252 }
12253 }
12254 }
12255
12256 self.graph.clear_spill_region(anchor_vertex);
12257 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
12258 self.record_formula_plane_structural_change(scope);
12259 }
12260
12261 if self.config.arrow_storage_enabled
12262 && self.config.delta_overlay_enabled
12263 && self.config.write_formula_overlay_enabled
12264 {
12265 let empty = LiteralValue::Empty;
12266 for cell in spill_cells.iter() {
12267 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12268 self.mirror_value_to_computed_overlay(
12269 &sheet_name,
12270 cell.coord.row() + 1,
12271 cell.coord.col() + 1,
12272 &empty,
12273 );
12274 }
12275 }
12276 }
12277
12278 fn apply_cycle_outcome(
12292 &mut self,
12293 cycle: &[VertexId],
12294 mut delta: Option<&mut DeltaCollector>,
12295 dirty_filter: Option<&FxHashSet<VertexId>>,
12296 ) -> usize {
12297 let circ_error = LiteralValue::Error(
12298 ExcelError::new(ExcelErrorKind::Circ)
12299 .with_message("Circular dependency detected".to_string()),
12300 );
12301 let mut stamped = 0usize;
12302 for &vertex_id in cycle {
12303 if let Some(filter) = dirty_filter
12304 && !filter.contains(&vertex_id)
12305 {
12306 continue;
12307 }
12308 self.stamp_cycle_error(vertex_id, &circ_error, delta.as_deref_mut());
12309 stamped += 1;
12310 }
12311 stamped
12312 }
12313
12314 fn stamp_cycle_error(
12329 &mut self,
12330 vertex_id: VertexId,
12331 circ_error: &LiteralValue,
12332 mut delta: Option<&mut DeltaCollector>,
12333 ) {
12334 if self.graph.spill_registry_has_anchor(vertex_id) {
12336 self.clear_spill_projection_and_mirror(vertex_id, delta.as_deref_mut());
12337 }
12338 self.spill_mgr.release_owner(vertex_id);
12341
12342 if let Some(d) = delta
12344 && d.mode != DeltaMode::Off
12345 && let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id)
12346 {
12347 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12348 let old = self
12349 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12350 .unwrap_or(LiteralValue::Empty);
12351 if old != *circ_error {
12352 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12353 }
12354 }
12355
12356 self.graph
12357 .update_vertex_value(vertex_id, circ_error.clone());
12358 self.mirror_vertex_value_to_overlay(vertex_id, circ_error);
12359 }
12360
12361 fn handle_cycle_unit(
12374 &mut self,
12375 cycle: &[VertexId],
12376 mut delta: Option<&mut DeltaCollector>,
12377 dirty_filter: Option<&FxHashSet<VertexId>>,
12378 cancel_flag: Option<&AtomicBool>,
12379 ) -> Result<usize, ExcelError> {
12380 match self.config.cycle.detection {
12381 CycleDetection::Static => {
12382 Ok(self.apply_cycle_outcome(cycle, delta.as_deref_mut(), dirty_filter))
12383 }
12384 CycleDetection::Runtime => {
12385 if let Some(filter) = dirty_filter
12386 && !cycle.iter().any(|v| filter.contains(v))
12387 {
12388 return Ok(0);
12389 }
12390 self.evaluate_scc_unit(cycle, delta, cancel_flag)
12394 }
12395 }
12396 }
12397
12398 pub(crate) fn evaluate_scc_unit(
12418 &mut self,
12419 cycle: &[VertexId],
12420 mut delta: Option<&mut DeltaCollector>,
12421 cancel_flag: Option<&AtomicBool>,
12422 ) -> Result<usize, ExcelError> {
12423 struct SccMember {
12424 vertex: VertexId,
12425 cell: Option<CellRef>,
12426 }
12427
12428 let task_start = crate::instant::FzInstant::now();
12429
12430 let mut cell_members: Vec<(VertexId, CellRef)> = Vec::new();
12435 let mut name_members: Vec<(VertexId, String)> = Vec::new();
12436 let mut other_members: Vec<VertexId> = Vec::new();
12437 for &v in cycle {
12438 match self.graph.get_vertex_kind(v) {
12439 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
12440 match self.graph.get_cell_ref(v) {
12441 Some(cell) => cell_members.push((v, cell)),
12442 None => other_members.push(v),
12443 }
12444 }
12445 VertexKind::NamedScalar | VertexKind::NamedArray => {
12446 match self.graph.name_key_for_vertex(v) {
12447 Some(key) => name_members.push((v, key)),
12448 None => other_members.push(v),
12449 }
12450 }
12451 _ => other_members.push(v),
12452 }
12453 }
12454 cell_members.sort_unstable_by_key(|(_, c)| (c.sheet_id, c.coord.row(), c.coord.col()));
12455 name_members.sort_unstable_by(|(av, ak), (bv, bk)| ak.cmp(bk).then(av.cmp(bv)));
12456 other_members.sort_unstable();
12457
12458 let cell_refs: Vec<CellRef> = cell_members.iter().map(|(_, c)| *c).collect();
12459 let name_keys: Vec<String> = name_members.iter().map(|(_, k)| k.clone()).collect();
12460 let mut members: Vec<SccMember> = Vec::with_capacity(cycle.len());
12461 for (v, c) in &cell_members {
12462 members.push(SccMember {
12463 vertex: *v,
12464 cell: Some(*c),
12465 });
12466 }
12467 for (v, _) in &name_members {
12468 members.push(SccMember {
12469 vertex: *v,
12470 cell: None,
12471 });
12472 }
12473 for v in &other_members {
12474 members.push(SccMember {
12475 vertex: *v,
12476 cell: None,
12477 });
12478 }
12479 let n = members.len();
12480 let recordable = cell_refs.len() + name_keys.len();
12483
12484 let circ_error = LiteralValue::Error(
12485 ExcelError::new(ExcelErrorKind::Circ)
12486 .with_message("Circular dependency detected".to_string()),
12487 );
12488
12489 if !self.iterative_state_values.is_empty() {
12500 let restore: Vec<(VertexId, LiteralValue)> = members
12501 .iter()
12502 .filter_map(|m| {
12503 let cell = m.cell?;
12504 let persisted = self.iterative_state_values.get(&m.vertex)?;
12505 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12506 let overlay = self
12507 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12508 .unwrap_or(LiteralValue::Empty);
12509 if matches!(overlay, LiteralValue::Empty) {
12510 Some((m.vertex, persisted.clone()))
12511 } else {
12512 None
12513 }
12514 })
12515 .collect();
12516 for (vertex, value) in restore {
12517 self.mirror_vertex_value_to_overlay(vertex, &value);
12518 }
12519 }
12520
12521 let snapshot: Vec<LiteralValue> = members
12524 .iter()
12525 .map(|m| match m.cell {
12526 Some(cell) => {
12527 let sheet_name = self.graph.sheet_name(cell.sheet_id);
12528 self.get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
12529 .unwrap_or(LiteralValue::Empty)
12530 }
12531 None => self
12532 .graph
12533 .get_value(m.vertex)
12534 .unwrap_or(LiteralValue::Empty),
12535 })
12536 .collect();
12537
12538 let mut excluded = vec![false; n];
12543 let mut last_value = snapshot.clone();
12544 let mut stamped = 0usize;
12545 for (i, m) in members.iter().enumerate() {
12546 match self.graph.get_vertex_kind(m.vertex) {
12547 VertexKind::FormulaArray => {
12548 self.stamp_cycle_error(m.vertex, &circ_error, delta.as_deref_mut());
12552 excluded[i] = true;
12553 last_value[i] = circ_error.clone();
12554 stamped += 1;
12555 }
12556 VertexKind::FormulaScalar | VertexKind::NamedScalar | VertexKind::NamedArray => {}
12557 _ => excluded[i] = true,
12558 }
12559 }
12560
12561 let collector = LiveEdgeCollector::new_with_names(&cell_refs, &name_keys);
12562
12563 let mut out_edges: Vec<Vec<u32>> = vec![Vec::new(); n];
12565 let mut pos: Vec<i64> = vec![-1; n];
12567 let mut changed = vec![false; n];
12569
12570 macro_rules! run_member {
12573 ($i:expr) => {{
12574 let i: usize = $i;
12575 let m = &members[i];
12576 if i < recordable {
12577 collector.set_current(i as u32);
12578 }
12579 let value = {
12580 let ctx = RecordingContext::new(&*self, &collector);
12581 match self.evaluate_vertex_recorded(m.vertex, &ctx, &collector) {
12582 Ok(v) => v,
12583 Err(e) => LiteralValue::Error(e),
12584 }
12585 };
12586 let is_cell_formula = m.cell.is_some();
12587 if is_cell_formula && matches!(value, LiteralValue::Array(_)) {
12588 self.stamp_cycle_error(m.vertex, &circ_error, None);
12593 excluded[i] = true;
12594 stamped += 1;
12595 changed[i] = last_value[i] != circ_error;
12596 last_value[i] = circ_error.clone();
12597 } else {
12598 self.graph.update_vertex_value(m.vertex, value.clone());
12599 self.mirror_vertex_value_to_overlay(m.vertex, &value);
12600 #[cfg(debug_assertions)]
12604 if let Some(cell) = m.cell {
12605 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
12606 debug_assert!(
12607 self.read_delta_overlay_cell(
12608 &sheet_name,
12609 cell.coord.row() + 1,
12610 cell.coord.col() + 1
12611 )
12612 .is_none(),
12613 "user overlay must never shadow a formula SCC member ({sheet_name}!r{}c{})",
12614 cell.coord.row() + 1,
12615 cell.coord.col() + 1
12616 );
12617 }
12618 changed[i] = last_value[i] != value;
12619 last_value[i] = value;
12620 }
12621 }};
12622 }
12623
12624 let check_cancel = |flag: Option<&AtomicBool>| -> Result<(), ExcelError> {
12625 if let Some(flag) = flag
12626 && flag.load(Ordering::Relaxed)
12627 {
12628 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
12629 .with_message("Evaluation cancelled during SCC evaluation".to_string()));
12630 }
12631 Ok(())
12632 };
12633
12634 check_cancel(cancel_flag)?;
12636 let mut passes = 1usize;
12637 {
12638 let mut p = 0i64;
12639 for i in 0..n {
12640 if excluded[i] {
12641 continue;
12642 }
12643 run_member!(i);
12644 pos[i] = p;
12645 p += 1;
12646 }
12647 }
12648
12649 let policy = self.config.cycle.policy;
12665 let cap = n + 2;
12666 let mut witnessed_cycles = 0usize;
12667 let mut capped = false;
12668 let mut iterating = false;
12670 let mut converged = false;
12671 let mut prev_pass: Option<Vec<LiteralValue>> = None;
12675 let mut iter_max_delta = 0f64;
12678 let mut iter_nan_converged = 0usize;
12679 let mut settle_passes = 0usize;
12683 loop {
12684 let drained = collector.take_edges();
12687 for i in 0..n {
12688 if pos[i] >= 0 {
12689 out_edges[i].clear();
12690 }
12691 }
12692 for (from, to) in drained {
12693 debug_assert!(
12694 pos[from as usize] >= 0,
12695 "edge from a member that did not run"
12696 );
12697 out_edges[from as usize].push(to);
12698 }
12699 let mut edges: Vec<(u32, u32)> = Vec::new();
12700 for (i, outs) in out_edges.iter().enumerate() {
12701 if excluded[i] {
12702 continue;
12703 }
12704 for &t in outs {
12705 edges.push((i as u32, t));
12706 }
12707 }
12708 edges.sort_unstable();
12709 edges.dedup();
12710
12711 let analysis = analyze_live_graph(n, &edges);
12712
12713 if analysis.cycle_count > 0 {
12714 witnessed_cycles = witnessed_cycles.max(analysis.cycle_count);
12718 match policy {
12719 CyclePolicy::Error => {
12720 for i in 0..n {
12726 if analysis.in_cycle[i] && !excluded[i] {
12727 self.stamp_cycle_error(members[i].vertex, &circ_error, None);
12728 excluded[i] = true;
12729 last_value[i] = circ_error.clone();
12730 stamped += 1;
12731 }
12732 }
12733 check_cancel(cancel_flag)?;
12734 let order: Vec<usize> = analysis
12735 .topo
12736 .iter()
12737 .map(|&i| i as usize)
12738 .filter(|&i| !excluded[i])
12739 .collect();
12740 if !order.is_empty() {
12741 passes += 1;
12742 for i in order {
12743 run_member!(i);
12744 }
12745 }
12746 break;
12747 }
12748 CyclePolicy::Iterate {
12749 max_iterations,
12750 max_change,
12751 } => {
12752 iterating = true;
12754
12755 if let Some(prev) = &prev_pass {
12761 let mut round_max_delta = 0f64;
12762 let mut round_nan = 0usize;
12763 let mut all_converged = true;
12764 for i in 0..n {
12765 if excluded[i] {
12766 continue;
12770 }
12771 let out = crate::engine::convergence::values_converged(
12772 &prev[i],
12773 &last_value[i],
12774 max_change,
12775 self.config.date_system,
12776 );
12777 if out.nan_converged {
12778 round_nan += 1;
12779 }
12780 if let Some(d) = out.abs_delta {
12781 round_max_delta = round_max_delta.max(d);
12782 }
12783 if !out.converged {
12784 all_converged = false;
12785 }
12786 }
12787 iter_max_delta = round_max_delta;
12790 iter_nan_converged = round_nan;
12791 if all_converged {
12792 converged = true;
12793 break;
12794 }
12795 }
12796
12797 if passes >= max_iterations as usize {
12809 capped = true;
12810 break;
12811 }
12812
12813 check_cancel(cancel_flag)?;
12814 prev_pass = Some(last_value.clone());
12822 for x in pos.iter_mut() {
12823 *x = -1;
12824 }
12825 changed.fill(false);
12826 passes += 1;
12827 let mut p = 0i64;
12828 for i in 0..n {
12829 if excluded[i] {
12830 continue;
12831 }
12832 run_member!(i);
12833 pos[i] = p;
12834 p += 1;
12835 }
12836 continue;
12837 }
12838 }
12839 }
12840
12841 let mut stale: Vec<usize> = Vec::new();
12844 for i in 0..n {
12845 if excluded[i] {
12846 continue;
12847 }
12848 let is_stale = out_edges[i].iter().any(|&t| {
12849 let t = t as usize;
12850 changed[t] && (pos[i] < 0 || (pos[t] >= 0 && pos[i] < pos[t]))
12851 });
12852 if is_stale {
12853 stale.push(i);
12854 }
12855 }
12856 if stale.is_empty() {
12857 break; }
12859 if 1 + settle_passes >= cap {
12860 capped = true;
12862 for (i, m) in members.iter().enumerate() {
12863 if !excluded[i] {
12864 self.stamp_cycle_error(m.vertex, &circ_error, None);
12865 excluded[i] = true;
12866 last_value[i] = circ_error.clone();
12867 stamped += 1;
12868 }
12869 }
12870 break;
12871 }
12872
12873 check_cancel(cancel_flag)?;
12874 prev_pass = None;
12881 let topo_pos = analysis.topo_positions();
12882 stale.sort_unstable_by_key(|&i| topo_pos[i]);
12883 for x in pos.iter_mut() {
12884 *x = -1;
12885 }
12886 changed.fill(false);
12887 passes += 1;
12888 settle_passes += 1;
12889 for (p, i) in stale.into_iter().enumerate() {
12890 run_member!(i);
12891 pos[i] = p as i64;
12892 }
12893 }
12894
12895 if iterating && !converged && !capped {
12900 converged = true;
12901 }
12902
12903 collector.clear_current();
12906 if let Some(d) = delta
12907 && d.mode != DeltaMode::Off
12908 {
12909 for (i, m) in members.iter().enumerate() {
12910 if let Some(cell) = m.cell
12911 && last_value[i] != snapshot[i]
12912 {
12913 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
12914 }
12915 }
12916 }
12917
12918 if iterating {
12925 self.pending_iterative_redirty
12926 .extend(members.iter().map(|m| m.vertex));
12927 }
12928
12929 {
12930 let t = &mut self.last_cycle_telemetry;
12931 t.static_sccs += 1;
12932 if witnessed_cycles == 0 && stamped == 0 && !capped {
12933 t.phantom_sccs += 1;
12934 }
12935 t.live_cycles_witnessed += witnessed_cycles;
12936 t.circ_cells_stamped += stamped;
12937 t.settle_passes_total += passes;
12938 t.max_passes_single_scc = t.max_passes_single_scc.max(passes);
12939 if iterating {
12940 t.iterated_sccs += 1;
12941 if converged {
12942 t.converged_sccs += 1;
12943 }
12944 t.max_abs_delta_at_stop = t.max_abs_delta_at_stop.max(iter_max_delta);
12945 t.nan_converged += iter_nan_converged;
12946 }
12947 if capped {
12948 t.capped_sccs += 1;
12949 }
12950 t.elapsed_ms += task_start.elapsed().as_millis();
12951 }
12952
12953 Ok(stamped)
12954 }
12955
12956 fn evaluate_vertex_recorded(
12964 &self,
12965 vertex_id: VertexId,
12966 ctx: &RecordingContext<'_, R>,
12967 collector: &LiveEdgeCollector,
12968 ) -> Result<LiteralValue, ExcelError> {
12969 if !self.graph.vertex_exists(vertex_id) {
12970 return Err(ExcelError::new(formualizer_common::ExcelErrorKind::Ref)
12971 .with_message(format!("Vertex not found: {vertex_id:?}")));
12972 }
12973
12974 let kind = self.graph.get_vertex_kind(vertex_id);
12975 let sheet_id = self.graph.get_vertex_sheet_id(vertex_id);
12976
12977 match kind {
12978 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
12979 let Some(ast_id) = self.graph.get_formula_id(vertex_id) else {
12980 return Ok(LiteralValue::Number(0.0)); };
12982 let sheet_name = self.graph.sheet_name(sheet_id);
12983 let cell_ref = self
12984 .graph
12985 .get_cell_ref(vertex_id)
12986 .expect("cell ref for vertex");
12987 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
12988 interpreter
12989 .evaluate_arena_ast(ast_id, self.graph.data_store(), self.graph.sheet_reg())
12990 .map(|cv| cv.into_literal())
12991 }
12992 VertexKind::NamedScalar | VertexKind::NamedArray => {
12993 let named_range = self.graph.named_range_by_vertex(vertex_id).ok_or_else(|| {
12994 ExcelError::new(ExcelErrorKind::Name)
12995 .with_message("Named range metadata missing".to_string())
12996 })?;
12997
12998 match &named_range.definition {
12999 NamedDefinition::Formula { ast, .. } => {
13000 let context_sheet = match named_range.scope {
13001 NameScope::Sheet(id) => id,
13002 NameScope::Workbook => sheet_id,
13003 };
13004 let sheet_name = self.graph.sheet_name(context_sheet);
13005 let cell_ref = self
13006 .graph
13007 .get_cell_ref(vertex_id)
13008 .unwrap_or_else(|| self.graph.make_cell_ref(sheet_name, 0, 0));
13009 let interpreter = Interpreter::new_with_cell(ctx, sheet_name, cell_ref);
13010 if kind == VertexKind::NamedScalar {
13011 interpreter.evaluate_ast(ast).map(|cv| cv.into_literal())
13012 } else {
13013 match interpreter.evaluate_ast(ast) {
13014 Ok(cv) => match cv.into_literal() {
13015 v @ LiteralValue::Array(_) => Ok(v),
13016 other => Ok(LiteralValue::Array(vec![vec![other]])),
13017 },
13018 Err(err) => Ok(LiteralValue::Error(err)),
13019 }
13020 }
13021 }
13022 NamedDefinition::Cell(cell_ref) => {
13023 collector.record_scalar(
13027 cell_ref.sheet_id,
13028 cell_ref.coord.row(),
13029 cell_ref.coord.col(),
13030 );
13031 self.evaluate_vertex_immutable(vertex_id)
13032 }
13033 NamedDefinition::Range(range_ref) => {
13034 if range_ref.start.sheet_id == range_ref.end.sheet_id {
13035 collector.record_rect(
13036 range_ref.start.sheet_id,
13037 range_ref.start.coord.row(),
13038 range_ref.start.coord.col(),
13039 range_ref.end.coord.row(),
13040 range_ref.end.coord.col(),
13041 );
13042 }
13043 self.evaluate_vertex_immutable(vertex_id)
13044 }
13045 NamedDefinition::Literal(_) => self.evaluate_vertex_immutable(vertex_id),
13046 }
13047 }
13048 _ => self.evaluate_vertex_immutable(vertex_id),
13049 }
13050 }
13051
13052 fn commit_spill_and_mirror(
13054 &mut self,
13055 anchor_vertex: VertexId,
13056 targets: &[CellRef],
13057 rows: Vec<Vec<LiteralValue>>,
13058 delta: Option<&mut DeltaCollector>,
13059 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13060 ) -> Result<(), ExcelError> {
13061 let prev_spill_cells = self
13062 .graph
13063 .spill_cells_for_anchor(anchor_vertex)
13064 .map(|cells| cells.to_vec())
13065 .unwrap_or_default();
13066
13067 if let Some(delta) = delta
13068 && delta.mode != DeltaMode::Off
13069 {
13070 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
13071 targets.iter().copied().collect();
13072 let empty = LiteralValue::Empty;
13073
13074 for cell in prev_spill_cells.iter() {
13076 if target_set.contains(cell) {
13077 continue;
13078 }
13079 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13080 let old = self
13081 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13082 .unwrap_or(LiteralValue::Empty);
13083 if old != empty {
13084 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13085 }
13086 }
13087
13088 if !targets.is_empty() && !rows.is_empty() && !rows[0].is_empty() {
13090 let width = rows[0].len();
13091 for (idx, cell) in targets.iter().enumerate() {
13092 let r_off = idx / width;
13093 let c_off = idx % width;
13094 let new = rows
13095 .get(r_off)
13096 .and_then(|r| r.get(c_off))
13097 .cloned()
13098 .unwrap_or(LiteralValue::Empty);
13099 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13100 let old = self
13101 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13102 .unwrap_or(LiteralValue::Empty);
13103 if old != new {
13104 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13105 }
13106 }
13107 } else {
13108 for cell in targets.iter() {
13110 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13111 let old = self
13112 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13113 .unwrap_or(LiteralValue::Empty);
13114 if !matches!(old, LiteralValue::Empty) {
13115 delta.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13116 }
13117 }
13118 }
13119 }
13120
13121 let arrow_sheets = &self.arrow_sheets;
13124 self.spill_mgr.commit_array_with_value_probe(
13125 &mut self.graph,
13126 anchor_vertex,
13127 targets,
13128 rows.clone(),
13129 overwritable_formulas,
13130 |g, cell| {
13131 let sheet_name = g.sheet_name(cell.sheet_id);
13132 let asheet = arrow_sheets.sheet(sheet_name)?;
13133 let r0 = cell.coord.row() as usize;
13134 let c0 = cell.coord.col() as usize;
13135 let v = asheet.get_cell_value(r0, c0);
13136 if matches!(v, LiteralValue::Empty) {
13137 None
13138 } else {
13139 Some(v)
13140 }
13141 },
13142 )?;
13143
13144 if let Some(scope) = Self::formula_plane_region_from_cells(&prev_spill_cells) {
13145 self.record_formula_plane_structural_change(scope);
13146 }
13147 if let Some(scope) = Self::formula_plane_region_from_cells(targets) {
13148 self.record_formula_plane_structural_change(scope);
13149 }
13150
13151 if self.config.arrow_storage_enabled
13152 && self.config.delta_overlay_enabled
13153 && self.config.write_formula_overlay_enabled
13154 {
13155 if !prev_spill_cells.is_empty() {
13156 let target_set: std::collections::HashSet<CellRef, CoordBuildHasher> =
13157 targets.iter().copied().collect();
13158 let empty = LiteralValue::Empty;
13159 for cell in prev_spill_cells.iter() {
13160 if !target_set.contains(cell) {
13161 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13162 self.mirror_value_to_computed_overlay(
13163 &sheet_name,
13164 cell.coord.row() + 1,
13165 cell.coord.col() + 1,
13166 &empty,
13167 );
13168 }
13169 }
13170 }
13171
13172 for (idx, cell) in targets.iter().enumerate() {
13173 if rows.is_empty() || rows[0].is_empty() {
13174 break;
13175 }
13176 let width = rows[0].len();
13177 let r_off = idx / width;
13178 let c_off = idx % width;
13179 let v = rows[r_off][c_off].clone();
13180 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13181 self.mirror_value_to_computed_overlay(
13182 &sheet_name,
13183 cell.coord.row() + 1,
13184 cell.coord.col() + 1,
13185 &v,
13186 );
13187 }
13188 }
13189 Ok(())
13190 }
13191}
13192
13193use crate::engine::effects::Effect;
13198use crate::engine::graph::editor::change_log::{ChangeEvent, ChangeLog, SpillSnapshot};
13199
13200impl<R> Engine<R>
13201where
13202 R: EvaluationContext,
13203{
13204 pub(crate) fn plan_vertex_effects(
13211 &mut self,
13212 vertex_id: VertexId,
13213 computed_value: LiteralValue,
13214 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13215 ) -> Result<Vec<Effect>, ExcelError> {
13216 let kind = self.graph.get_vertex_kind(vertex_id);
13217 let is_formula = matches!(kind, VertexKind::FormulaScalar | VertexKind::FormulaArray);
13218
13219 if !is_formula {
13223 if let Some(cell) = self.graph.get_cell_ref(vertex_id)
13224 && let Some(owner) = self.graph.spill_registry_anchor_for_cell(cell)
13225 && owner != vertex_id
13226 {
13227 return Ok(Vec::new());
13228 }
13229 return Ok(vec![Effect::WriteCell {
13231 vertex_id,
13232 value: computed_value,
13233 }]);
13234 }
13235
13236 match computed_value {
13237 LiteralValue::Array(rows) => {
13238 self.plan_array_effects(vertex_id, rows, overwritable_formulas)
13239 }
13240 other => self.plan_scalar_effects(vertex_id, other),
13241 }
13242 }
13243
13244 fn plan_scalar_effects(
13246 &self,
13247 vertex_id: VertexId,
13248 value: LiteralValue,
13249 ) -> Result<Vec<Effect>, ExcelError> {
13250 let has_spill = self
13251 .graph
13252 .spill_cells_for_anchor(vertex_id)
13253 .is_some_and(|c| !c.is_empty());
13254
13255 let mut effects = Vec::new();
13256 if has_spill {
13257 effects.push(Effect::SpillClear {
13258 anchor_vertex: vertex_id,
13259 });
13260 }
13261 effects.push(Effect::WriteCell { vertex_id, value });
13262 Ok(effects)
13263 }
13264
13265 fn plan_array_effects(
13267 &mut self,
13268 vertex_id: VertexId,
13269 rows: Vec<Vec<LiteralValue>>,
13270 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13271 ) -> Result<Vec<Effect>, ExcelError> {
13272 self.graph.set_kind(vertex_id, VertexKind::FormulaArray);
13274
13275 let anchor = self
13276 .graph
13277 .get_cell_ref(vertex_id)
13278 .expect("cell ref for vertex");
13279 let sheet_id = anchor.sheet_id;
13280 let h = rows.len() as u32;
13281 let w = rows.first().map(|r| r.len()).unwrap_or(0) as u32;
13282
13283 let spill_cells = (h as u64).saturating_mul(w as u64);
13285 if spill_cells > self.config.spill.max_spill_cells as u64 {
13286 return self.plan_spill_error_effects(vertex_id, "SpillTooLarge", h, w);
13287 }
13288
13289 const PACKED_MAX_ROW: u32 = 1_048_575;
13291 const PACKED_MAX_COL: u32 = 16_383;
13292 let end_row = anchor.coord.row().saturating_add(h).saturating_sub(1);
13293 let end_col = anchor.coord.col().saturating_add(w).saturating_sub(1);
13294 if end_row > PACKED_MAX_ROW || end_col > PACKED_MAX_COL {
13295 return self.plan_spill_error_effects(vertex_id, "Spill exceeds sheet bounds", h, w);
13296 }
13297
13298 let mut targets = Vec::new();
13299 for r in 0..h {
13300 for c in 0..w {
13301 targets.push(self.graph.make_cell_ref_internal(
13302 sheet_id,
13303 anchor.coord.row() + r,
13304 anchor.coord.col() + c,
13305 ));
13306 }
13307 }
13308
13309 match self.spill_mgr.reserve(
13311 vertex_id,
13312 anchor,
13313 SpillShape { rows: h, cols: w },
13314 SpillMeta {
13315 epoch: self.recalc_epoch,
13316 config: self.config.spill,
13317 },
13318 ) {
13319 Ok(()) => {
13320 if let Err(_e) = self.graph.plan_spill_region_allowing_formula_overwrite(
13322 vertex_id,
13323 &targets,
13324 overwritable_formulas,
13325 ) {
13326 return self.plan_spill_error_effects(vertex_id, "Spill blocked", h, w);
13327 }
13328
13329 if !self.graph.value_cache_enabled() {
13333 let sheet_name = self.graph.sheet_name(sheet_id);
13334 if let Some(asheet) = self.sheet_store().sheet(sheet_name) {
13335 for cell in targets.iter() {
13336 if *cell == anchor {
13338 continue;
13339 }
13340 if self.graph.spill_registry_anchor_for_cell(*cell).is_some() {
13342 continue;
13343 }
13344 if let Some(&vid) = self.graph.get_vertex_id_for_address(cell)
13346 && vid != vertex_id
13347 {
13348 match self.graph.get_vertex_kind(vid) {
13349 VertexKind::FormulaScalar | VertexKind::FormulaArray => {
13350 continue;
13351 }
13352 _ => {}
13353 }
13354 }
13355
13356 let v = asheet.get_cell_value(
13357 cell.coord.row() as usize,
13358 cell.coord.col() as usize,
13359 );
13360 if !matches!(v, LiteralValue::Empty) {
13361 return self.plan_spill_error_effects(
13362 vertex_id,
13363 "BlockedByValue",
13364 h,
13365 w,
13366 );
13367 }
13368 }
13369 }
13370 }
13371
13372 let top_left = rows
13373 .first()
13374 .and_then(|r| r.first())
13375 .cloned()
13376 .unwrap_or(LiteralValue::Empty);
13377
13378 let mut effects = Vec::new();
13379 let has_prev = self
13381 .graph
13382 .spill_cells_for_anchor(vertex_id)
13383 .is_some_and(|c| !c.is_empty());
13384 if has_prev {
13385 effects.push(Effect::SpillClear {
13386 anchor_vertex: vertex_id,
13387 });
13388 }
13389 effects.push(Effect::SpillCommit {
13390 anchor_vertex: vertex_id,
13391 anchor_cell: anchor,
13392 target_cells: targets,
13393 values: rows,
13394 });
13395 effects.push(Effect::WriteCell {
13396 vertex_id,
13397 value: top_left,
13398 });
13399 Ok(effects)
13400 }
13401 Err(e) => {
13402 let msg = e.message.unwrap_or_else(|| "Spill blocked".to_string());
13403 self.plan_spill_error_effects(vertex_id, &msg, h, w)
13404 }
13405 }
13406 }
13407
13408 fn plan_spill_error_effects(
13410 &self,
13411 vertex_id: VertexId,
13412 message: &str,
13413 expected_rows: u32,
13414 expected_cols: u32,
13415 ) -> Result<Vec<Effect>, ExcelError> {
13416 let spill_err = ExcelError::new(ExcelErrorKind::Spill)
13417 .with_message(message)
13418 .with_extra(formualizer_common::ExcelErrorExtra::Spill {
13419 expected_rows,
13420 expected_cols,
13421 });
13422 let spill_val = LiteralValue::Error(spill_err);
13423
13424 let effects = vec![
13425 Effect::SpillClear {
13426 anchor_vertex: vertex_id,
13427 },
13428 Effect::WriteCell {
13429 vertex_id,
13430 value: spill_val,
13431 },
13432 ];
13433 Ok(effects)
13434 }
13435
13436 pub(crate) fn apply_effect(
13438 &mut self,
13439 effect: &Effect,
13440 delta: Option<&mut DeltaCollector>,
13441 log: Option<&mut ChangeLog>,
13442 ) -> Result<(), ExcelError> {
13443 self.apply_effect_with_computed_writes(effect, delta, log, None)
13444 }
13445
13446 fn apply_effect_with_computed_writes(
13447 &mut self,
13448 effect: &Effect,
13449 delta: Option<&mut DeltaCollector>,
13450 log: Option<&mut ChangeLog>,
13451 computed_writes: Option<&mut ComputedWriteBuffer>,
13452 ) -> Result<(), ExcelError> {
13453 match effect {
13454 Effect::WriteCell { vertex_id, value } => {
13455 self.apply_write_cell(*vertex_id, value, delta, computed_writes)?;
13456 }
13457 Effect::SpillClear { anchor_vertex } => {
13458 self.apply_spill_clear(*anchor_vertex, delta, log, computed_writes)?;
13459 }
13460 Effect::SpillCommit {
13461 anchor_vertex,
13462 anchor_cell: _,
13463 target_cells,
13464 values,
13465 } => {
13466 self.apply_spill_commit(
13467 *anchor_vertex,
13468 target_cells,
13469 values.clone(),
13470 delta,
13471 log,
13472 computed_writes,
13473 )?;
13474 }
13475 }
13476 Ok(())
13477 }
13478
13479 fn apply_write_cell(
13481 &mut self,
13482 vertex_id: VertexId,
13483 value: &LiteralValue,
13484 delta: Option<&mut DeltaCollector>,
13485 mut computed_writes: Option<&mut ComputedWriteBuffer>,
13486 ) -> Result<(), ExcelError> {
13487 if let Some(d) = delta
13488 && d.mode != DeltaMode::Off
13489 {
13490 if let Some(buffer) = computed_writes.as_deref_mut() {
13491 self.flush_computed_write_buffer(buffer)?;
13492 }
13493 if let Some(cell) = self.graph.get_cell_ref_for_vertex(vertex_id) {
13494 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13495 let old = self
13496 .read_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13497 .unwrap_or(LiteralValue::Empty);
13498 if old != *value {
13499 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13500 }
13501 }
13502 }
13503 self.graph.update_vertex_value(vertex_id, value.clone());
13504 self.record_vertex_value_to_overlay(vertex_id, value, computed_writes)?;
13505 Ok(())
13506 }
13507
13508 fn apply_spill_clear(
13510 &mut self,
13511 anchor_vertex: VertexId,
13512 delta: Option<&mut DeltaCollector>,
13513 log: Option<&mut ChangeLog>,
13514 computed_writes: Option<&mut ComputedWriteBuffer>,
13515 ) -> Result<(), ExcelError> {
13516 if let Some(buffer) = computed_writes {
13517 self.flush_computed_write_buffer(buffer)?;
13518 }
13519
13520 let spill_cells = self
13521 .graph
13522 .spill_cells_for_anchor(anchor_vertex)
13523 .map(|cells| cells.to_vec())
13524 .unwrap_or_default();
13525 if spill_cells.is_empty() {
13526 return Ok(());
13527 }
13528
13529 let snapshot = if log.is_some() {
13531 self.snapshot_spill_for_anchor(anchor_vertex)
13532 } else {
13533 None
13534 };
13535
13536 if let Some(d) = delta
13538 && d.mode != DeltaMode::Off
13539 {
13540 let empty = LiteralValue::Empty;
13541 for cell in spill_cells.iter() {
13542 let sheet_name = self.graph.sheet_name(cell.sheet_id);
13543 let old = self
13544 .get_cell_value(sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13545 .unwrap_or(LiteralValue::Empty);
13546 if old != empty {
13547 d.record_cell(cell.sheet_id, cell.coord.row(), cell.coord.col());
13548 }
13549 }
13550 }
13551
13552 self.graph.clear_spill_region(anchor_vertex);
13553 if let Some(scope) = Self::formula_plane_region_from_cells(&spill_cells) {
13554 self.record_formula_plane_structural_change(scope);
13555 }
13556
13557 if self.config.arrow_storage_enabled
13559 && self.config.delta_overlay_enabled
13560 && self.config.write_formula_overlay_enabled
13561 {
13562 let empty = LiteralValue::Empty;
13563 for cell in spill_cells.iter() {
13564 let sheet_name = self.graph.sheet_name(cell.sheet_id).to_string();
13565 self.mirror_value_to_computed_overlay(
13566 &sheet_name,
13567 cell.coord.row() + 1,
13568 cell.coord.col() + 1,
13569 &empty,
13570 );
13571 }
13572 }
13573
13574 if let Some(log) = log
13576 && let Some(old) = snapshot
13577 {
13578 log.record(ChangeEvent::SpillCleared {
13579 anchor: anchor_vertex,
13580 old,
13581 });
13582 }
13583 Ok(())
13584 }
13585
13586 fn apply_spill_commit(
13588 &mut self,
13589 anchor_vertex: VertexId,
13590 target_cells: &[CellRef],
13591 values: Vec<Vec<LiteralValue>>,
13592 delta: Option<&mut DeltaCollector>,
13593 log: Option<&mut ChangeLog>,
13594 computed_writes: Option<&mut ComputedWriteBuffer>,
13595 ) -> Result<(), ExcelError> {
13596 if let Some(buffer) = computed_writes {
13597 self.flush_computed_write_buffer(buffer)?;
13598 }
13599
13600 let old_snapshot = if log.is_some() {
13602 self.snapshot_spill_for_anchor(anchor_vertex)
13603 } else {
13604 None
13605 };
13606
13607 self.commit_spill_and_mirror(
13609 anchor_vertex,
13610 target_cells,
13611 values.clone(),
13612 delta,
13613 None, )?;
13615
13616 if let Some(log) = log {
13618 log.record(ChangeEvent::SpillCommitted {
13619 anchor: anchor_vertex,
13620 old: old_snapshot,
13621 new: SpillSnapshot {
13622 target_cells: target_cells.to_vec(),
13623 values,
13624 },
13625 });
13626 }
13627 Ok(())
13628 }
13629
13630 fn snapshot_spill_for_anchor(&self, anchor: VertexId) -> Option<SpillSnapshot> {
13635 let cells = self.graph.spill_cells_for_anchor(anchor)?.to_vec();
13636 if cells.is_empty() {
13637 return None;
13638 }
13639
13640 let max = self.config.spill.max_spill_cells as usize;
13641 let mut cells = cells;
13642 if cells.len() > max {
13643 cells.truncate(max);
13644 }
13645
13646 let first = *cells.first().expect("non-empty spill cells");
13647 let sheet_name = self.graph.sheet_name(first.sheet_id).to_string();
13648 let row0 = first.coord.row();
13649 let col0 = first.coord.col();
13650
13651 let mut max_row = row0;
13652 let mut max_col = col0;
13653 let mut by_coord: FxHashMap<(u32, u32), LiteralValue> = FxHashMap::default();
13654 for cell in &cells {
13655 max_row = max_row.max(cell.coord.row());
13656 max_col = max_col.max(cell.coord.col());
13657 let v = self
13658 .get_cell_value(&sheet_name, cell.coord.row() + 1, cell.coord.col() + 1)
13659 .unwrap_or(LiteralValue::Empty);
13660 by_coord.insert((cell.coord.row(), cell.coord.col()), v);
13661 }
13662
13663 let rows = (max_row - row0 + 1) as usize;
13664 let cols = (max_col - col0 + 1) as usize;
13665 let mut values: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows);
13666 for r in 0..rows {
13667 let mut row: Vec<LiteralValue> = Vec::with_capacity(cols);
13668 for c in 0..cols {
13669 row.push(
13670 by_coord
13671 .get(&(row0 + r as u32, col0 + c as u32))
13672 .cloned()
13673 .unwrap_or(LiteralValue::Empty),
13674 );
13675 }
13676 values.push(row);
13677 }
13678
13679 Some(SpillSnapshot {
13680 target_cells: cells,
13681 values,
13682 })
13683 }
13684
13685 fn flush_before_range_dependent_vertex(
13686 &mut self,
13687 vertex_id: VertexId,
13688 computed_writes: &mut ComputedWriteBuffer,
13689 ) -> Result<(), ExcelError> {
13690 if self.graph.get_range_dependencies(vertex_id).is_some() {
13691 self.flush_computed_write_buffer(computed_writes)?;
13692 }
13693 Ok(())
13694 }
13695
13696 fn plan_vertex_effects_with_computed_flush(
13697 &mut self,
13698 vertex_id: VertexId,
13699 computed_value: LiteralValue,
13700 overwritable_formulas: Option<&rustc_hash::FxHashSet<VertexId>>,
13701 computed_writes: &mut ComputedWriteBuffer,
13702 ) -> Result<Vec<Effect>, ExcelError> {
13703 if matches!(&computed_value, LiteralValue::Array(_)) {
13704 self.flush_computed_write_buffer(computed_writes)?;
13705 }
13706 self.plan_vertex_effects(vertex_id, computed_value, overwritable_formulas)
13707 }
13708
13709 fn evaluate_small_layer_direct_effects(
13712 &mut self,
13713 layer: &super::scheduler::Layer,
13714 mut delta: Option<&mut DeltaCollector>,
13715 mut log: Option<&mut ChangeLog>,
13716 cancel_flag: Option<&AtomicBool>,
13717 cancel_check_every: usize,
13718 cancel_message: &'static str,
13719 ) -> Result<usize, ExcelError> {
13720 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13721 if cancel_check_every > 0
13722 && i % cancel_check_every == 0
13723 && cancel_flag.is_some_and(|flag| flag.load(Ordering::Relaxed))
13724 {
13725 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13726 .with_message(cancel_message.to_string()));
13727 }
13728 let value = match self.evaluate_vertex_immutable(vertex_id) {
13729 Ok(v) => v,
13730 Err(e) => LiteralValue::Error(e),
13731 };
13732 let effects = self.plan_vertex_effects(vertex_id, value, None)?;
13733 for effect in &effects {
13734 self.apply_effect_with_computed_writes(
13735 effect,
13736 delta.as_deref_mut(),
13737 log.as_deref_mut(),
13738 None,
13739 )?;
13740 }
13741 }
13742 Ok(layer.vertices.len())
13743 }
13744
13745 fn evaluate_layer_sequential_effects(
13747 &mut self,
13748 layer: &super::scheduler::Layer,
13749 ) -> Result<usize, ExcelError> {
13750 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13751 return self.evaluate_small_layer_direct_effects(
13752 layer,
13753 None,
13754 None,
13755 None,
13756 0,
13757 "Evaluation cancelled within layer",
13758 );
13759 }
13760
13761 let mut computed_writes = ComputedWriteBuffer::default();
13762 for &vertex_id in &layer.vertices {
13763 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13764 let value = match self.evaluate_vertex_immutable(vertex_id) {
13765 Ok(v) => v,
13766 Err(e) => LiteralValue::Error(e),
13767 };
13768 let effects = match self.plan_vertex_effects_with_computed_flush(
13769 vertex_id,
13770 value,
13771 None,
13772 &mut computed_writes,
13773 ) {
13774 Ok(effects) => effects,
13775 Err(e) => {
13776 self.flush_computed_write_buffer(&mut computed_writes)?;
13777 return Err(e);
13778 }
13779 };
13780 for effect in &effects {
13781 if let Err(e) = self.apply_effect_with_computed_writes(
13782 effect,
13783 None,
13784 None,
13785 Some(&mut computed_writes),
13786 ) {
13787 self.flush_computed_write_buffer(&mut computed_writes)?;
13788 return Err(e);
13789 }
13790 }
13791 }
13792 self.flush_computed_write_buffer(&mut computed_writes)?;
13793 Ok(layer.vertices.len())
13794 }
13795
13796 fn evaluate_layer_sequential_with_delta_effects(
13798 &mut self,
13799 layer: &super::scheduler::Layer,
13800 delta: &mut DeltaCollector,
13801 ) -> Result<usize, ExcelError> {
13802 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13803 return self.evaluate_small_layer_direct_effects(
13804 layer,
13805 Some(delta),
13806 None,
13807 None,
13808 0,
13809 "Evaluation cancelled within layer",
13810 );
13811 }
13812
13813 let mut computed_writes = ComputedWriteBuffer::default();
13814 for &vertex_id in &layer.vertices {
13815 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13816 let value = match self.evaluate_vertex_immutable(vertex_id) {
13817 Ok(v) => v,
13818 Err(e) => LiteralValue::Error(e),
13819 };
13820 let effects = match self.plan_vertex_effects_with_computed_flush(
13821 vertex_id,
13822 value,
13823 None,
13824 &mut computed_writes,
13825 ) {
13826 Ok(effects) => effects,
13827 Err(e) => {
13828 self.flush_computed_write_buffer(&mut computed_writes)?;
13829 return Err(e);
13830 }
13831 };
13832 for effect in &effects {
13833 if let Err(e) = self.apply_effect_with_computed_writes(
13834 effect,
13835 Some(delta),
13836 None,
13837 Some(&mut computed_writes),
13838 ) {
13839 self.flush_computed_write_buffer(&mut computed_writes)?;
13840 return Err(e);
13841 }
13842 }
13843 }
13844 self.flush_computed_write_buffer(&mut computed_writes)?;
13845 Ok(layer.vertices.len())
13846 }
13847
13848 fn evaluate_layer_sequential_cancellable_effects(
13850 &mut self,
13851 layer: &super::scheduler::Layer,
13852 cancel_flag: &AtomicBool,
13853 ) -> Result<usize, ExcelError> {
13854 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13855 return self.evaluate_small_layer_direct_effects(
13856 layer,
13857 None,
13858 None,
13859 Some(cancel_flag),
13860 256,
13861 "Evaluation cancelled within layer",
13862 );
13863 }
13864
13865 let mut computed_writes = ComputedWriteBuffer::default();
13866 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13867 if i % 256 == 0 && cancel_flag.load(Ordering::Relaxed) {
13868 self.flush_computed_write_buffer(&mut computed_writes)?;
13869 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13870 .with_message("Evaluation cancelled within layer".to_string()));
13871 }
13872 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13873 let value = match self.evaluate_vertex_immutable(vertex_id) {
13874 Ok(v) => v,
13875 Err(e) => LiteralValue::Error(e),
13876 };
13877 let effects = match self.plan_vertex_effects_with_computed_flush(
13878 vertex_id,
13879 value,
13880 None,
13881 &mut computed_writes,
13882 ) {
13883 Ok(effects) => effects,
13884 Err(e) => {
13885 self.flush_computed_write_buffer(&mut computed_writes)?;
13886 return Err(e);
13887 }
13888 };
13889 for effect in &effects {
13890 if let Err(e) = self.apply_effect_with_computed_writes(
13891 effect,
13892 None,
13893 None,
13894 Some(&mut computed_writes),
13895 ) {
13896 self.flush_computed_write_buffer(&mut computed_writes)?;
13897 return Err(e);
13898 }
13899 }
13900 }
13901 self.flush_computed_write_buffer(&mut computed_writes)?;
13902 Ok(layer.vertices.len())
13903 }
13904
13905 fn evaluate_layer_sequential_cancellable_demand_driven_effects(
13907 &mut self,
13908 layer: &super::scheduler::Layer,
13909 cancel_flag: &AtomicBool,
13910 ) -> Result<usize, ExcelError> {
13911 if layer.vertices.len() < COMPUTED_WRITE_COALESCING_MIN_LAYER_WIDTH {
13912 return self.evaluate_small_layer_direct_effects(
13913 layer,
13914 None,
13915 None,
13916 Some(cancel_flag),
13917 128,
13918 "Demand-driven evaluation cancelled within layer",
13919 );
13920 }
13921
13922 let mut computed_writes = ComputedWriteBuffer::default();
13923 for (i, &vertex_id) in layer.vertices.iter().enumerate() {
13924 if i % 128 == 0 && cancel_flag.load(Ordering::Relaxed) {
13925 self.flush_computed_write_buffer(&mut computed_writes)?;
13926 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
13927 .with_message("Demand-driven evaluation cancelled within layer".to_string()));
13928 }
13929 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
13930 let value = match self.evaluate_vertex_immutable(vertex_id) {
13931 Ok(v) => v,
13932 Err(e) => LiteralValue::Error(e),
13933 };
13934 let effects = match self.plan_vertex_effects_with_computed_flush(
13935 vertex_id,
13936 value,
13937 None,
13938 &mut computed_writes,
13939 ) {
13940 Ok(effects) => effects,
13941 Err(e) => {
13942 self.flush_computed_write_buffer(&mut computed_writes)?;
13943 return Err(e);
13944 }
13945 };
13946 for effect in &effects {
13947 if let Err(e) = self.apply_effect_with_computed_writes(
13948 effect,
13949 None,
13950 None,
13951 Some(&mut computed_writes),
13952 ) {
13953 self.flush_computed_write_buffer(&mut computed_writes)?;
13954 return Err(e);
13955 }
13956 }
13957 }
13958 self.flush_computed_write_buffer(&mut computed_writes)?;
13959 Ok(layer.vertices.len())
13960 }
13961
13962 fn evaluate_layer_parallel_effects(
13964 &mut self,
13965 layer: &super::scheduler::Layer,
13966 ) -> Result<usize, ExcelError> {
13967 use rayon::prelude::*;
13968
13969 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
13970
13971 let mut phase1: Vec<VertexId> = Vec::new();
13972 let mut phase2: Vec<VertexId> = Vec::new();
13973 for &vid in &layer.vertices {
13974 if self.graph.get_range_dependencies(vid).is_some() {
13975 phase2.push(vid);
13976 } else {
13977 phase1.push(vid);
13978 }
13979 }
13980
13981 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
13982 let mut applied = 0usize;
13983
13984 for group in [&phase1[..], &phase2[..]] {
13985 if group.is_empty() {
13986 continue;
13987 }
13988 let mut computed_writes = ComputedWriteBuffer::default();
13989
13990 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
13991 thread_pool.install(|| {
13992 group
13993 .par_iter()
13994 .map(
13995 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
13996 Ok(v) => Ok((vertex_id, v)),
13997 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
13998 },
13999 )
14000 .collect()
14001 });
14002
14003 match results {
14004 Ok(vertex_results) => {
14005 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14008 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14009 for (vertex_id, result) in vertex_results {
14010 if matches!(result, LiteralValue::Array(_)) {
14011 arrays.push((vertex_id, result));
14012 } else {
14013 others.push((vertex_id, result));
14014 }
14015 }
14016 for (vertex_id, result) in arrays {
14017 let effects = match self.plan_vertex_effects_with_computed_flush(
14018 vertex_id,
14019 result,
14020 Some(&inflight),
14021 &mut computed_writes,
14022 ) {
14023 Ok(effects) => effects,
14024 Err(e) => {
14025 self.flush_computed_write_buffer(&mut computed_writes)?;
14026 return Err(e);
14027 }
14028 };
14029 for effect in &effects {
14030 if let Err(e) = self.apply_effect_with_computed_writes(
14031 effect,
14032 None,
14033 None,
14034 Some(&mut computed_writes),
14035 ) {
14036 self.flush_computed_write_buffer(&mut computed_writes)?;
14037 return Err(e);
14038 }
14039 }
14040 applied = applied.saturating_add(1);
14041 }
14042 self.flush_computed_write_buffer(&mut computed_writes)?;
14044 for (vertex_id, result) in others {
14045 let effects = match self.plan_vertex_effects_with_computed_flush(
14046 vertex_id,
14047 result,
14048 Some(&inflight),
14049 &mut computed_writes,
14050 ) {
14051 Ok(effects) => effects,
14052 Err(e) => {
14053 self.flush_computed_write_buffer(&mut computed_writes)?;
14054 return Err(e);
14055 }
14056 };
14057 for effect in &effects {
14058 if let Err(e) = self.apply_effect_with_computed_writes(
14059 effect,
14060 None,
14061 None,
14062 Some(&mut computed_writes),
14063 ) {
14064 self.flush_computed_write_buffer(&mut computed_writes)?;
14065 return Err(e);
14066 }
14067 }
14068 applied = applied.saturating_add(1);
14069 }
14070 self.flush_computed_write_buffer(&mut computed_writes)?;
14072 }
14073 Err(e) => {
14074 self.flush_computed_write_buffer(&mut computed_writes)?;
14075 return Err(e);
14076 }
14077 }
14078 }
14079
14080 Ok(applied)
14081 }
14082
14083 fn evaluate_layer_parallel_with_delta_effects(
14085 &mut self,
14086 layer: &super::scheduler::Layer,
14087 delta: &mut DeltaCollector,
14088 ) -> Result<usize, ExcelError> {
14089 use rayon::prelude::*;
14090
14091 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
14092
14093 let mut phase1: Vec<VertexId> = Vec::new();
14094 let mut phase2: Vec<VertexId> = Vec::new();
14095 for &vid in &layer.vertices {
14096 if self.graph.get_range_dependencies(vid).is_some() {
14097 phase2.push(vid);
14098 } else {
14099 phase1.push(vid);
14100 }
14101 }
14102
14103 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
14104 let mut applied = 0usize;
14105
14106 for group in [&phase1[..], &phase2[..]] {
14107 if group.is_empty() {
14108 continue;
14109 }
14110 let mut computed_writes = ComputedWriteBuffer::default();
14111 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
14112 thread_pool.install(|| {
14113 group
14114 .par_iter()
14115 .map(
14116 |&vertex_id| match self.evaluate_vertex_immutable(vertex_id) {
14117 Ok(v) => Ok((vertex_id, v)),
14118 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
14119 },
14120 )
14121 .collect()
14122 });
14123
14124 match results {
14125 Ok(vertex_results) => {
14126 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14127 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14128 for (vertex_id, result) in vertex_results {
14129 if matches!(result, LiteralValue::Array(_)) {
14130 arrays.push((vertex_id, result));
14131 } else {
14132 others.push((vertex_id, result));
14133 }
14134 }
14135 for (vertex_id, result) in arrays {
14136 let effects = match self.plan_vertex_effects_with_computed_flush(
14137 vertex_id,
14138 result,
14139 Some(&inflight),
14140 &mut computed_writes,
14141 ) {
14142 Ok(effects) => effects,
14143 Err(e) => {
14144 self.flush_computed_write_buffer(&mut computed_writes)?;
14145 return Err(e);
14146 }
14147 };
14148 for effect in &effects {
14149 if let Err(e) = self.apply_effect_with_computed_writes(
14150 effect,
14151 Some(delta),
14152 None,
14153 Some(&mut computed_writes),
14154 ) {
14155 self.flush_computed_write_buffer(&mut computed_writes)?;
14156 return Err(e);
14157 }
14158 }
14159 applied = applied.saturating_add(1);
14160 }
14161 self.flush_computed_write_buffer(&mut computed_writes)?;
14162 for (vertex_id, result) in others {
14163 let effects = match self.plan_vertex_effects_with_computed_flush(
14164 vertex_id,
14165 result,
14166 Some(&inflight),
14167 &mut computed_writes,
14168 ) {
14169 Ok(effects) => effects,
14170 Err(e) => {
14171 self.flush_computed_write_buffer(&mut computed_writes)?;
14172 return Err(e);
14173 }
14174 };
14175 for effect in &effects {
14176 if let Err(e) = self.apply_effect_with_computed_writes(
14177 effect,
14178 Some(delta),
14179 None,
14180 Some(&mut computed_writes),
14181 ) {
14182 self.flush_computed_write_buffer(&mut computed_writes)?;
14183 return Err(e);
14184 }
14185 }
14186 applied = applied.saturating_add(1);
14187 }
14188 self.flush_computed_write_buffer(&mut computed_writes)?;
14189 }
14190 Err(e) => {
14191 self.flush_computed_write_buffer(&mut computed_writes)?;
14192 return Err(e);
14193 }
14194 }
14195 }
14196
14197 Ok(applied)
14198 }
14199
14200 fn evaluate_layer_parallel_cancellable_effects(
14202 &mut self,
14203 layer: &super::scheduler::Layer,
14204 cancel_flag: &AtomicBool,
14205 ) -> Result<usize, ExcelError> {
14206 use rayon::prelude::*;
14207
14208 let thread_pool = self.thread_pool.as_ref().unwrap().clone();
14209
14210 if cancel_flag.load(Ordering::Relaxed) {
14211 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
14212 .with_message("Parallel evaluation cancelled before starting".to_string()));
14213 }
14214
14215 let mut phase1: Vec<VertexId> = Vec::new();
14216 let mut phase2: Vec<VertexId> = Vec::new();
14217 for &vid in &layer.vertices {
14218 if self.graph.get_range_dependencies(vid).is_some() {
14219 phase2.push(vid);
14220 } else {
14221 phase1.push(vid);
14222 }
14223 }
14224
14225 let inflight: rustc_hash::FxHashSet<VertexId> = layer.vertices.iter().copied().collect();
14226 let mut applied = 0usize;
14227
14228 for group in [&phase1[..], &phase2[..]] {
14229 if group.is_empty() {
14230 continue;
14231 }
14232 let mut computed_writes = ComputedWriteBuffer::default();
14233
14234 let results: Result<Vec<(VertexId, LiteralValue)>, ExcelError> =
14235 thread_pool.install(|| {
14236 group
14237 .par_iter()
14238 .map(|&vertex_id| {
14239 if cancel_flag.load(Ordering::Relaxed) {
14240 return Err(ExcelError::new(ExcelErrorKind::Cancelled)
14241 .with_message(
14242 "Parallel evaluation cancelled during execution"
14243 .to_string(),
14244 ));
14245 }
14246 match self.evaluate_vertex_immutable(vertex_id) {
14247 Ok(v) => Ok((vertex_id, v)),
14248 Err(e) => Ok((vertex_id, LiteralValue::Error(e))),
14249 }
14250 })
14251 .collect()
14252 });
14253
14254 match results {
14255 Ok(vertex_results) => {
14256 let mut arrays: Vec<(VertexId, LiteralValue)> = Vec::new();
14257 let mut others: Vec<(VertexId, LiteralValue)> = Vec::new();
14258 for (vertex_id, result) in vertex_results {
14259 if matches!(result, LiteralValue::Array(_)) {
14260 arrays.push((vertex_id, result));
14261 } else {
14262 others.push((vertex_id, result));
14263 }
14264 }
14265 for (vertex_id, result) in arrays {
14266 let effects = match self.plan_vertex_effects_with_computed_flush(
14267 vertex_id,
14268 result,
14269 Some(&inflight),
14270 &mut computed_writes,
14271 ) {
14272 Ok(effects) => effects,
14273 Err(e) => {
14274 self.flush_computed_write_buffer(&mut computed_writes)?;
14275 return Err(e);
14276 }
14277 };
14278 for effect in &effects {
14279 if let Err(e) = self.apply_effect_with_computed_writes(
14280 effect,
14281 None,
14282 None,
14283 Some(&mut computed_writes),
14284 ) {
14285 self.flush_computed_write_buffer(&mut computed_writes)?;
14286 return Err(e);
14287 }
14288 }
14289 applied = applied.saturating_add(1);
14290 }
14291 self.flush_computed_write_buffer(&mut computed_writes)?;
14292 for (vertex_id, result) in others {
14293 let effects = match self.plan_vertex_effects_with_computed_flush(
14294 vertex_id,
14295 result,
14296 Some(&inflight),
14297 &mut computed_writes,
14298 ) {
14299 Ok(effects) => effects,
14300 Err(e) => {
14301 self.flush_computed_write_buffer(&mut computed_writes)?;
14302 return Err(e);
14303 }
14304 };
14305 for effect in &effects {
14306 if let Err(e) = self.apply_effect_with_computed_writes(
14307 effect,
14308 None,
14309 None,
14310 Some(&mut computed_writes),
14311 ) {
14312 self.flush_computed_write_buffer(&mut computed_writes)?;
14313 return Err(e);
14314 }
14315 }
14316 applied = applied.saturating_add(1);
14317 }
14318 self.flush_computed_write_buffer(&mut computed_writes)?;
14319 }
14320 Err(e) => {
14321 self.flush_computed_write_buffer(&mut computed_writes)?;
14322 return Err(e);
14323 }
14324 }
14325 }
14326
14327 Ok(applied)
14328 }
14329
14330 pub fn evaluate_all_logged(&mut self, log: &mut ChangeLog) -> Result<EvalResult, ExcelError> {
14337 self.begin_evaluation_request();
14338 let _source_cache = self.source_cache_session();
14339 self.validate_deterministic_mode()?;
14340 if self.config.defer_graph_building {
14341 self.build_graph_all()?;
14342 }
14343 if self.graph.formula_authority().active_span_count() > 0 {
14344 return self.evaluate_authoritative_formula_plane_all();
14345 }
14346 self.reset_virtual_dep_telemetry_if_disabled();
14347 let start = crate::instant::FzInstant::now();
14348 let mut computed_vertices = 0;
14349 let mut cycle_errors = 0;
14350
14351 let mut replan_iterations = 0;
14352 const MAX_REPLAN: usize = 5;
14353 let mut telemetry = self
14354 .config
14355 .enable_virtual_dep_telemetry
14356 .then(|| self.start_virtual_dep_telemetry());
14357
14358 log.begin_compound(format!("evaluate_all(epoch={})", self.recalc_epoch));
14359
14360 loop {
14361 let to_evaluate = self.graph.get_evaluation_vertices();
14362 if to_evaluate.is_empty() {
14363 if let Some(t) = telemetry.as_mut()
14364 && t.bailout_reason.is_none()
14365 {
14366 t.bailout_reason = Some("no_work");
14367 }
14368 break;
14369 }
14370
14371 let (schedule, old_vdeps, meta) = self.create_evaluation_schedule(&to_evaluate)?;
14372 if let Some(t) = telemetry.as_mut() {
14373 Self::accumulate_schedule_meta(t, &meta);
14374 }
14375
14376 for &unit in &schedule.units {
14379 match unit {
14380 ScheduleUnit::Cycle(i) => {
14381 if self.handle_cycle_unit(schedule.unit_cycle(i), None, None, None)? > 0 {
14392 cycle_errors += 1;
14393 }
14394 }
14395 ScheduleUnit::Layer(i) => {
14396 computed_vertices +=
14397 self.evaluate_layer_logged(schedule.unit_layer(i), log)?;
14398 }
14399 }
14400 }
14401
14402 let changed_vertices = self.changed_virtual_dep_vertices(&to_evaluate, &old_vdeps);
14403 if let Some(t) = telemetry.as_mut() {
14404 t.changed_vdeps_total += changed_vertices.len();
14405 }
14406 self.graph.clear_dirty_flags(&to_evaluate);
14407 for v in &changed_vertices {
14408 self.graph.set_dirty(*v, true);
14409 }
14410
14411 if changed_vertices.is_empty() {
14412 if let Some(t) = telemetry.as_mut() {
14413 t.bailout_reason = Some("converged");
14414 }
14415 break;
14416 }
14417 if replan_iterations >= MAX_REPLAN {
14418 if let Some(t) = telemetry.as_mut() {
14419 t.bailout_reason = Some("max_replan");
14420 }
14421 break;
14422 }
14423 replan_iterations += 1;
14424 }
14425
14426 if let Some(mut t) = telemetry {
14427 t.replan_iterations = replan_iterations;
14428 self.last_virtual_dep_telemetry = t;
14429 }
14430
14431 log.end_compound();
14432
14433 self.redirty_for_next_recalc();
14434 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
14435
14436 Ok(EvalResult {
14437 computed_vertices,
14438 cycle_errors,
14439 elapsed: start.elapsed(),
14440 })
14441 }
14442
14443 fn evaluate_layer_logged(
14445 &mut self,
14446 layer: &super::scheduler::Layer,
14447 log: &mut ChangeLog,
14448 ) -> Result<usize, ExcelError> {
14449 let mut computed_writes = ComputedWriteBuffer::default();
14450 for &vertex_id in &layer.vertices {
14451 self.flush_before_range_dependent_vertex(vertex_id, &mut computed_writes)?;
14452 let value = match self.evaluate_vertex_immutable(vertex_id) {
14453 Ok(v) => v,
14454 Err(e) => LiteralValue::Error(e),
14455 };
14456 let effects = match self.plan_vertex_effects_with_computed_flush(
14457 vertex_id,
14458 value,
14459 None,
14460 &mut computed_writes,
14461 ) {
14462 Ok(effects) => effects,
14463 Err(e) => {
14464 self.flush_computed_write_buffer(&mut computed_writes)?;
14465 return Err(e);
14466 }
14467 };
14468 for effect in &effects {
14469 if let Err(e) = self.apply_effect_with_computed_writes(
14470 effect,
14471 None,
14472 Some(log),
14473 Some(&mut computed_writes),
14474 ) {
14475 self.flush_computed_write_buffer(&mut computed_writes)?;
14476 return Err(e);
14477 }
14478 }
14479 }
14480 self.flush_computed_write_buffer(&mut computed_writes)?;
14481 Ok(layer.vertices.len())
14482 }
14483}