Every edit of an object copied its whole appearance (its fills, strokes and effects, about 540 bytes for a basic object) into the new node, though a move leaves it as it is. Over 45 whole-selection moves of 10,000 paths, undo history grew by 589 MB; now 349 MB (41 % less), and a nudge or a drag of the selection takes 12 ms instead of 15. - Node::appearance is a vectorcraft_doc::Shared<Appearance>: copy-on-write. A clone shares it, and the first change through DerefMut copies it (Arc::make_mut). Reads, writes, equality (also against a plain Appearance) and the saved form are those of the Appearance itself, so call sites read and change it as before. Sites that build or assign a whole appearance add .into(), and the few that need a plain copy take (*n.appearance).clone(). - Node::transform changes the appearance only when it has gradients to transform, and pattern paints are transformed only when there are some: a move no longer copies an appearance it doesn't change. The workspace suite (5,301 tests) and clippy pass with the change; the doc and engine suites pass again after the transform guard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmMmc1J8XeNLxhRihWv9cc
73 lines
2.0 KiB
Rust
73 lines
2.0 KiB
Rust
//! [`Shared`]: a value shared between copies of a node until one of them changes it.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
|
|
/// A copy-on-write value: cloning shares it, and the first change through `DerefMut` makes the
|
|
/// changed copy its own (`Arc::make_mut`). A node moved or recoloured keeps sharing what it didn't
|
|
/// change with the version the undo history holds. Reads, writes, equality and the saved form are
|
|
/// exactly those of the value itself.
|
|
#[derive(Clone, Default)]
|
|
pub struct Shared<T>(Arc<T>);
|
|
|
|
impl<T> Shared<T> {
|
|
pub fn new(v: T) -> Self {
|
|
Shared(Arc::new(v))
|
|
}
|
|
/// Whether `a` and `b` share one value (an unchanged copy).
|
|
pub fn ptr_eq(a: &Self, b: &Self) -> bool {
|
|
Arc::ptr_eq(&a.0, &b.0)
|
|
}
|
|
}
|
|
|
|
impl<T> std::ops::Deref for Shared<T> {
|
|
type Target = T;
|
|
fn deref(&self) -> &T {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl<T: Clone> std::ops::DerefMut for Shared<T> {
|
|
fn deref_mut(&mut self) -> &mut T {
|
|
Arc::make_mut(&mut self.0)
|
|
}
|
|
}
|
|
|
|
impl<T> From<T> for Shared<T> {
|
|
fn from(v: T) -> Self {
|
|
Shared::new(v)
|
|
}
|
|
}
|
|
|
|
impl<T: PartialEq> PartialEq for Shared<T> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
Arc::ptr_eq(&self.0, &other.0) || *self.0 == *other.0
|
|
}
|
|
}
|
|
|
|
/// A shared value equals the plain value it holds (`node.appearance == style.appearance`).
|
|
impl<T: PartialEq> PartialEq<T> for Shared<T> {
|
|
fn eq(&self, other: &T) -> bool {
|
|
*self.0 == *other
|
|
}
|
|
}
|
|
|
|
impl<T: std::fmt::Debug> std::fmt::Debug for Shared<T> {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl<T: Serialize> Serialize for Shared<T> {
|
|
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
|
self.0.serialize(s)
|
|
}
|
|
}
|
|
|
|
impl<'de, T: Deserialize<'de>> Deserialize<'de> for Shared<T> {
|
|
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
|
T::deserialize(d).map(Shared::new)
|
|
}
|
|
}
|