From 1edfb8f4504d97d0f92b245965aa1965f95450a9 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 24 Oct 2026 18:01:41 -0700 Subject: [PATCH] Render: brush art is made again when a symbol or pattern it shows is renamed The brush cache dropped brush art that can show a symbol or a pattern only when the art of the document's symbols or patterns changed. Renaming one, for example swapping the names of two symbols, left their art as it was, so a brush whose art holds an instance of one of those names kept drawing the old symbol. The cache now compares the names along with the art. --- crates/render/src/brush_fx.rs | 16 ++++---- crates/render/src/tests_memory.rs | 62 +++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 8 deletions(-) diff --git a/crates/render/src/brush_fx.rs b/crates/render/src/brush_fx.rs index 1322ef056..cb370520f 100644 --- a/crates/render/src/brush_fx.rs +++ b/crates/render/src/brush_fx.rs @@ -46,9 +46,9 @@ fn shows_definitions(brush: &Brush, st: &StrokeLayer) -> bool { matches!(st.paint, vectorcraft_color::Paint::Pattern { .. }) || crate::fx::Reads::of_all(art.into_iter()).shows_definitions() } -/// The art of `doc`'s patterns and symbols, in order. -fn definitions(doc: &Document) -> impl Iterator> { - doc.patterns.iter().flat_map(|p| &p.art).chain(doc.symbols.iter().map(|s| &s.art)) +/// The names and art of `doc`'s patterns and symbols, in order. +fn definitions(doc: &Document) -> impl Iterator)> { + doc.patterns.iter().flat_map(|p| p.art.iter().map(|a| (p.name.as_str(), a))).chain(doc.symbols.iter().map(|s| (s.name.as_str(), &s.art))) } /// Parsed brush library and generated brush art. @@ -61,9 +61,9 @@ pub(crate) struct BrushCache { /// (frame stamp, document address) the library was last validated for. checked: (u64, usize), lib: HashMap>, - /// The art of the document's patterns and symbols ([`definitions`]) when the library was last - /// validated, held weakly as the pattern tiles hold theirs. - definitions: Vec>, + /// The names and art of the document's patterns and symbols ([`definitions`]) when the library + /// was last validated, the art held weakly as the pattern tiles hold theirs. + definitions: Vec<(String, Weak)>, /// Brush art per (node id, stroke item index, [`path_key`] of the stroked path). pub(crate) entries: FrameCache<(u64, usize, u64), Entry>, /// How many times [`Renderer::draw_brush`] made brush art. @@ -93,8 +93,8 @@ impl BrushCache { return; } self.checked = key; - if !self.definitions.iter().map(Weak::as_ptr).eq(definitions(doc).map(Arc::as_ptr)) { - self.definitions = definitions(doc).map(Arc::downgrade).collect(); + if !self.definitions.iter().map(|(n, a)| (n.as_str(), a.as_ptr())).eq(definitions(doc).map(|(n, a)| (n, Arc::as_ptr(a)))) { + self.definitions = definitions(doc).map(|(n, a)| (n.to_string(), Arc::downgrade(a))).collect(); self.entries.retain(|e| !e.definitions); } let cur = doc.unknown.get(vectorcraft_brush::DOC_KEY); diff --git a/crates/render/src/tests_memory.rs b/crates/render/src/tests_memory.rs index 9b0304e3c..156edb765 100644 --- a/crates/render/src/tests_memory.rs +++ b/crates/render/src/tests_memory.rs @@ -801,6 +801,68 @@ fn brush_art_draws_edits_of_the_symbols_and_patterns_it_shows() { } } +/// As above, with symbols `Dot` (a blue square) and `Other` (a red square with a brushed line), +/// and patterns `Fill` (a green square) and `Spare` (a red square with a brushed line). After a +/// frame, the next one draws as a new renderer does when two symbols or two patterns swap names, +/// their art left as it is: the brush art's instance of `Dot` or its square painted with `Fill` +/// then shows art that draws a brushed stroke. +#[test] +fn brush_art_draws_renamed_symbols_and_patterns_it_shows() { + let _fonts = fonts_steady(); + use vectorcraft_brush::{Brush, BrushKind, Scatter}; + use vectorcraft_doc::pattern::{PatternDef, pattern_paint}; + use vectorcraft_geom::shapes::{line, rectangle}; + let square = |d: &mut Document, paint: Paint| filled(d, rectangle(Rect::new(-4.0, -4.0, 4.0, 4.0)), paint, vec![]); + let art = |d: &mut Document, paint: Paint, brushed: bool| { + let mut members = vec![Arc::new(square(d, paint))]; + if brushed { + members.push(Arc::new(brushed_line(d))); + } + Node::group(d.alloc_id(), members) + }; + let doc = || { + let mut d = Document::new(200.0, 100.0); + for (name, paint, brushed) in + [("Dot", Paint::solid(Color::rgb(0.0, 0.0, 1.0)), false), ("Other", Paint::solid(Color::rgb(1.0, 0.0, 0.0)), true)] + { + let a = art(&mut d, paint, brushed); + d.symbols.push(vectorcraft_doc::Symbol { name: name.into(), art: Arc::new(a) }); + } + for (name, paint, brushed) in + [("Fill", Paint::solid(Color::rgb(0.0, 0.6, 0.0)), false), ("Spare", Paint::solid(Color::rgb(1.0, 0.0, 0.0)), true)] + { + let a = art(&mut d, paint, brushed); + let mut def = PatternDef::new(name, vec![Arc::new(a)]); + def.tile = Rect::new(-6.0, -6.0, 6.0, 6.0); + d.patterns.push(def); + } + let id = d.alloc_id(); + let instance = Node::new(id, NodeKind::SymbolInstance { symbol: "Dot".into(), xf: Affine::translate((0.0, -8.0)) }); + let mut painted = square(&mut d, pattern_paint("Fill")); + painted.transform(Affine::translate((0.0, 8.0)), true); + let scatter = Node::group(d.alloc_id(), vec![Arc::new(instance), Arc::new(painted)]); + let mut lib = vectorcraft_brush::library(&d); + lib.push(Brush { name: "Dots".into(), kind: BrushKind::Scatter(Scatter { art: scatter, spacing: (150.0, 150.0), ..Default::default() }) }); + vectorcraft_brush::store(&mut d, &lib); + let mut ap = vectorcraft_doc::Appearance::basic(Paint::None, Paint::solid(Color::BLACK), 1.0); + if let Some(s) = ap.stroke_mut() { + s.brush = Some("Dots".into()); + } + let path = Node::path(d.alloc_id(), line((20.0, 50.0).into(), (180.0, 50.0).into()), ap); + let l = d.layers[0].id; + d.insert(Some(l), usize::MAX, path).unwrap(); + d + }; + edit_draws_as_new(doc(), |d| { + d.symbols[0].name = "Other".into(); + d.symbols[1].name = "Dot".into(); + }); + edit_draws_as_new(doc(), |d| { + d.patterns[0].name = "Spare".into(); + d.patterns[1].name = "Fill".into(); + }); +} + /// A path of the document and an instance of symbol `Wave` (a group holding another path) are /// stroked with scatter brush `Bent`, whose art is an arc envelope around a green band: the second /// frame evaluates none of the envelopes in their brush art again, and draws what the first drew