mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-30 12:47:13 -06:00
Merge pull request #4560 from el-j/perf/edited-root-refresh-4554
perf(engine): gate post-edit Shaper and Text-Wrap maintenance via edited-root diffing (#4554)
This commit is contained in:
@@ -197,9 +197,9 @@ fn replace(doc: &mut Document, id: NodeId, dim: Dimension) -> Result<()> {
|
||||
|
||||
/// Called inside every edit, so dimensions and their undo history stay in step with the art they
|
||||
/// measure. Returns at once in documents without dimensions.
|
||||
pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<()> {
|
||||
pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<bool> {
|
||||
if !doc.dimensions {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
let mut dimensions = Vec::new();
|
||||
doc.walk(|n| {
|
||||
@@ -208,6 +208,7 @@ pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<()> {
|
||||
}
|
||||
});
|
||||
doc.dimensions = !dimensions.is_empty();
|
||||
let mut changed = false;
|
||||
for (id, old) in dimensions {
|
||||
let mut next = old.clone();
|
||||
let moved = next.moved.take();
|
||||
@@ -215,6 +216,7 @@ pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<()> {
|
||||
// A deleted/degenerate source has no valid measurement. Remove its annotation in
|
||||
// this same undo step instead of keeping a silently wrong number.
|
||||
doc.remove(id)?;
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
// Moved on its own (the art it measures stayed put): it keeps its new place.
|
||||
@@ -233,9 +235,10 @@ pub(crate) fn refresh(before: &Document, doc: &mut Document) -> Result<()> {
|
||||
|| before.node(id).and_then(|n| n.extras().dimension.as_deref()).is_some_and(|d| d != &next)
|
||||
{
|
||||
replace(doc, id, next)?;
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
Ok(changed)
|
||||
}
|
||||
|
||||
/// Redraw the dimensions of an opened document whose art isn't in its colour mode: files saved
|
||||
|
||||
@@ -4,7 +4,10 @@
|
||||
use super::pathops::Beside;
|
||||
use super::{CommandSpec, EngineError, Result, Session, bad, bool_or, checked_ids_param, cmd, f64_or, has_doc, id_param, point_param};
|
||||
use serde_json::{Value, json};
|
||||
use std::{collections::BTreeMap, sync::Arc};
|
||||
use std::{
|
||||
collections::{BTreeMap, HashMap},
|
||||
sync::Arc,
|
||||
};
|
||||
use vectorcraft_doc::shaper::{EdgeEdit, FILL_PREFIX, FaceEdit, FaceSelector, SOURCES, STROKE_PREFIX, ShaperSpec};
|
||||
use vectorcraft_doc::{Appearance, AppearanceItem, Document, Node, NodeId, NodeKind};
|
||||
use vectorcraft_geom::{FillRule, PathData, Point, Rect, Shape as _};
|
||||
@@ -386,17 +389,37 @@ fn generated(d: &mut Document, src: &[Arc<Node>], spec: &ShaperSpec, map: &Build
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Re-evaluate only groups whose original art changed. Errors reach Session::edit's rollback.
|
||||
pub(crate) fn refresh(before: &Document, d: &mut Document) -> Result<()> {
|
||||
let mut ids = vec![];
|
||||
d.walk(|n| {
|
||||
if n.extras().shaper.is_some() {
|
||||
ids.push(n.id)
|
||||
fn changed_shaper_groups(before: &[Arc<Node>], after: &[Arc<Node>], out: &mut Vec<NodeId>) {
|
||||
let mut by_id: Option<HashMap<NodeId, &Arc<Node>>> = None;
|
||||
for (i, a) in after.iter().enumerate() {
|
||||
let b = match before.get(i) {
|
||||
Some(b) if b.id == a.id => Some(b),
|
||||
_ => by_id.get_or_insert_with(|| before.iter().map(|b| (b.id, b)).collect()).get(&a.id).copied(),
|
||||
};
|
||||
if b.is_some_and(|b| Arc::ptr_eq(b, a)) {
|
||||
continue;
|
||||
}
|
||||
});
|
||||
if a.extras().shaper.is_some() {
|
||||
out.push(a.id);
|
||||
}
|
||||
if let Some(children) = a.children() {
|
||||
changed_shaper_groups(b.and_then(|b| b.children()).map_or(&[], Vec::as_slice), children, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Re-evaluate only groups whose original art changed. Errors reach Session::edit's rollback.
|
||||
/// Returns whether any Shaper group's artwork was rebuilt or modified.
|
||||
pub(crate) fn refresh(before: &Document, d: &mut Document) -> Result<bool> {
|
||||
let mut ids = vec![];
|
||||
changed_shaper_groups(&before.layers, &d.layers, &mut ids);
|
||||
if ids.is_empty() {
|
||||
return Ok(false);
|
||||
}
|
||||
// Groups whose source art is all gone are removed through `emptied`, which is made when the
|
||||
// first one is found.
|
||||
let mut emptied: Option<Beside> = None;
|
||||
let mut rebuilt_any = false;
|
||||
for &id in &ids {
|
||||
let Some(current) = d.node(id).cloned() else { continue };
|
||||
let Some(old) = before.node(id) else { continue };
|
||||
@@ -413,6 +436,7 @@ pub(crate) fn refresh(before: &Document, d: &mut Document) -> Result<()> {
|
||||
}
|
||||
if src.is_empty() {
|
||||
emptied.get_or_insert_with(|| Beside::replacing(d, &ids)).put(d, id, vec![])?;
|
||||
rebuilt_any = true;
|
||||
continue;
|
||||
}
|
||||
let map = map_of(&src)?;
|
||||
@@ -510,8 +534,10 @@ pub(crate) fn refresh(before: &Document, d: &mut Document) -> Result<()> {
|
||||
if let Some(ch) = n.children_mut() {
|
||||
*ch = children;
|
||||
}
|
||||
rebuilt_any = true;
|
||||
}
|
||||
emptied.map_or(Ok(()), |e| e.finish(d))
|
||||
emptied.map_or(Ok(()), |e| e.finish(d))?;
|
||||
Ok(rebuilt_any)
|
||||
}
|
||||
|
||||
fn candidates(s: &Session, p: &Value, points: &[Point], tolerance: f64) -> Result<Vec<NodeId>> {
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
//! resolves, for each area text object, the outlines of the wrap objects above it into
|
||||
//! `TextObject::wrap` (text space); the layout engine subtracts them from each line's span.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{Document, Node, NodeId, NodeKind, TextKind, TextWrap, WrapShape};
|
||||
use vectorcraft_geom::{PathData, shapes};
|
||||
@@ -149,21 +151,18 @@ fn collect<'a>(n: &'a Node, out: &mut Vec<Item<'a>>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn any_wrap_state(n: &Node) -> bool {
|
||||
n.extras().wrap.is_some()
|
||||
|| matches!(&n.kind, NodeKind::Text(t) if !t.wrap.is_empty())
|
||||
|| n.children().is_some_and(|ch| ch.iter().any(|c| any_wrap_state(c)))
|
||||
}
|
||||
|
||||
/// Re-resolve the wrap shapes of every area text object (called after each edit).
|
||||
pub(crate) fn refresh(doc: &mut Document) {
|
||||
if !doc.layers.iter().any(|l| any_wrap_state(l)) {
|
||||
return;
|
||||
}
|
||||
pub(crate) fn refresh(before: &Document, doc: &mut Document) {
|
||||
let mut updates: Vec<(NodeId, Vec<WrapShape>)> = vec![];
|
||||
for layer in &doc.layers {
|
||||
for (idx, layer) in doc.layers.iter().enumerate() {
|
||||
if before.layers.get(idx).is_some_and(|b| Arc::ptr_eq(b, layer)) || before.layers.iter().any(|b| b.id == layer.id && Arc::ptr_eq(b, layer)) {
|
||||
continue;
|
||||
}
|
||||
let mut items = vec![];
|
||||
collect(layer, &mut items);
|
||||
if items.is_empty() {
|
||||
continue;
|
||||
}
|
||||
for (i, it) in items.iter().enumerate() {
|
||||
let Item::Text(id) = it else { continue };
|
||||
let Some(NodeKind::Text(t)) = doc.node(*id).map(|n| &n.kind) else { continue };
|
||||
|
||||
@@ -19,6 +19,7 @@ impl Session {
|
||||
let doc = Arc::make_mut(&mut st.doc);
|
||||
// The Global Edit members the edit reached besides the selection.
|
||||
let mut spread = vec![];
|
||||
let mut dim_changed = false;
|
||||
// Objects attached to a path follow it, and dimensions follow the art they measure.
|
||||
let result = match f(doc, &mut st.selection).and_then(|v| {
|
||||
// Global Edit: the other members take what the edit changed about the one it edited.
|
||||
@@ -26,18 +27,20 @@ impl Session {
|
||||
spread = cmd::globaledit::propagate(&before, doc, ge, [&before_sel.objects, &st.selection.objects]);
|
||||
}
|
||||
cmd::objects_on_path::refresh(&before, doc);
|
||||
cmd::dimension::refresh(&before, doc)?;
|
||||
dim_changed = cmd::dimension::refresh(&before, doc)?;
|
||||
Ok(v)
|
||||
}) {
|
||||
Ok(v) => match cmd::shaper::refresh(&before, Arc::make_mut(&mut st.doc)) {
|
||||
Err(e) => Err(e),
|
||||
Ok(()) => {
|
||||
Ok(shaper_changed) => {
|
||||
// Attached Shaper groups whose art was rebuilt go back on their path.
|
||||
cmd::objects_on_path::refresh(&before, Arc::make_mut(&mut st.doc));
|
||||
if shaper_changed || dim_changed {
|
||||
cmd::objects_on_path::refresh(&before, Arc::make_mut(&mut st.doc));
|
||||
}
|
||||
// Inline graphics in text follow their symbols.
|
||||
cmd::inline::refresh(&before, Arc::make_mut(&mut st.doc));
|
||||
// Text Wrap: area type follows its wrap objects; then threads re-flow.
|
||||
cmd::textwrap::refresh(Arc::make_mut(&mut st.doc));
|
||||
cmd::textwrap::refresh(&before, Arc::make_mut(&mut st.doc));
|
||||
// Type in no thread numbers its list paragraphs from 1 (before the mask copies
|
||||
// its editing layer's art).
|
||||
cmd::threads::restart_unthreaded_counts(&mut st.doc);
|
||||
|
||||
Reference in New Issue
Block a user