Skip to content

Commit 866cfa8

Browse files
committed
Make VectorModification properly implement CacheHash
1 parent 060e5a2 commit 866cfa8

2 files changed

Lines changed: 112 additions & 59 deletions

File tree

node-graph/libraries/graphene-hash/src/lib.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,30 @@ impl<T: CacheHash + ?Sized> CacheHash for std::sync::Arc<T> {
202202
}
203203
}
204204

205+
#[cfg(feature = "std")]
206+
impl<K: CacheHash, V: CacheHash> CacheHash for std::collections::BTreeMap<K, V> {
207+
#[inline]
208+
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
209+
core::hash::Hash::hash(&self.len(), state);
210+
for (key, value) in self {
211+
key.cache_hash(state);
212+
value.cache_hash(state);
213+
}
214+
}
215+
}
216+
217+
#[cfg(feature = "std")]
218+
impl<T: CacheHash> CacheHash for std::collections::BTreeSet<T> {
219+
#[inline]
220+
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
221+
core::hash::Hash::hash(&self.len(), state);
222+
for item in self {
223+
item.cache_hash(state);
224+
}
225+
}
226+
}
227+
228+
205229
impl<T: CacheHash + ?Sized> CacheHash for &T {
206230
#[inline]
207231
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {

node-graph/libraries/vector-types/src/vector/vector_modification.rs

Lines changed: 88 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,18 @@
11
use super::*;
22
use crate::subpath::BezierHandles;
33
use crate::vector::misc::{HandleId, HandleType, point_to_dvec2};
4-
use core_types::uuid::generate_uuid;
54
use dyn_any::DynAny;
65
use glam::DVec2;
76
use kurbo::{BezPath, PathEl, Point};
8-
use std::collections::{HashMap, HashSet};
9-
use std::hash::BuildHasher;
7+
use std::collections::{BTreeMap, BTreeSet, HashSet};
108

119
/// Represents a procedural change to the [`PointDomain`] in [`Vector`].
12-
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
10+
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
1311
pub struct PointModification {
1412
add: Vec<PointId>,
15-
remove: HashSet<PointId>,
16-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
17-
delta: HashMap<PointId, DVec2>,
18-
}
19-
20-
impl Hash for PointModification {
21-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
22-
generate_uuid().hash(state)
23-
}
13+
remove: BTreeSet<PointId>,
14+
#[serde(deserialize_with = "deserialize_btreemap")]
15+
delta: BTreeMap<PointId, DVec2>,
2416
}
2517

2618
impl PointModification {
@@ -62,7 +54,7 @@ impl PointModification {
6254
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
6355
Self {
6456
add: vector.point_domain.ids().to_vec(),
65-
remove: HashSet::new(),
57+
remove: BTreeSet::new(),
6658
delta: vector.point_domain.ids().iter().copied().zip(vector.point_domain.positions().iter().cloned()).collect(),
6759
}
6860
}
@@ -80,20 +72,20 @@ impl PointModification {
8072
}
8173

8274
/// Represents a procedural change to the [`SegmentDomain`] in [`Vector`].
83-
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
75+
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
8476
pub struct SegmentModification {
8577
add: Vec<SegmentId>,
86-
remove: HashSet<SegmentId>,
87-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
88-
start_point: HashMap<SegmentId, PointId>,
89-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
90-
end_point: HashMap<SegmentId, PointId>,
91-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
92-
handle_primary: HashMap<SegmentId, Option<DVec2>>,
93-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
94-
handle_end: HashMap<SegmentId, Option<DVec2>>,
95-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
96-
stroke: HashMap<SegmentId, StrokeId>,
78+
remove: BTreeSet<SegmentId>,
79+
#[serde(deserialize_with = "deserialize_btreemap")]
80+
start_point: BTreeMap<SegmentId, PointId>,
81+
#[serde(deserialize_with = "deserialize_btreemap")]
82+
end_point: BTreeMap<SegmentId, PointId>,
83+
#[serde(deserialize_with = "deserialize_btreemap")]
84+
handle_primary: BTreeMap<SegmentId, Option<DVec2>>,
85+
#[serde(deserialize_with = "deserialize_btreemap")]
86+
handle_end: BTreeMap<SegmentId, Option<DVec2>>,
87+
#[serde(deserialize_with = "deserialize_btreemap")]
88+
stroke: BTreeMap<SegmentId, StrokeId>,
9789
}
9890

9991
impl SegmentModification {
@@ -219,7 +211,7 @@ impl SegmentModification {
219211
let point_id = |(&segment, &index)| (segment, vector.point_domain.ids()[index]);
220212
Self {
221213
add: vector.segment_domain.ids().to_vec(),
222-
remove: HashSet::new(),
214+
remove: BTreeSet::new(),
223215
start_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.start_point()).map(point_id).collect(),
224216
end_point: vector.segment_domain.ids().iter().zip(vector.segment_domain.end_point()).map(point_id).collect(),
225217
handle_primary: vector.segment_bezier_iter().map(|(id, b, _, _)| (id, b.handle_start().map(|handle| handle - b.start))).collect(),
@@ -250,14 +242,14 @@ impl SegmentModification {
250242
}
251243

252244
/// Represents a procedural change to the [`RegionDomain`] in [`Vector`].
253-
#[derive(Clone, Debug, Default, PartialEq, serde::Serialize, serde::Deserialize)]
245+
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, serde::Serialize, serde::Deserialize)]
254246
pub struct RegionModification {
255247
add: Vec<RegionId>,
256-
remove: HashSet<RegionId>,
257-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
258-
segment_range: HashMap<RegionId, std::ops::RangeInclusive<SegmentId>>,
259-
#[serde(serialize_with = "serialize_hashmap", deserialize_with = "deserialize_hashmap")]
260-
fill: HashMap<RegionId, FillId>,
248+
remove: BTreeSet<RegionId>,
249+
#[serde(deserialize_with = "deserialize_btreemap")]
250+
segment_range: BTreeMap<RegionId, std::ops::RangeInclusive<SegmentId>>,
251+
#[serde(deserialize_with = "deserialize_btreemap")]
252+
fill: BTreeMap<RegionId, FillId>,
261253
}
262254

263255
impl RegionModification {
@@ -286,21 +278,21 @@ impl RegionModification {
286278
pub fn create_from_vector<Upstream>(vector: &Vector<Upstream>) -> Self {
287279
Self {
288280
add: vector.region_domain.ids().to_vec(),
289-
remove: HashSet::new(),
281+
remove: BTreeSet::new(),
290282
segment_range: vector.region_domain.ids().iter().copied().zip(vector.region_domain.segment_range().iter().cloned()).collect(),
291283
fill: vector.region_domain.ids().iter().copied().zip(vector.region_domain.fill().iter().cloned()).collect(),
292284
}
293285
}
294286
}
295287

296288
/// Represents a procedural change to the [`Vector`].
297-
#[derive(Clone, Debug, Default, PartialEq, DynAny, serde::Serialize, serde::Deserialize)]
289+
#[derive(Clone, Debug, Default, PartialEq, graphene_hash::CacheHash, DynAny, serde::Serialize, serde::Deserialize)]
298290
pub struct VectorModification {
299291
points: PointModification,
300292
segments: SegmentModification,
301293
regions: RegionModification,
302-
add_g1_continuous: HashSet<[HandleId; 2]>,
303-
remove_g1_continuous: HashSet<[HandleId; 2]>,
294+
add_g1_continuous: BTreeSet<[HandleId; 2]>,
295+
remove_g1_continuous: BTreeSet<[HandleId; 2]>,
304296
}
305297

306298
/// A modification type that can be added to a [`VectorModification`].
@@ -506,33 +498,70 @@ impl VectorModification {
506498
segments: SegmentModification::create_from_vector(vector),
507499
regions: RegionModification::create_from_vector(vector),
508500
add_g1_continuous: vector.colinear_manipulators.iter().copied().collect(),
509-
remove_g1_continuous: HashSet::new(),
501+
remove_g1_continuous: BTreeSet::new(),
510502
}
511503
}
512504
}
513505

514-
impl Hash for VectorModification {
515-
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
516-
generate_uuid().hash(state)
506+
// Deserializes a BTreeMap from either a sequence of tuples (old document format) or a standard map.
507+
// TODO: Eventually remove this document upgrade code (the sequence-of-tuples path)
508+
use serde::de::{MapAccess, SeqAccess, Visitor};
509+
use serde::ser::SerializeSeq;
510+
use serde::{Deserialize, Deserializer, Serialize, Serializer};
511+
use std::fmt;
512+
use std::hash::{BuildHasher, Hash};
513+
fn deserialize_btreemap<'de, K, V, D>(deserializer: D) -> Result<BTreeMap<K, V>, D::Error>
514+
where
515+
K: Deserialize<'de> + Ord,
516+
V: Deserialize<'de>,
517+
D: Deserializer<'de>,
518+
{
519+
struct BTreeMapVisitor<K, V> {
520+
marker: std::marker::PhantomData<fn() -> BTreeMap<K, V>>,
517521
}
518-
}
519522

520-
// Intentionally non-deterministic: fields contain HashMaps with non-deterministic iteration order,
521-
// so we use a UUID to always bust the cache and force re-evaluation when any modification is present
522-
impl graphene_hash::CacheHash for VectorModification {
523-
fn cache_hash<H: core::hash::Hasher>(&self, state: &mut H) {
524-
core::hash::Hash::hash(&generate_uuid(), state);
523+
impl<'de, K, V> Visitor<'de> for BTreeMapVisitor<K, V>
524+
where
525+
K: Deserialize<'de> + Ord,
526+
V: Deserialize<'de>,
527+
{
528+
type Value = BTreeMap<K, V>;
529+
530+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
531+
formatter.write_str("a map or a sequence of key-value tuples")
532+
}
533+
534+
// Old document format: serialized as a sequence of (key, value) tuples
535+
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
536+
where
537+
A: SeqAccess<'de>,
538+
{
539+
let mut map = BTreeMap::new();
540+
while let Some((key, value)) = seq.next_element()? {
541+
map.insert(key, value);
542+
}
543+
Ok(map)
544+
}
545+
546+
// New format: standard map serialization from BTreeMap
547+
fn visit_map<A>(self, mut map_access: A) -> Result<Self::Value, A::Error>
548+
where
549+
A: MapAccess<'de>,
550+
{
551+
let mut map = BTreeMap::new();
552+
while let Some((key, value)) = map_access.next_entry()? {
553+
map.insert(key, value);
554+
}
555+
Ok(map)
556+
}
525557
}
558+
559+
deserializer.deserialize_any(BTreeMapVisitor { marker: std::marker::PhantomData })
526560
}
527561

528-
// Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
529-
// TODO: Eventually remove this document upgrade code
530-
use serde::de::{SeqAccess, Visitor};
531-
use serde::ser::SerializeSeq;
532-
use serde::{Deserialize, Deserializer, Serialize, Serializer};
533-
use std::fmt;
534-
use std::hash::Hash;
535-
pub fn serialize_hashmap<K, V, S, H>(hashmap: &HashMap<K, V, H>, serializer: S) -> Result<S::Ok, S::Error>
562+
// Serializes a HashMap as a sequence of (key, value) tuples for stable document serialization.
563+
// Used externally by graph-craft and the editor for HashMap fields in serialized documents.
564+
pub fn serialize_hashmap<K, V, S, H>(hashmap: &std::collections::HashMap<K, V, H>, serializer: S) -> Result<S::Ok, S::Error>
536565
where
537566
K: Serialize + Eq + Hash,
538567
V: Serialize,
@@ -546,7 +575,7 @@ where
546575
seq.end()
547576
}
548577

549-
pub fn deserialize_hashmap<'de, K, V, D, H>(deserializer: D) -> Result<HashMap<K, V, H>, D::Error>
578+
pub fn deserialize_hashmap<'de, K, V, D, H>(deserializer: D) -> Result<std::collections::HashMap<K, V, H>, D::Error>
550579
where
551580
K: Deserialize<'de> + Eq + Hash,
552581
V: Deserialize<'de>,
@@ -555,7 +584,7 @@ where
555584
{
556585
struct HashMapVisitor<K, V, H> {
557586
#[allow(clippy::type_complexity)]
558-
marker: std::marker::PhantomData<fn() -> HashMap<K, V, H>>,
587+
marker: std::marker::PhantomData<fn() -> std::collections::HashMap<K, V, H>>,
559588
}
560589

561590
impl<'de, K, V, H> Visitor<'de> for HashMapVisitor<K, V, H>
@@ -564,7 +593,7 @@ where
564593
V: Deserialize<'de>,
565594
H: BuildHasher + Default,
566595
{
567-
type Value = HashMap<K, V, H>;
596+
type Value = std::collections::HashMap<K, V, H>;
568597

569598
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
570599
formatter.write_str("a sequence of tuples")
@@ -574,7 +603,7 @@ where
574603
where
575604
A: SeqAccess<'de>,
576605
{
577-
let mut hashmap = HashMap::default();
606+
let mut hashmap = std::collections::HashMap::default();
578607
while let Some((key, value)) = seq.next_element()? {
579608
hashmap.insert(key, value);
580609
}

0 commit comments

Comments
 (0)