mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-24 10:46:50 -06:00
Recalc: an array that spills as before doesn't requeue its dependents
After each round, every array formula that was evaluated queued the dependents of its spill area again, even when it spilled the same area with the same values. Two arrays that read each other's spill areas then queued each other in every round, up to the limit of 8: one edit evaluated 24 formulas instead of 3. An unchanged spill now leaves its dependents alone. last_recalc_cells counts the formulas of all rounds.
This commit is contained in:
@@ -456,6 +456,20 @@ fn spill_blocked(host: &PassHost<'_>, (sheet, anchor): Key, a: &Array) -> bool {
|
||||
|| sh.spill.range(range.start..=range.end).any(|(c, _)| range.contains(*c) && spilled_over(*c))
|
||||
}
|
||||
|
||||
/// Whether `arr`, anchored at `anchor`, would spill exactly what the sheet already holds over
|
||||
/// `range`: the same area, nothing new in the way, and the same values.
|
||||
fn spills_as_before(sheet: &gridcraft_model::Sheet, anchor: CellRef, range: RangeRef, arr: &Array) -> bool {
|
||||
let end =
|
||||
CellRef::new(anchor.row.saturating_add((arr.rows as u32).saturating_sub(1)), anchor.col.saturating_add((arr.cols as u32).saturating_sub(1)));
|
||||
RangeRef::new(anchor, end) == range
|
||||
&& !sheet.merges.iter().any(|m| m.intersects(&range))
|
||||
&& !sheet.tables.iter().any(|t| t.range.contains(anchor))
|
||||
&& range
|
||||
.iter()
|
||||
.enumerate()
|
||||
.all(|(i, c)| c == anchor || (!sheet.cells.has(c) && sheet.spill.get(&c) == Some(arr.data.get(i).unwrap_or(&Value::Empty))))
|
||||
}
|
||||
|
||||
impl PassHost<'_> {
|
||||
fn compute(&mut self, k: Key) -> Value {
|
||||
if let Some(v) = self.results.get(&k) {
|
||||
@@ -950,6 +964,7 @@ impl Calc {
|
||||
/// Evaluates the dirty set and writes results (and spills) back.
|
||||
fn run(&mut self, wb: &mut Workbook, mut dirty: Vec<Key>, full: bool) {
|
||||
let mut dynamic_again = !self.graph.dynamic.is_empty();
|
||||
self.last_recalc_cells = 0;
|
||||
for _round in 0..8 {
|
||||
if dirty.is_empty() {
|
||||
break;
|
||||
@@ -984,7 +999,7 @@ impl Calc {
|
||||
eprintln!(" eval {} formulas {:.1} ms", results.len(), prof_now() - tp);
|
||||
}
|
||||
let tw = prof_now();
|
||||
self.last_recalc_cells = results.len();
|
||||
self.last_recalc_cells += results.len();
|
||||
self.circular = cycles;
|
||||
// Write back.
|
||||
let mut spill_changes: Vec<Key> = Vec::new();
|
||||
@@ -1004,6 +1019,14 @@ impl Calc {
|
||||
if old.is_none() && new.is_none() {
|
||||
continue;
|
||||
}
|
||||
// An array that spills exactly as before changes nothing its dependents read, so
|
||||
// they aren't queued again. Otherwise two arrays that read each other's spill areas
|
||||
// queue each other in every round, up to the round limit.
|
||||
if let (Some(r), Some(arr)) = (old, new)
|
||||
&& spills_as_before(sheet, k.1, r, arr)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Some(r) = old {
|
||||
for c in r.iter() {
|
||||
sheet.spill.remove(&c);
|
||||
|
||||
@@ -67,6 +67,25 @@ fn spill_range_operator() {
|
||||
assert_eq!(t.num("E1"), 100.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arrays_that_spill_as_before_do_not_requeue_their_dependents() {
|
||||
// Two arrays that each read the other's spill area, and a formula reading both spills.
|
||||
let mut t = T::new();
|
||||
t.set("A1", "=SEQUENCE(2)+0*B2");
|
||||
t.set("B1", "=SEQUENCE(2)+0*A2");
|
||||
t.set("C1", "=SUM(A1#)+SUM(B1#)");
|
||||
// An edit that spills the same values again: one round, not one per round up to the limit.
|
||||
t.set("A1", "=SEQUENCE(2)+0*B2+0");
|
||||
assert_eq!((t.num("A2"), t.num("B2"), t.num("C1")), (2.0, 2.0, 6.0));
|
||||
assert!(t.calc.last_recalc_cells <= 3, "{} formulas evaluated for one edit", t.calc.last_recalc_cells);
|
||||
// Only the anchor's value changes: formulas reading the whole spill still see it.
|
||||
t.set("A1", "={7;2}+0*B2");
|
||||
assert_eq!((t.num("A1"), t.num("C1")), (7.0, 12.0));
|
||||
// A spill that does change still reaches its dependents.
|
||||
t.set("A1", "=SEQUENCE(2)*10+0*B2");
|
||||
assert_eq!((t.num("A2"), t.num("C1")), (20.0, 33.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_and_dependencies() {
|
||||
let mut t = T::new();
|
||||
|
||||
Reference in New Issue
Block a user