Commit f0d56ac
committed
refactor(calc): unify engine — delete legacy per-sheet cascade (-810 LOC)
Removes the per-sheet `recalculate_dependents` BFS cascade and the
legacy cross-sheet propagation maps. The workbook-wide unified graph
(WorkbookGraph + cell_purities + structural_targets) is now the single
source of truth for dependency tracking and propagation.
What goes away:
* `Spreadsheet::dependents` + `dependencies` fields (was per-sheet
dep maps); `Spreadsheet::recalculate_dependents` (BFS cascade);
`add_cell_dependencies` / `remove_cell_dependencies`;
`rebuild_dependencies`. `Spreadsheet::set_cell` / `clear_cell` /
`set_many` / `clear_many` are now pure writes (no propagation).
`recalculate_cell` merges into `refresh_cell_value`.
* `Workbook::cross_sheet_dependents` / `cross_sheet_dependencies`
legacy cross-sheet maps; `cells_with_qualified_refs` fast-path set;
`needs_workbook_context()` predicate that guarded the workbook-clone
optimization. The single-clone snapshot is now unconditional (cheap
next to the recalc that follows).
* `Workbook::propagate_cross_sheet_changes` / `*_batch` (full BFS
impls, ~200 LOC each); `propagate_active_cell`.
* Per-sheet cascade tests in spreadsheet.rs that asserted dependents
auto-update from `Spreadsheet::set_cell` — that contract no longer
holds at the Spreadsheet level; coverage shifted to workbook-level
and to the scenario framework.
What replaces it:
* Every workbook-level mutator (`set_cell_on_active`, `clear_cell_on_active`,
`write_cells_on_active`, `clear_cells_on_active`, structural row/col
insert/delete, `rename_sheet`, `set_name`, `remove_name`) writes the
cell(s), updates the unified graph via `register_cross_sheet_deps`,
marks dirty, and runs ONE `recalc_via_graph_result()`. Callers see
a fresh empty dirty set after each call.
* `register_cross_sheet_deps` simplified to just refresh unified-graph
edges + purity + structural_targets. The 80-line cross_sheet_* map
maintenance is gone.
* `rebuild_cross_sheet_deps` shrunk to a wipe + per-cell re-register
(was wiping legacy maps PLUS unified state).
* `would_create_cross_sheet_cycle` rewritten to consult the unified
graph's `transitive_dependents` instead of walking the legacy maps.
* `propagate_cell_change` (App-level) kept as a thin compatibility
helper for bespoke callers; just marks dirty + recalcs.
Iterative-calc semantics change:
Self-referencing formulas (=A1+1 in A1) used to get silently dropped
from the graph (`link` filtered self-edges) on the assumption that the
user-facing `would_create_circular_reference` check would reject them
at the door. With the legacy cascade gone, that filtering meant
self-cycles got a single eval against their pre-write value and never
reached iterative-calc.
Now: self-edges are RETAINED in the graph. `topo_levels_from_seeds`
counts them in in-degree (no `p != n` filter), so self-loops land in
the cyclic remainder. `iterative_calc_cyclic` runs the cycle up to
`iter_max`.
Non-convergence is now strict (per design decision): cycles that hit
iter_max get `#NUM!` written to every cyclic cell + DidNotConverge
in status_message. The previous behavior of writing the iter_max'th
iteration value was misleading — that "answer" is an artifact of the
iter_max setting, not a meaningful result. Legitimate fixed-point
iteration (=A1/2+1 → 2) still gets the converged value, no error.
Diffstat: +453 / -1263 = net -810 LOC. Six bookkeeping caches
(dependents, dependencies, cross_sheet_dependents, cross_sheet_dependencies,
cells_with_qualified_refs, graph) collapsed to two (graph, dirty).
Perf (cargo bench --bench calc_engine): deep_small 275µs → 92µs (~3x
faster on tiny workloads from removing the workbook-clone overhead);
deep_large 25.4ms → 24.3ms (unchanged on large — already dominated by
eval work); wide/fanout unchanged or slightly improved.
Tests: 588 lib (was 584, +4 net: replaced 8 legacy-cascade tests with
new contract tests covering divergent iterative calc → #NUM!,
convergent → fixed point, unified-recalc trigger discipline). All 12
PTY scenarios + 11 existing PTY suites green.
CLAUDE.md updated to describe the new "one engine, one graph" world.1 parent 7ca3a7e commit f0d56ac
10 files changed
Lines changed: 453 additions & 1263 deletions
File tree
- src
- application/state
- domain
- models
- services
- infrastructure
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
38 | 38 | | |
39 | 39 | | |
40 | 40 | | |
41 | | - | |
| 41 | + | |
42 | 42 | | |
43 | 43 | | |
44 | 44 | | |
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
53 | | - | |
| 53 | + | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| |||
85 | 85 | | |
86 | 86 | | |
87 | 87 | | |
88 | | - | |
| 88 | + | |
89 | 89 | | |
90 | | - | |
| 90 | + | |
91 | 91 | | |
92 | | - | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
93 | 99 | | |
94 | 100 | | |
95 | | - | |
96 | | - | |
| 101 | + | |
| 102 | + | |
97 | 103 | | |
98 | | - | |
| 104 | + | |
99 | 105 | | |
100 | | - | |
| 106 | + | |
101 | 107 | | |
102 | 108 | | |
103 | 109 | | |
104 | | - | |
105 | | - | |
106 | | - | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
107 | 115 | | |
108 | | - | |
| 116 | + | |
109 | 117 | | |
110 | 118 | | |
111 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
863 | 863 | | |
864 | 864 | | |
865 | 865 | | |
866 | | - | |
867 | | - | |
868 | | - | |
869 | | - | |
870 | | - | |
871 | | - | |
872 | | - | |
873 | | - | |
874 | | - | |
875 | | - | |
876 | | - | |
877 | | - | |
878 | | - | |
879 | | - | |
880 | | - | |
881 | | - | |
882 | | - | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
| 890 | + | |
| 891 | + | |
| 892 | + | |
| 893 | + | |
| 894 | + | |
| 895 | + | |
| 896 | + | |
| 897 | + | |
| 898 | + | |
| 899 | + | |
| 900 | + | |
| 901 | + | |
| 902 | + | |
| 903 | + | |
| 904 | + | |
| 905 | + | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
883 | 910 | | |
884 | 911 | | |
885 | 912 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
368 | 368 | | |
369 | 369 | | |
370 | 370 | | |
| 371 | + | |
| 372 | + | |
371 | 373 | | |
372 | 374 | | |
373 | 375 | | |
374 | 376 | | |
375 | | - | |
376 | 377 | | |
377 | 378 | | |
378 | 379 | | |
| |||
426 | 427 | | |
427 | 428 | | |
428 | 429 | | |
429 | | - | |
430 | 430 | | |
431 | 431 | | |
432 | 432 | | |
| |||
740 | 740 | | |
741 | 741 | | |
742 | 742 | | |
743 | | - | |
744 | 743 | | |
745 | 744 | | |
746 | 745 | | |
| |||
810 | 809 | | |
811 | 810 | | |
812 | 811 | | |
813 | | - | |
814 | | - | |
815 | | - | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
816 | 818 | | |
817 | | - | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
818 | 824 | | |
819 | 825 | | |
820 | 826 | | |
| |||
0 commit comments