1use super::*;
3use crate::engine::resumable::*;
4
5pub(in crate::engine) struct HeldRecalcSchedule {
6 full: Walk,
7 priority: Option<Walk>,
8 candidates: Vec<VertexId>,
9 old_vdeps: FxHashMap<VertexId, Vec<VertexId>>,
10 aux_retained_bytes: u64,
11}
12struct Walk {
13 schedule: EvaluationSchedule,
14 unit: usize,
15 layer_offset: usize,
17 completed: usize,
18 total: usize,
19 retained_bytes: u64,
20}
21impl Walk {
22 fn new(schedule: EvaluationSchedule) -> Self {
23 let total = schedule
24 .layers
25 .iter()
26 .map(|l| l.vertices.len())
27 .sum::<usize>()
28 + schedule.cycles.iter().map(Vec::len).sum::<usize>();
29 let retained_bytes = match &schedule {
30 EvaluationSchedule::Shared(_) => 0,
31 EvaluationSchedule::Owned(s) => {
32 (s.units.capacity() * std::mem::size_of::<ScheduleUnit>()
33 + s.layers.capacity() * std::mem::size_of::<crate::engine::scheduler::Layer>()
34 + s.cycles.capacity() * std::mem::size_of::<Vec<VertexId>>()
35 + s.cycles
36 .iter()
37 .map(|c| c.capacity() * std::mem::size_of::<VertexId>())
38 .sum::<usize>()
39 + s.layers
40 .iter()
41 .map(|l| {
42 l.vertices.capacity() * std::mem::size_of::<VertexId>()
43 + l.runs.capacity()
44 * std::mem::size_of::<crate::engine::scheduler::LayerRun>()
45 })
46 .sum::<usize>()) as u64
47 }
48 };
49 Self {
50 schedule,
51 unit: 0,
52 layer_offset: 0,
53 completed: 0,
54 total,
55 retained_bytes,
56 }
57 }
58 fn done(&self) -> bool {
59 self.unit == self.schedule.units.len()
60 }
61}
62impl<R: EvaluationContext> Engine<R> {
63 pub fn begin_recalc(&mut self, options: RecalcJobOptions) -> Result<(), RecalcJobError> {
66 if self.is_poisoned() {
67 return Err(RecalcJobError::Poisoned);
68 }
69 if self.has_active_recalc_job() || self.resumable.is_some() {
70 return Err(RecalcJobError::AlreadyPending {
71 explicit: self.has_active_recalc_job(),
72 });
73 }
74 self.recalc_options = Some(options);
75 Ok(())
76 }
77 pub(super) fn active_recalc_job_error() -> ExcelError {
78 ExcelError::new(ExcelErrorKind::Value)
79 .with_message(
80 "recalculation job is active; use step_recalc, prioritize_recalc, or cancel_recalc",
81 )
82 .with_extra(formualizer_common::ExcelErrorExtra::RecalcJobActive)
83 }
84 pub fn has_active_recalc_job(&self) -> bool {
87 self.recalc_options.is_some()
88 }
89
90 pub fn prioritize_recalc(
92 &mut self,
93 targets: &[crate::engine::EvaluationTarget],
94 ) -> Result<(), RecalcJobError> {
95 if self.is_poisoned() {
96 return Err(RecalcJobError::Poisoned);
97 }
98 let options = self
99 .recalc_options
100 .as_mut()
101 .ok_or(RecalcJobError::NoActiveJob)?;
102 options.initial_priority = targets.to_vec();
103 if let Some(held) = self.resumable.as_mut().and_then(|s| s.held.as_mut()) {
104 held.priority = None;
105 }
106 Ok(())
107 }
108
109 pub fn cancel_recalc(&mut self) -> Option<StepReport> {
112 if self.is_poisoned() {
113 return None;
114 }
115 self.recalc_options.take()?;
116 let progress = self.recalc_progress();
117 self.resumable = None;
118 self.redirty_for_next_recalc();
119 self.freshness_begin_request();
120 Some(StepReport {
121 phase: StepPhase::Cancelled,
122 progress,
123 step_computed_vertices: 0,
124 step_elapsed: std::time::Duration::ZERO,
125 changed: Default::default(),
126 longest_unit: None,
127 result: None,
128 })
129 }
130 pub fn step_recalc(&mut self, stop: EvaluationStop) -> Result<StepReport, RecalcJobError> {
137 if self.is_poisoned() {
138 return Err(RecalcJobError::Poisoned);
139 }
140 if !self.has_active_recalc_job() {
141 return Err(RecalcJobError::NoActiveJob);
142 }
143 let result = self.drive_recalc_step(stop);
144 if result.as_ref().map_or(true, |r| {
145 matches!(r.phase, StepPhase::Finished | StepPhase::Cancelled)
146 }) {
147 self.recalc_options = None;
148 if result
149 .as_ref()
150 .is_ok_and(|r| r.phase == StepPhase::Cancelled)
151 {
152 self.resumable = None;
153 self.redirty_for_next_recalc();
154 self.freshness_begin_request();
155 }
156 }
157 result.map_err(Into::into)
158 }
159
160 pub fn recalc_with(
172 &mut self,
173 options: RecalcJobOptions,
174 stop_per_step: EvaluationStop,
175 mut on_step: impl FnMut(&mut Self, &StepReport) -> std::ops::ControlFlow<()>,
176 ) -> Result<RecalcOutcome, RecalcJobError> {
177 if self.is_poisoned() {
178 return Err(RecalcJobError::Poisoned);
179 }
180 self.begin_recalc(options)?;
181 loop {
182 let report = self.step_recalc(stop_per_step.clone())?;
183 let flow = on_step(self, &report);
184 if let Some(result) = report.result {
185 return Ok(RecalcOutcome::Finished(result));
186 }
187 if report.phase == StepPhase::Cancelled || !self.has_active_recalc_job() {
188 return Ok(RecalcOutcome::Cancelled);
189 }
190 if flow.is_break() {
191 return Ok(RecalcOutcome::Paused);
192 }
193 }
194 }
195
196 pub(super) fn drive_recalc_step(
197 &mut self,
198 stop: EvaluationStop,
199 ) -> Result<StepReport, ExcelError> {
200 self.ensure_not_poisoned()?;
201 let _unwind_poison = crate::engine::poison::UnwindPoisonGuard(self.poisoned.clone());
202 self.resumable_call = true;
203 self.active_cancel_flag = stop.cancel.clone();
204 let result = self.observe_evaluation_resource_request(
205 EvaluationRequestKind::FullCancellable,
206 |engine| engine.run_recalc_step(stop),
207 );
208 self.active_cancel_flag = None;
209 self.resumable_call = false;
210 if result.is_err() {
211 self.resumable = None;
212 self.redirty_for_next_recalc();
213 }
214 result
215 }
216
217 fn recalc_progress(&self) -> StepProgress {
218 let Some(s) = &self.resumable else {
219 return StepProgress::default();
220 };
221 let held = s.held.as_ref();
222 StepProgress {
223 pass: s.pass.max(1),
224 completed_in_pass: s.pass_completed,
225 scheduled_in_pass: s.pass_scheduled,
226 cumulative_computed_vertices: s.computed_vertices,
227 total_exact: s.pass_exact
228 && !self.graph.has_spill_anchors()
229 && !self.has_staged_formulas(),
230 staged_formulas_remaining: self.staged_formula_count(),
231 priority_pending: s.priority_deferred
232 || held.is_some_and(|h| h.priority.as_ref().is_some_and(|p| !p.done()))
233 || self
234 .recalc_options
235 .as_ref()
236 .is_some_and(|o| !o.initial_priority.is_empty()),
237 }
238 }
239
240 fn run_recalc_step(&mut self, stop: EvaluationStop) -> Result<StepReport, ExcelError> {
241 debug_assert!(!self.graph.deferred_dirty_active());
242 let start = crate::instant::FzInstant::now();
243 let stop_at = stop.soft_deadline.and_then(|d| start.checked_add(d));
244 let explicit = self.has_active_recalc_job();
245 let function_epoch = crate::function_registry::semantic_epoch();
246 let provider_revision = self.resolver.planning_semantic_revision();
247 self.observe_function_semantic_epoch()?;
248 self.lookup_index_cache.reset_counters();
249 let _source_cache = self.source_cache_session();
250 self.validate_deterministic_mode()?;
251 self.account_recalc_retained()?;
252 let initial = self.resumable.is_none();
253 if initial {
254 self.begin_evaluation_request();
255 let mut state = LogicalRecalculation::default();
256 let mut retained: FxHashMap<u64, Vec<VertexId>> = FxHashMap::default();
257 for (&vertex, &scc) in &self.retained_scc_members {
258 retained.entry(scc).or_default().push(vertex);
259 }
260 for mut key in retained.into_values() {
261 key.sort_unstable();
262 state
263 .cycle_seeds
264 .insert(key.clone(), self.capture_original_cycle_seeds(&key));
265 state.original_cycles.insert(key);
266 }
267 state.refresh_seed_retained_bytes();
268 self.resumable = Some(state);
269 }
270 let mutation_revision = self.inspection_mutation_revision();
271 let state = self.resumable.as_ref().unwrap();
272 let semantics_changed = !initial
273 && (state.function_epoch != function_epoch
274 || state.provider_revision != provider_revision);
275 let mutated = !initial
276 && (state.mutation_revision != mutation_revision
277 || state.source_mutation_generation != self.source_mutation_generation);
278 let invalidated = semantics_changed
279 || mutated
280 || (!initial
281 && (state.topology_epoch != self.topology_epoch
282 || state.authority_revision != self.schedule_cache_authority_revision()));
283 if semantics_changed || mutated {
284 let vertices: Vec<_> = state
285 .installed
286 .iter()
287 .filter(|&v| {
288 self.graph.is_live_formula_vertex(v)
289 && (semantics_changed
290 || self.graph.is_volatile(v)
291 || self.graph.is_dynamic(v))
292 })
293 .collect();
294 self.graph.mark_dirty_many(&vertices);
295 }
296 if invalidated {
297 self.refresh_resolution_snapshots();
300 let state = self.resumable.as_mut().unwrap();
301 state.held = None;
302 if let Some(options) = &mut self.recalc_options
303 && options.initial_priority.is_empty()
304 {
305 options.initial_priority = state.priority_targets.clone();
306 }
307 }
308 let mut computed = 0;
309 let mut cycle_errors = 0;
310 let mut units = 0;
311 let mut phase = StepPhase::Evaluating;
312 let mut longest: Option<SlowUnitReport> = None;
313 let collect = self
314 .recalc_options
315 .as_ref()
316 .is_some_and(|o| o.collect_deltas);
317 let mut delta = DeltaCollector::new(if collect {
318 DeltaMode::Cells
319 } else {
320 DeltaMode::Off
321 });
322 let cancelled =
323 |stop: &EvaluationStop| stop.cancel.as_ref().is_some_and(|t| t.is_cancelled());
324 let expired = || stop_at.is_some_and(|t| crate::instant::FzInstant::now() >= t);
325 if explicit && cancelled(&stop) {
326 phase = StepPhase::Cancelled;
327 }
328 if phase != StepPhase::Cancelled
329 && self.config.defer_graph_building
330 && self.has_staged_formulas()
331 {
332 let t = crate::instant::FzInstant::now();
333 let saved_cancel = self.active_cancel_flag.take();
336 let built = self.build_graph_all();
337 self.active_cancel_flag = saved_cancel;
338 built?;
339 self.refresh_resolution_snapshots();
342 Self::note_slow_unit(
343 &mut longest,
344 SlowUnitKind::GraphBuild,
345 &[],
346 t.elapsed(),
347 &self.graph,
348 );
349 self.resumable.as_mut().unwrap().held = None;
350 if explicit && cancelled(&stop) {
351 phase = StepPhase::Cancelled;
352 } else if explicit && expired() {
353 phase = StepPhase::BuildingGraph;
354 }
355 }
356 if phase != StepPhase::Cancelled {
357 self.require_unified_authority()?;
358 }
359 if phase == StepPhase::Evaluating {
360 loop {
361 if self.resumable.as_ref().unwrap().held.is_none() {
362 let candidates: Vec<_> = self
363 .graph
364 .get_evaluation_vertices()
365 .into_iter()
366 .filter(|&v| {
367 initial && self.resumable.as_ref().unwrap().pass == 0
368 || self.graph.is_dirty(v)
369 })
370 .collect();
371 if candidates.is_empty() {
372 phase = StepPhase::Finished;
373 break;
374 }
375 if cancelled(&stop) && self.resumable.as_ref().unwrap().original_cycles_recorded
376 {
377 phase = StepPhase::Cancelled;
378 break;
379 }
380 let saved = if cancelled(&stop) {
383 self.active_cancel_flag.take()
384 } else {
385 None
386 };
387 let t = crate::instant::FzInstant::now();
388 let planned = self.create_evaluation_schedule(&candidates);
389 if saved.is_some() {
390 self.active_cancel_flag = saved;
391 }
392 let (schedule, old_vdeps, _) = planned?;
393 self.record_original_cycle_seeds(&schedule);
394 let exact = self.recalc_total_exact(&candidates);
397 Self::note_slow_unit(
398 &mut longest,
399 SlowUnitKind::ScheduleBuild,
400 &[],
401 t.elapsed(),
402 &self.graph,
403 );
404 let authority_revision = self.schedule_cache_authority_revision();
405 let aux_retained_bytes = (candidates.capacity()
406 * std::mem::size_of::<VertexId>()
407 + old_vdeps.capacity() * std::mem::size_of::<(VertexId, Vec<VertexId>)>()
408 + old_vdeps
409 .values()
410 .map(|v| v.capacity() * std::mem::size_of::<VertexId>())
411 .sum::<usize>()) as u64;
412 let full = Walk::new(schedule);
413 let s = self.resumable.as_mut().unwrap();
414 s.pass += 1;
415 s.pass_completed = 0;
416 s.pass_scheduled = full.total;
417 s.pass_exact = exact;
418 s.authority_revision = authority_revision;
419 s.held = Some(HeldRecalcSchedule {
420 full,
421 priority: None,
422 candidates,
423 old_vdeps,
424 aux_retained_bytes,
425 });
426 self.account_recalc_retained()?;
427 if cancelled(&stop) {
428 phase = StepPhase::Cancelled;
429 break;
430 }
431 if explicit && expired() {
432 phase = StepPhase::Scheduling;
433 break;
434 }
435 }
436 if cancelled(&stop) {
437 phase = StepPhase::Cancelled;
438 break;
439 }
440 let mut held = self.resumable.as_mut().unwrap().held.take().unwrap();
441 if let Some(options) = self.recalc_options.as_mut()
442 && !options.initial_priority.is_empty()
443 {
444 let targets = std::mem::take(&mut options.initial_priority);
445 self.resumable.as_mut().unwrap().priority_targets = targets.clone();
446 let t = crate::instant::FzInstant::now();
447 held.priority = self.build_recalc_priority(&held.full.schedule, &targets)?;
448 self.resumable.as_mut().unwrap().priority_deferred = held.priority.is_none();
449 Self::note_slow_unit(
450 &mut longest,
451 SlowUnitKind::ScheduleBuild,
452 &[],
453 t.elapsed(),
454 &self.graph,
455 );
456 if explicit && expired() {
457 self.resumable.as_mut().unwrap().held = Some(held);
458 phase = StepPhase::Scheduling;
459 break;
460 }
461 }
462 self.begin_pass(&held.full.schedule);
463 let mut completed_cycles = Vec::new();
464 let mut barrier = false;
465 let mut stopped = false;
466 loop {
467 let priority = held.priority.as_ref().is_some_and(|p| !p.done());
468 let walk = if priority {
469 held.priority.as_mut().unwrap()
470 } else {
471 &mut held.full
472 };
473 if walk.done() {
474 break;
475 }
476 let index = walk.unit;
477 match walk.schedule.units[index] {
478 ScheduleUnit::Cycle(i) => {
479 let vertices = walk.schedule.unit_cycle(i);
480 let skip = vertices.iter().all(|v| {
481 self.resumable.as_ref().unwrap().installed.contains(v)
482 && !self.graph.is_dirty(*v)
483 });
484 let t = crate::instant::FzInstant::now();
485 if !skip {
486 match self.handle_cycle_unit(
487 vertices,
488 collect.then_some(&mut delta),
489 None,
490 stop.cancel.as_ref().map(|t| &**t.as_flag()),
491 ) {
492 Ok(n) => {
493 computed += vertices.len();
494 cycle_errors += usize::from(n > 0);
495 completed_cycles.extend_from_slice(vertices);
496 }
497 Err(e) if e.kind == ExcelErrorKind::Cancelled => {
498 self.restore_cancelled_recalc_cycle(vertices);
499 phase = StepPhase::Cancelled;
500 stopped = true;
501 break;
502 }
503 Err(e) => return Err(e),
504 }
505 Self::note_slow_unit(
506 &mut longest,
507 SlowUnitKind::Cycle,
508 vertices,
509 t.elapsed(),
510 &self.graph,
511 );
512 units += 1;
513 }
514 walk.completed += vertices.len();
515 walk.unit += 1;
516 }
517 ScheduleUnit::Layer(i) => {
518 let layer = walk.schedule.unit_layer(i);
519 let lo = walk.layer_offset;
520 let run_idx = layer.runs.partition_point(|r| (r.start as usize) < lo);
521 let run = layer.runs.get(run_idx).filter(|r| r.start as usize == lo);
522 let hi = run.map_or(lo + 1, |r| (r.start + r.len) as usize);
523 let vertices = &layer.vertices[lo..hi];
524 let skip = vertices.iter().all(|v| {
525 self.resumable.as_ref().unwrap().installed.contains(v)
526 && !self.graph.is_dirty(*v)
527 });
528 if !skip {
529 let part = layer.sub_layer(lo, hi);
530 self.resource_checkpoint(part.vertices.len() as u64)?;
531 self.resumable
532 .as_mut()
533 .unwrap()
534 .forget_dissolved_new_cycles(&part.vertices);
535 let t = crate::instant::FzInstant::now();
536 let run_delta = collect
541 && run.is_some()
542 && !self.graph.has_spill_anchors()
543 && (self.resumable.as_ref().unwrap().pass_exact
544 || self
545 .graph
546 .get_formula(vertices[0])
547 .is_some_and(|ast| Self::recalc_scalar_ast(&ast)));
548 match self.evaluate_layer_units_until(
551 &part,
552 (collect && !run_delta).then_some(&mut delta),
553 None,
554 None,
555 buffer_layer_writes(&part),
556 None,
557 ) {
558 Ok(n) => computed += n,
559 Err(e) if e.kind == ExcelErrorKind::Cancelled => {
560 phase = StepPhase::Cancelled;
561 stopped = true;
562 break;
563 }
564 Err(e) => return Err(e),
565 }
566 if run_delta {
567 let first = self.graph.get_cell_ref(vertices[0]).unwrap();
568 let last =
569 self.graph.get_cell_ref(*vertices.last().unwrap()).unwrap();
570 delta.record_region(
571 first.sheet_id,
572 first.coord.row(),
573 first.coord.col(),
574 last.coord.row(),
575 last.coord.col(),
576 );
577 if self.graph.has_spill_anchors() {
581 for &vertex in vertices {
582 if let Some((a, b)) =
583 self.graph.spill_extent_for_anchor(vertex)
584 {
585 delta.record_region(
586 a.sheet_id,
587 a.coord.row(),
588 a.coord.col(),
589 b.coord.row(),
590 b.coord.col(),
591 );
592 }
593 }
594 }
595 }
596 Self::note_slow_unit(
597 &mut longest,
598 if run.is_some() {
599 SlowUnitKind::FamilyRun
600 } else {
601 SlowUnitKind::SingleCell
602 },
603 vertices,
604 t.elapsed(),
605 &self.graph,
606 );
607 units += 1;
608 }
609 walk.completed += hi - lo;
610 walk.layer_offset = hi;
611 if hi == layer.vertices.len() {
612 walk.layer_offset = 0;
613 walk.unit += 1;
614 }
615 }
616 }
617 if self.stop_after_unit(&walk.schedule, index) {
618 barrier = true;
619 break;
620 }
621 if self.resumable_should_stop(units, stop_at) {
622 stopped = true;
623 break;
624 }
625 }
626 let changed = self.changed_virtual_dep_vertices(&held.candidates, &held.old_vdeps);
627 self.resource_checkpoint(0)?;
628 let mut installed = std::mem::take(&mut self.resumable.as_mut().unwrap().installed);
629 let pending =
630 self.freshness_finish_interrupted_pass(&completed_cycles, &mut installed);
631 self.resumable.as_mut().unwrap().installed = installed;
632 let (pending, fr5_replan) = pending?;
633 barrier |= fr5_replan;
634 for v in changed {
635 self.graph.set_dirty(v, true);
636 }
637 let full_done = held.full.done();
638 self.resumable.as_mut().unwrap().pass_completed = held.full.completed;
639 self.resumable.as_mut().unwrap().held = Some(held);
640 if !pending && !self.graph.has_dirty_evaluation_vertices() {
641 phase = StepPhase::Finished;
642 break;
643 }
644 if phase == StepPhase::Cancelled {
645 break;
646 }
647 if barrier || full_done {
648 let state = self.resumable.as_mut().unwrap();
649 if state.replans >= 5 {
650 return Err(self.replan_exhausted_error(5, "dynamic dependency evaluation"));
651 }
652 state.replans += 1;
653 state.held = None;
654 if let Some(options) = &mut self.recalc_options {
655 options.initial_priority = state.priority_targets.clone();
656 }
657 }
658 if stopped {
659 break;
660 }
661 }
662 }
663 self.resource_checkpoint(0)?;
664 let authority_revision = self.schedule_cache_authority_revision();
665 let mutation_revision = self.inspection_mutation_revision();
666 let s = self.resumable.as_mut().unwrap();
667 s.function_epoch = function_epoch;
668 s.provider_revision = provider_revision;
669 s.mutation_revision = mutation_revision;
670 s.source_mutation_generation = self.source_mutation_generation;
671 s.topology_epoch = self.topology_epoch;
672 if s.authority_revision != authority_revision && s.held.is_some() {
673 s.held = None;
674 if let Some(options) = &mut self.recalc_options
675 && options.initial_priority.is_empty()
676 {
677 options.initial_priority = s.priority_targets.clone();
678 }
679 }
680 s.authority_revision = authority_revision;
681 if phase == StepPhase::Finished {
682 s.priority_deferred = false;
683 s.pass_completed = s.pass_scheduled;
684 }
685 s.computed_vertices += computed;
686 s.cycle_errors += cycle_errors;
687 s.elapsed += start.elapsed();
688 let progress = self.recalc_progress();
689 self.account_recalc_retained()?;
690 let changed = Self::finish_step_delta(
691 delta,
692 self.recalc_options
693 .as_ref()
694 .map_or(1024, |o| o.max_delta_records),
695 );
696 let result = if phase == StepPhase::Finished {
697 let state = self.resumable.take().unwrap();
698 self.redirty_for_next_recalc();
699 self.recalc_epoch = self.recalc_epoch.wrapping_add(1);
700 Some(EvalResult {
701 computed_vertices: state.computed_vertices,
702 cycle_errors: state.cycle_errors,
703 elapsed: state.elapsed,
704 })
705 } else {
706 None
707 };
708 Ok(StepReport {
709 phase,
710 progress,
711 step_computed_vertices: computed,
712 step_elapsed: start.elapsed(),
713 changed,
714 longest_unit: longest,
715 result,
716 })
717 }
718
719 fn restore_cancelled_recalc_cycle(&mut self, vertices: &[VertexId]) {
720 let mut key = vertices.to_vec();
721 key.sort_unstable();
722 let seeds = self
723 .resumable
724 .as_ref()
725 .and_then(|s| s.cycle_seeds.get(&key))
726 .cloned()
727 .unwrap_or_default();
728 for (vertex, value) in seeds {
729 self.graph.update_vertex_value_ref(vertex, &value);
730 self.mirror_vertex_value_to_overlay(vertex, &value);
731 self.graph.set_dirty(vertex, true);
732 }
733 }
734
735 fn account_recalc_retained(&mut self) -> Result<(), ExcelError> {
736 let bytes = self.resumable.as_ref().map_or(0, |s| {
737 let held = s.held.as_ref().map_or(0, |h| {
738 h.full.retained_bytes
739 + h.priority.as_ref().map_or(0, |p| p.retained_bytes)
740 + h.aux_retained_bytes
741 });
742 held + s.installed.heap_bytes() as u64
743 + s.new_cycle_members.heap_bytes() as u64
744 + s.seed_retained_bytes
745 });
746 if let Some(ledger) = self.active_resource_ledger.as_mut() {
747 ledger
748 .account_recalc_retained(bytes)
749 .map_err(crate::engine::ResourceLedgerError::into_excel_error)?;
750 }
751 Ok(())
752 }
753
754 fn recalc_total_exact(&self, candidates: &[VertexId]) -> bool {
755 if self.graph.has_spill_anchors() {
756 return false;
757 }
758 let mut scalar_templates = FxHashSet::default();
759 for &vertex in candidates {
760 if self.graph.is_dynamic(vertex) {
761 return false;
762 }
763 let Some(view) = self.graph.formula_view(vertex) else {
764 return false;
765 };
766 if scalar_templates.contains(&view.template) {
767 continue;
768 }
769 if scalar_templates.len() >= 1024
772 || !self
773 .graph
774 .get_formula(vertex)
775 .is_some_and(|ast| Self::recalc_scalar_ast(&ast))
776 {
777 return false;
778 }
779 scalar_templates.insert(view.template);
780 }
781 true
782 }
783
784 fn recalc_scalar_ast(ast: &ASTNode) -> bool {
785 match &ast.node_type {
786 ASTNodeType::Literal(v) => !matches!(v, LiteralValue::Array(_)),
787 ASTNodeType::Reference { reference, .. } => {
788 matches!(reference, ReferenceType::Cell { .. })
789 }
790 ASTNodeType::UnaryOp { expr, .. } => Self::recalc_scalar_ast(expr),
791 ASTNodeType::BinaryOp { left, right, .. } => {
792 Self::recalc_scalar_ast(left) && Self::recalc_scalar_ast(right)
793 }
794 _ => false,
795 }
796 }
797
798 fn note_slow_unit(
799 longest: &mut Option<SlowUnitReport>,
800 kind: SlowUnitKind,
801 vertices: &[VertexId],
802 elapsed: std::time::Duration,
803 graph: &DependencyGraph,
804 ) {
805 if longest.as_ref().is_none_or(|r| elapsed > r.elapsed) {
806 let representative_cell = vertices
807 .first()
808 .and_then(|v| graph.get_cell_ref(*v))
809 .and_then(|c| {
810 formualizer_common::PackedSheetCell::try_new(
811 c.sheet_id,
812 c.coord.row(),
813 c.coord.col(),
814 )
815 });
816 *longest = Some(SlowUnitReport {
817 kind,
818 representative_cell,
819 vertex_count: vertices.len(),
820 elapsed,
821 });
822 }
823 }
824 fn finish_step_delta(delta: DeltaCollector, cap: usize) -> StepDelta {
825 let mut delta = delta.finish_target();
826 let mut overflow_sheets = Vec::new();
827 if delta.records.len() > cap {
828 overflow_sheets.extend(delta.records.iter().map(|r| r.sheet_id()));
829 overflow_sheets.sort_unstable();
830 overflow_sheets.dedup();
831 delta.records.clear();
832 }
833 StepDelta {
834 delta,
835 overflow_sheets,
836 }
837 }
838
839 fn build_recalc_priority(
840 &mut self,
841 full: &crate::engine::scheduler::Schedule,
842 targets: &[crate::engine::EvaluationTarget],
843 ) -> Result<Option<Walk>, ExcelError> {
844 use crate::engine::target_preparation::TargetProducer;
845 let roots = self.resolve_target_producers(targets)?;
846 let roots: Vec<_> = roots
847 .into_iter()
848 .filter_map(|r| match r {
849 TargetProducer::Legacy(v) | TargetProducer::Symbol(v) => Some(v),
850 TargetProducer::ValueOnly(_) => None,
851 })
852 .collect();
853 let (mut vertices, _) = self.demand_subgraph(&roots)?;
854 loop {
858 let mut selected = crate::engine::idset::DenseIdSet::default();
859 selected.extend(vertices.iter().copied());
860 let mut expanded = false;
861 for layer in &full.layers {
862 for run in &layer.runs {
863 let members =
864 &layer.vertices[run.start as usize..(run.start + run.len) as usize];
865 if members.iter().any(|v| selected.contains(v)) {
866 for &v in members {
867 expanded |= selected.insert(v);
868 }
869 }
870 }
871 }
872 if selected.len() > 65_536 {
873 return Ok(None);
874 }
875 if !expanded {
876 return Ok(full
877 .restrict(&selected)
878 .map(|(s, _)| Walk::new(EvaluationSchedule::Owned(s))));
879 }
880 let seeds: Vec<_> = selected.iter().collect();
881 let (closure, _) = self.demand_subgraph(&seeds)?;
882 vertices = seeds;
883 vertices.extend(closure);
884 }
885 }
886
887 pub fn has_pending_in_rect(&self, sheet: SheetId, r0: u32, c0: u32, r1: u32, c1: u32) -> bool {
890 if r0 > r1 || c0 > c1 {
891 return false;
892 }
893 for row in r0..=r1 {
894 for col in c0..=c1 {
895 if self.pending_at(sheet, row, col) {
896 return true;
897 }
898 }
899 }
900 false
901 }
902 pub fn pending_cells_in_rect(
905 &self,
906 sheet: SheetId,
907 r0: u32,
908 c0: u32,
909 r1: u32,
910 c1: u32,
911 ) -> crate::engine::TargetEvalDelta {
912 let mut delta = DeltaCollector::new(DeltaMode::Cells);
913 if r0 <= r1 && c0 <= c1 {
914 for row in r0..=r1 {
915 for col in c0..=c1 {
916 if self.pending_at(sheet, row, col) {
917 delta.record_cell(sheet, row, col);
918 }
919 }
920 }
921 }
922 delta.finish_target()
923 }
924 fn pending_at(&self, sheet: SheetId, row: u32, col: u32) -> bool {
925 let cell = self
926 .graph
927 .make_cell_ref(self.graph.sheet_name(sheet), row + 1, col + 1);
928 self.graph
929 .get_vertex_id_for_address(&cell)
930 .is_some_and(|v| self.graph.is_live_formula_vertex(v) && self.graph.is_dirty(v))
931 || self
932 .get_staged_formula_text(self.graph.sheet_name(sheet), row + 1, col + 1)
933 .is_some()
934 }
935}