diff --git a/crates/engine/src/cmd/graph/generate.rs b/crates/engine/src/cmd/graph/generate.rs index 8efdc2df9..7110974c3 100644 --- a/crates/engine/src/cmd/graph/generate.rs +++ b/crates/engine/src/cmd/graph/generate.rs @@ -244,7 +244,8 @@ impl<'a> Plot<'a> { if marker_cost(g, &designs) > MARKER_BUDGET { designs.clear(); } - let nser = g.rows.iter().map(Vec::len).max().unwrap_or(0).max(1); + // The same dimensions `GraphSpec::cells` reads: raw rows can be longer than the grid they are read through. + let nser = g.rows.iter().map(Vec::len).max().unwrap_or(0).clamp(1, vectorcraft_doc::MAX_GRAPH_SERIES); let kinds: Vec = (0..nser).map(|s| g.series_kind(s)).collect(); Plot { g, @@ -253,7 +254,7 @@ impl<'a> Plot<'a> { designs, r: g.rect, nser, - ncat: g.rows.len().max(1), + ncat: g.rows.len().clamp(1, vectorcraft_doc::MAX_GRAPH_CATEGORIES), vs: label_size(&g.label_styles, Role::Value), cs: label_size(&g.label_styles, Role::Category), ls: label_size(&g.label_styles, Role::Legend), diff --git a/crates/engine/src/cmd/webexport.rs b/crates/engine/src/cmd/webexport.rs index c67d9428f..af0f313ea 100644 --- a/crates/engine/src/cmd/webexport.rs +++ b/crates/engine/src/cmd/webexport.rs @@ -21,7 +21,7 @@ use vectorcraft_render::encode::quantize::{self, Dither, Indexed, PaletteOptions use vectorcraft_render::encode::web::{self, ColorSort}; use vectorcraft_render::encode::{gif, jpeg, png}; -use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, f64_or, has_doc, str_param, unique_name}; +use super::{CommandSpec, Result, Session, always, bad, bool_or, cmd, f64_or, has_doc, str_param, unique_name_from}; const C: &str = "document.exportForWeb"; @@ -866,6 +866,8 @@ pub fn export_files(doc: &Document, selected: &[NodeId], s: &WebSettings, stem: placed.push((None, Some(name))); } else { let mut taken = HashSet::new(); + // Per base, where the suffix search resumes, so many slices of one name don't each count up from 2. + let mut next = std::collections::HashMap::new(); for c in &cells { let kind = c.area.id.and_then(|id| doc.slice_options(id)).map(|o| o.kind).unwrap_or_default(); if kind != SliceKind::Image { @@ -875,7 +877,7 @@ pub fn export_files(doc: &Document, selected: &[NodeId], s: &WebSettings, stem: let base: String = doc.slice_name(c.area).chars().map(|ch| if ch.is_alphanumeric() || "-_.".contains(ch) { ch } else { '-' }).collect(); // A slice named after a Windows device (`aux`, `NUL`…) gets a `-` after the device name. let file = |base: &str| super::fileio::safe_file_name(&format!("{base}.{ext}")); - let base = unique_name(&base, |n| taken.contains(&file(n).to_lowercase())); + let base = unique_name_from(&base, &mut next, |n| taken.contains(&file(n).to_lowercase())); taken.insert(file(&base).to_lowercase()); let name = format!("{IMAGES}/{}", file(&base)); let [x0, y0, x1, y1] = c.px; diff --git a/crates/plugins/src/effect.rs b/crates/plugins/src/effect.rs index 29d5483d3..b49f5b831 100644 --- a/crates/plugins/src/effect.rs +++ b/crates/plugins/src/effect.rs @@ -120,8 +120,15 @@ struct Cache { static CACHE: Mutex> = Mutex::new(None); +/// Cache units a path takes: its anchors, and one for each subpath's own allocation. fn anchors(p: &PathData) -> usize { - p.subpaths.iter().map(|sp| sp.anchors.len()).sum() + p.subpaths.iter().map(|sp| sp.anchors.len() + 1).sum() +} + +/// Cache units an entry takes: its input and result paths, and the serialized parameters and +/// paints its key owns (one unit per [`CACHE_ANCHORS`]'s 50 bytes an anchor stands for). +fn weight(key: &Key, out: Option<&PathData>) -> usize { + anchors(&key.path) + out.map_or(0, anchors) + (key.params.len() + key.paints.len()) / 50 + 1 } static ERRORS: Mutex>> = Mutex::new(None); @@ -168,7 +175,7 @@ pub fn apply(id: &str, params: &Value, path: &PathData, bounds: Rect, rule: Fill } note(id, &r); let out = r.ok(); - let size = anchors(&key.path) + out.as_ref().map_or(0, anchors); + let size = weight(&key, out.as_ref()); if size > CACHE_ANCHORS / 4 { return out; } @@ -178,7 +185,7 @@ pub fn apply(id: &str, params: &Value, path: &PathData, bounds: Rect, rule: Fill *cache = Cache::default(); } if let Some((old, old_out)) = cache.entries.insert(h, (key, out.clone())) { - cache.anchors = cache.anchors.saturating_sub(anchors(&old.path) + old_out.as_ref().map_or(0, anchors)); + cache.anchors = cache.anchors.saturating_sub(weight(&old, old_out.as_ref())); } cache.anchors += size; out