fix(forms): merge a calculation's read names with a set

calculation_order merged the names a Calculate script reads into the
field's other reads by searching the list so far for each name, so a
script naming many fields took quadratic time before the link limits
applied. The merge looks names up in a set.
This commit is contained in:
aiongg
2026-10-22 18:02:33 +00:00
parent f2f86455dd
commit 2d5c5054e2
2 changed files with 27 additions and 1 deletions
+10 -1
View File
@@ -122,6 +122,12 @@ pub(crate) fn run_order(doc: &Document, fields: &[Field]) -> (Vec<(usize, bool)>
(run, extra)
}
#[cfg(test)]
thread_local! {
/// Read names compared while merging a field's reads.
pub(crate) static NAMES_COMPARED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
/// The form's calculation order (`fields` as [`fields`] reads them), with each calculated field's
/// reads and issues, and Fix order's order.
pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder {
@@ -132,8 +138,11 @@ pub fn calculation_order(doc: &Document, fields: &[Field]) -> CalculationOrder {
.map(|(f, _)| {
let mut names = f.actions.calculate.reads();
if let Some(js) = &f.actions.scripts.calculate {
let mut known: HashSet<String> = names.iter().cloned().collect();
for name in crate::af::calculation_reads(js) {
if !names.contains(&name) {
#[cfg(test)]
NAMES_COMPARED.with(|n| n.set(n.get() + 1));
if known.insert(name.clone()) {
names.push(name);
}
}
+17
View File
@@ -2981,3 +2981,20 @@ fn page_walk_skips_repeated_and_cyclic_kids_without_queueing_them() {
assert_eq!(walk.next(), None);
assert_eq!(page_refs(&doc), [page]);
}
/// A Calculate script naming thousands of fields is merged with the field's other reads in one
/// pass, not by searching the names so far for each one.
#[test]
fn a_script_reading_many_fields_merges_its_reads_in_one_pass() {
use crate::calc_order::NAMES_COMPARED;
let mut doc = one_page();
add_field(&mut doc, 0, [50.0, 700.0, 250.0, 720.0], &NewField::Text { multiline: false }, Some("total")).unwrap();
let n = 2_000;
let script: String = (0..n).map(|i| format!("getField(\"f{i}\").value + ")).collect::<String>() + "0";
set_field_script(&mut doc, "total", "calculate", Some(&format!("event.value = {script};"))).unwrap();
NAMES_COMPARED.with(|c| c.set(0));
let order = calculation_order(&doc, &fields(&doc));
assert_eq!(order.fields[0].reads.len(), n);
let compared = NAMES_COMPARED.with(std::cell::Cell::get);
assert!(compared <= 4 * n, "{compared} names compared for {n} reads");
}