@@ -5,14 +5,20 @@ use core_types::uuid::generate_uuid;
55use dyn_any:: DynAny ;
66use glam:: DVec2 ;
77use kurbo:: { BezPath , PathEl , Point } ;
8+ use serde:: de:: { SeqAccess , Visitor } ;
9+ use serde:: ser:: SerializeSeq ;
10+ use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
811use std:: collections:: { HashMap , HashSet } ;
12+ use std:: fmt;
913use std:: hash:: BuildHasher ;
14+ use std:: hash:: Hash ;
1015
1116/// Represents a procedural change to the [`PointDomain`] in [`Vector`].
1217#[ derive( Clone , Debug , Default , PartialEq ) ]
1318#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
1419pub struct PointModification {
1520 add : Vec < PointId > ,
21+ #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashset" ) ) ]
1622 remove : HashSet < PointId > ,
1723 #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashmap" , deserialize_with = "deserialize_hashmap" ) ) ]
1824 delta : HashMap < PointId , DVec2 > ,
@@ -79,6 +85,7 @@ impl PointModification {
7985#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
8086pub struct SegmentModification {
8187 add : Vec < SegmentId > ,
88+ #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashset" ) ) ]
8289 remove : HashSet < SegmentId > ,
8390 #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashmap" , deserialize_with = "deserialize_hashmap" ) ) ]
8491 start_point : HashMap < SegmentId , PointId > ,
@@ -250,6 +257,7 @@ impl SegmentModification {
250257#[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
251258pub struct RegionModification {
252259 add : Vec < RegionId > ,
260+ #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashset" ) ) ]
253261 remove : HashSet < RegionId > ,
254262 #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashmap" , deserialize_with = "deserialize_hashmap" ) ) ]
255263 segment_range : HashMap < RegionId , std:: ops:: RangeInclusive < SegmentId > > ,
@@ -297,7 +305,9 @@ pub struct VectorModification {
297305 points : PointModification ,
298306 segments : SegmentModification ,
299307 regions : RegionModification ,
308+ #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashset" ) ) ]
300309 add_g1_continuous : HashSet < [ HandleId ; 2 ] > ,
310+ #[ cfg_attr( feature = "serde" , serde( serialize_with = "serialize_hashset" ) ) ]
301311 remove_g1_continuous : HashSet < [ HandleId ; 2 ] > ,
302312}
303313
@@ -520,27 +530,65 @@ impl graphene_hash::CacheHash for VectorModification {
520530 }
521531}
522532
523- // Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
533+ // TODO: Do we want to enforce that all serialized/deserialized hashmaps are a vec of tuples?
524534// TODO: Eventually remove this document upgrade code
525- use serde:: de:: { SeqAccess , Visitor } ;
526- use serde:: ser:: SerializeSeq ;
527- use serde:: { Deserialize , Deserializer , Serialize , Serializer } ;
528- use std:: fmt;
529- use std:: hash:: Hash ;
535+ /// Serializes as sorted `[[key, value], ...]` (sequence of pairs)
530536pub fn serialize_hashmap < K , V , S , H > ( hashmap : & HashMap < K , V , H > , serializer : S ) -> Result < S :: Ok , S :: Error >
531537where
532- K : Serialize + Eq + Hash ,
538+ K : Serialize + Eq + Hash + Ord ,
533539 V : Serialize ,
534540 S : Serializer ,
535541 H : BuildHasher ,
536542{
537- let mut seq = serializer. serialize_seq ( Some ( hashmap. len ( ) ) ) ?;
538- for ( key, value) in hashmap {
543+ // Sort entries by key so the serialized output is deterministic across runs (HashMap iteration order is randomized).
544+ // Removes a major source of churn in saved-document diffs without affecting load behavior.
545+ let mut entries: Vec < _ > = hashmap. iter ( ) . collect ( ) ;
546+ entries. sort_by ( |a, b| a. 0 . cmp ( b. 0 ) ) ;
547+
548+ let mut seq = serializer. serialize_seq ( Some ( entries. len ( ) ) ) ?;
549+ for ( key, value) in entries {
539550 seq. serialize_element ( & ( key, value) ) ?;
540551 }
541552 seq. end ( )
542553}
543554
555+ /// Serializes as sorted `{"key": value, ...}` (JSON object)
556+ pub fn serialize_hashmap_as_sorted_object < K , V , S , H > ( hashmap : & HashMap < K , V , H > , serializer : S ) -> Result < S :: Ok , S :: Error >
557+ where
558+ K : Serialize + Eq + Hash + Ord ,
559+ V : Serialize ,
560+ S : Serializer ,
561+ H : BuildHasher ,
562+ {
563+ use serde:: ser:: SerializeMap ;
564+
565+ let mut entries: Vec < _ > = hashmap. iter ( ) . collect ( ) ;
566+ entries. sort_by ( |a, b| a. 0 . cmp ( b. 0 ) ) ;
567+
568+ let mut map = serializer. serialize_map ( Some ( entries. len ( ) ) ) ?;
569+ for ( key, value) in entries {
570+ map. serialize_entry ( key, value) ?;
571+ }
572+ map. end ( )
573+ }
574+
575+ /// Serializes as sorted `[value, ...]` (JSON array)
576+ pub fn serialize_hashset < T , S , H > ( set : & HashSet < T , H > , serializer : S ) -> Result < S :: Ok , S :: Error >
577+ where
578+ T : Serialize + Eq + Hash + Ord ,
579+ S : Serializer ,
580+ H : BuildHasher ,
581+ {
582+ let mut entries: Vec < _ > = set. iter ( ) . collect ( ) ;
583+ entries. sort ( ) ;
584+
585+ let mut seq = serializer. serialize_seq ( Some ( entries. len ( ) ) ) ?;
586+ for value in entries {
587+ seq. serialize_element ( value) ?;
588+ }
589+ seq. end ( )
590+ }
591+
544592pub fn deserialize_hashmap < ' de , K , V , D , H > ( deserializer : D ) -> Result < HashMap < K , V , H > , D :: Error >
545593where
546594 K : Deserialize < ' de > + Eq + Hash ,
0 commit comments