firewheel_core/diff/mod.rs
1//! Traits and derive macros for diffing and patching.
2//!
3//! _Diffing_ is the process of comparing a piece of data to some
4//! baseline and generating events to describe the differences.
5//! _Patching_ takes these events and applies them to another
6//! instance of this data. The [`Diff`] and [`Patch`] traits facilitate _fine-grained_
7//! event generation, meaning they'll generate events for
8//! only what's changed.
9//!
10//! In typical usage, [`Diff`] will be called in non-realtime contexts
11//! like game logic, whereas [`Patch`] will be called directly within
12//! audio processors. Consequently, [`Patch`] has been optimized for
13//! maximum performance and realtime predictability.
14//!
15//! [`Diff`] and [`Patch`] are [derivable](https://doc.rust-lang.org/book/appendix-03-derivable-traits.html),
16//! and most aggregate types should prefer the derive macros over
17//! manual implementations since the diffing data model is not
18//! yet guaranteed to be stable.
19//!
20//! # Examples
21//!
22//! Aggregate types like node parameters can derive
23//! [`Diff`] and [`Patch`] as long as each field also
24//! implements these traits.
25//!
26//! ```
27//! use firewheel_core::diff::{Diff, Patch};
28//!
29//! #[derive(Diff, Patch)]
30//! struct MyParams {
31//! a: f32,
32//! b: (bool, bool),
33//! }
34//! ```
35//!
36//! The derived implementation produces fine-grained
37//! events, making it easy to keep your audio processors in sync
38//! with the rest of your code with minimal overhead.
39//!
40//! ```
41//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
42//! # #[derive(Diff, Patch, Clone, PartialEq, Debug)]
43//! # struct MyParams {
44//! # a: f32,
45//! # b: (bool, bool),
46//! # }
47//! let mut params = MyParams {
48//! a: 1.0,
49//! b: (false, false),
50//! };
51//! let mut baseline = params.clone();
52//!
53//! // A change to any arbitrarily nested parameter
54//! // will produce a single event.
55//! params.b.0 = true;
56//!
57//! let mut event_queue = Vec::new();
58//! params.diff(&baseline, PathBuilder::default(), &mut event_queue);
59//!
60//! // When we apply this patch to another instance of
61//! // the same type, it will be brought in sync.
62//! baseline.apply(MyParams::patch_event(&event_queue[0]).unwrap());
63//! assert_eq!(params, baseline);
64//!
65//! ```
66//!
67//! Both traits can also be derived on enums.
68//!
69//! ```
70//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
71//! #[derive(Diff, Patch, Clone, PartialEq)]
72//! enum MyParams {
73//! Unit,
74//! Tuple(f32, f32),
75//! Struct { a: f32, b: f32 },
76//! }
77//! ```
78//!
79//! However, note that enums will only perform coarse diffing. If a single
80//! field in a variant changes, the entire variant will still be sent.
81//! As a result, you can accidentally introduce allocations
82//! in audio processors by including types that allocate on clone.
83//!
84//! ```
85//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
86//! #[derive(Diff, Patch, Clone, PartialEq)]
87//! enum MaybeAllocates {
88//! A(Vec<f32>), // Will cause allocations in `Patch`!
89//! B(f32),
90//! }
91//! ```
92//!
93//! [`Clone`] types are permitted because [`Clone`] does
94//! not always imply allocation. For example, consider
95//! the type:
96//!
97//! ```
98//! use firewheel_core::{collector::ArcGc, sample_resource::SampleResource};
99//!
100//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
101//! #[derive(Diff, Patch, Clone, PartialEq)]
102//! enum SoundSource {
103//! Sample(ArcGc<dyn SampleResource>), // Will _not_ cause allocations in `Patch`.
104//! Frequency(f32),
105//! }
106//! ```
107//!
108//! This bound may be restricted to [`Copy`] in the future.
109//!
110//! # Macro attributes
111//!
112//! [`Diff`] and [`Patch`] each accept a single attribute, `skip`, on
113//! struct fields. Any field annotated with `skip` will not receive
114//! diffing or patching, which may be useful for atomically synchronized
115//! types.
116//! ```
117//! use firewheel_core::{collector::ArcGc, diff::{Diff, Patch}};
118//! use bevy_platform::sync::atomic::AtomicUsize;
119//!
120//! #[derive(Diff, Patch)]
121//! struct MultiParadigm {
122//! normal_field: f32,
123//! #[diff(skip)]
124//! atomic_field: ArcGc<AtomicUsize>,
125//! }
126//! ```
127//!
128//! # Data model
129//!
130//! Diffing events are represented as `(data, path)` pairs. This approach
131//! provides a few important advantages. For one, the fields within nearly
132//! all Rust types can be uniquely addressed with index paths.
133//!
134//! ```
135//! # use firewheel_core::diff::{Diff, Patch};
136//! #[derive(Diff, Patch, Default)]
137//! struct MyParams {
138//! a: f32,
139//! b: (bool, bool),
140//! }
141//!
142//! let params = MyParams::default();
143//!
144//! params.a; // [0]
145//! params.b.0; // [1, 0]
146//! params.b.1; // [1, 1]
147//! ```
148//!
149//! Since these paths can be arbitrarily long, you can arbitrarily
150//! nest implementors of [`Diff`] and [`Patch`].
151//!
152//! ```
153//! # use firewheel_core::diff::{Diff, Patch};
154//! # #[derive(Diff, Patch, Default)]
155//! # struct MyParams {
156//! # a: f32,
157//! # b: (bool, bool),
158//! # }
159//! #[derive(Diff, Patch)]
160//! struct Aggregate {
161//! a: MyParams,
162//! b: MyParams,
163//! // Indexable types work great too!
164//! collection: [MyParams; 8],
165//! }
166//! ```
167//!
168//! Furthermore, since we build up paths during calls to
169//! [`Diff`], the derive macros and implementations only need
170//! to worry about _local indexing._ And, since the paths
171//! are built only during [`Diff`], we can traverse them
172//! highly performantly during [`Patch`] calls in audio processors.
173//!
174//! Firewheel provides a number of primitive types in [`ParamData`]
175//! that cover most use-cases for audio parameters. For anything
176//! not covered in the concrete variants, you can insert arbitrary
177//! data into [`ParamData::Any`]. Since this only incurs allocations
178//! during [`Diff`], this will still be generally performant.
179//!
180//! # Preserving invariants
181//!
182//! Firewheel's [`Patch`] derive macro cannot make assurances about
183//! your type's invariants. If two types `A` and `B` have similar structures:
184//!
185//! ```
186//! struct A {
187//! pub field_one: f32,
188//! pub field_two: f32,
189//! }
190//!
191//! struct B {
192//! special_field_one: f32,
193//! special_field_two: f32,
194//! }
195//! ```
196//!
197//! Then events produced for `A` are also valid for `B`.
198//!
199//! Receiving events produced by the wrong type is unlikely. Most
200//! types will not need special handling to preserve invariants.
201//! However, if your invariants are safety-critical, you _must_
202//! implement [`Patch`] manually.
203
204use crate::{
205 collector::ArcGc,
206 event::{NodeEventType, ParamData},
207};
208
209use smallvec::SmallVec;
210
211mod collections;
212mod leaf;
213mod memo;
214mod notify;
215
216pub use memo::Memo;
217pub use notify::Notify;
218
219/// Derive macros for diffing and patching.
220pub use firewheel_macros::{Diff, Patch};
221
222/// Fine-grained parameter diffing.
223///
224/// This trait allows a type to perform diffing on itself,
225/// generating events that another instance can use to patch
226/// itself.
227///
228/// For more information, see the [module docs][self].
229///
230/// # Examples
231///
232/// For most use cases, [`Diff`] is fairly straightforward.
233///
234/// ```
235/// use firewheel_core::diff::{Diff, PathBuilder};
236///
237/// #[derive(Diff, Clone)]
238/// struct MyParams {
239/// a: f32,
240/// b: f32,
241/// }
242///
243/// let mut params = MyParams {
244/// a: 1.0,
245/// b: 1.0,
246/// };
247///
248/// // This "baseline" instance allows us to keep track
249/// // of what's changed over time.
250/// let baseline = params.clone();
251///
252/// // A single mutation to a "leaf" type like `f32` will
253/// // produce a single event.
254/// params.a = 0.5;
255///
256/// // `Vec<NodeEventType>` implements `EventQueue`, meaning we
257/// // don't necessarily need to keep track of `NodeID`s for event generation.
258/// let mut event_queue = Vec::new();
259/// // Top-level calls to diff should always provide a default path builder.
260/// params.diff(&baseline, PathBuilder::default(), &mut event_queue);
261///
262/// assert_eq!(event_queue.len(), 1);
263/// ```
264///
265/// When using Firewheel in a standalone context, the [`Memo`] type can
266/// simplify this process.
267///
268/// ```
269/// # use firewheel_core::diff::{Diff, PathBuilder};
270/// # #[derive(Diff, Clone)]
271/// # struct MyParams {
272/// # a: f32,
273/// # b: f32,
274/// # }
275/// use firewheel_core::diff::Memo;
276///
277/// let mut params_memo = Memo::new(MyParams {
278/// a: 1.0,
279/// b: 1.0,
280/// });
281///
282/// // `Memo` implements `DerefMut` on the wrapped type, allowing you
283/// // to use it almost transparently.
284/// params_memo.a = 0.5;
285///
286/// let mut event_queue = Vec::new();
287/// // This generates patches and brings the internally managed
288/// // baseline in sync.
289/// params_memo.update_memo(&mut event_queue);
290/// ```
291///
292/// # Manual implementation
293///
294/// Aggregate types like parameters should prefer the derive macro, but
295/// manual implementations can occasionally be handy. You should strive
296/// to match the derived data model for maximum compatibility.
297///
298/// ```
299/// use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
300/// # struct MyParams {
301/// # a: f32,
302/// # b: f32,
303/// # }
304///
305/// impl Diff for MyParams {
306/// fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
307/// // The diffing data model requires a unique path to each field.
308/// // Because this type can be arbitrarily nested, you should always
309/// // extend the provided path builder using `PathBuilder::with`.
310/// //
311/// // Because this is the first field, we'll extend the path with 0.
312/// self.a.diff(&baseline.a, path.with(0), event_queue);
313/// self.b.diff(&baseline.b, path.with(1), event_queue);
314/// }
315/// }
316/// ```
317///
318/// You can easily override a type's [`Diff`] implementation by simply
319/// doing comparisons by hand.
320///
321/// ```
322/// use firewheel_core::event::ParamData;
323/// # use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
324/// # struct MyParams {
325/// # a: f32,
326/// # b: f32,
327/// # }
328///
329/// impl Diff for MyParams {
330/// fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
331/// // The above is essentially equivalent to:
332/// if self.a != baseline.a {
333/// event_queue.push_param(ParamData::F32(self.a), path.with(0));
334/// }
335///
336/// if self.b != baseline.b {
337/// event_queue.push_param(ParamData::F32(self.b), path.with(1));
338/// }
339/// }
340/// }
341/// ```
342///
343/// If your type has invariants between fields that _must not_ be violated, you
344/// can consider the whole type a "leaf," similar to how [`Diff`] is implemented
345/// on primitives. Depending on the type's data, you may require an allocation.
346///
347/// ```
348/// # use firewheel_core::{diff::{Diff, PathBuilder, EventQueue}, event::ParamData};
349/// # #[derive(PartialEq, Clone)]
350/// # struct MyParams {
351/// # a: f32,
352/// # b: f32,
353/// # }
354/// impl Diff for MyParams {
355/// fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
356/// if self != baseline {
357/// // Note that if we consider the whole type to be a leaf, there
358/// // is no need to extend the path.
359/// event_queue.push_param(ParamData::any(self.clone()), path);
360/// }
361/// }
362/// }
363/// ```
364pub trait Diff {
365 /// Compare `self` to `baseline` and generate events to resolve any differences.
366 fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E);
367}
368
369/// A path of indices that uniquely describes an arbitrarily nested field.
370#[derive(PartialEq, Eq)]
371pub enum ParamPath {
372 /// A path of one element.
373 ///
374 /// Parameters tend to be shallow structures, so allocations
375 /// can generally be avoided using this variant.
376 Single(u32),
377 /// When paths are more than one element, this variant keeps
378 /// the stack size to two pointers while avoiding double-indirection
379 /// in the range 2..=4.
380 Multi(ArcGc<SmallVec<[u32; 4]>>),
381}
382
383impl core::ops::Deref for ParamPath {
384 type Target = [u32];
385
386 fn deref(&self) -> &Self::Target {
387 match self {
388 Self::Single(single) => core::slice::from_ref(single),
389 Self::Multi(multi) => multi.as_ref(),
390 }
391 }
392}
393
394/// Fine-grained parameter patching.
395///
396/// This trait allows a type to perform patching on itself,
397/// applying changes generated from another instance.
398///
399/// For more information, see the [module docs][self].
400///
401/// # Examples
402///
403/// Like with [`Diff`], the typical [`Patch`] usage is simple.
404///
405/// ```
406/// use firewheel_core::{diff::Patch, event::*, node::*};
407///
408/// #[derive(Patch)]
409/// struct MyParams {
410/// a: f32,
411/// b: f32,
412/// }
413///
414/// struct MyProcessor {
415/// params: MyParams,
416/// }
417///
418/// impl AudioNodeProcessor for MyProcessor {
419/// fn process(
420/// &mut self,
421/// buffers: ProcBuffers,
422/// proc_info: &ProcInfo,
423/// mut events: NodeEventList,
424/// ) -> ProcessStatus {
425/// // Synchronize `params` from the event list.
426/// events.for_each_patch::<MyParams>(|patch| self.params.apply(patch));
427///
428/// // ...
429///
430/// ProcessStatus::outputs_not_silent()
431/// }
432/// }
433/// ```
434///
435/// If you need fine access to each patch, you can
436/// match on the patch type.
437///
438/// ```
439/// # use firewheel_core::{diff::{Patch}, event::*, node::*};
440/// # #[derive(Patch)]
441/// # struct MyParams {
442/// # a: f32,
443/// # b: f32,
444/// # }
445/// # struct MyProcessor {
446/// # params: MyParams,
447/// # }
448/// impl AudioNodeProcessor for MyProcessor {
449/// fn process(
450/// &mut self,
451/// buffers: ProcBuffers,
452/// proc_info: &ProcInfo,
453/// mut events: NodeEventList,
454/// ) -> ProcessStatus {
455/// events.for_each_patch::<MyParams>(|mut patch| {
456/// // When you derive `Patch`, it creates an enum with variants
457/// // for each field.
458/// match &mut patch {
459/// MyParamsPatch::A(a) => {
460/// // You can mutate the patch itself if you want
461/// // to constrain or modify values.
462/// *a = a.clamp(0.0, 1.0);
463/// }
464/// MyParamsPatch::B(b) => {}
465/// }
466///
467/// // And / or apply it directly.
468/// self.params.apply(patch);
469/// });
470///
471/// // ...
472///
473/// ProcessStatus::outputs_not_silent()
474/// }
475/// }
476/// ```
477///
478/// # Manual implementation
479///
480/// Like with [`Diff`], types like parameters should prefer the [`Patch`] derive macro.
481/// Nonetheless, Firewheel provides a few tools to make manual implementations straightforward.
482///
483/// ```
484/// use firewheel_core::{diff::{Patch, PatchError}, event::ParamData};
485///
486/// struct MyParams {
487/// a: f32,
488/// b: bool,
489/// }
490///
491/// // To follow the derive macro convention, create an
492/// // enum with variants for each field.
493/// enum MyParamsPatch {
494/// A(f32),
495/// B(bool),
496/// }
497///
498/// impl Patch for MyParams {
499/// type Patch = MyParamsPatch;
500///
501/// fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError> {
502/// match path {
503/// [0] => {
504/// // Types that exist in `ParamData`'s variants can use
505/// // `try_into`.
506/// let a = data.try_into()?;
507/// Ok(MyParamsPatch::A(a))
508/// }
509/// [1] => {
510/// let b = data.try_into()?;
511/// Ok(MyParamsPatch::B(b))
512/// }
513/// _ => Err(PatchError::InvalidPath)
514/// }
515/// }
516///
517/// fn apply(&mut self, patch: Self::Patch) {
518/// match patch {
519/// MyParamsPatch::A(a) => self.a = a,
520/// MyParamsPatch::B(b) => self.b = b,
521/// }
522/// }
523/// }
524/// ```
525pub trait Patch {
526 /// A type's _patch_.
527 ///
528 /// This is a value that enumerates all the ways a type can be changed.
529 /// For leaf types (values that represent the smallest diffable unit) like `f32`,
530 /// this is just the type itself. For aggregate types like structs, this should
531 /// be an enum over each field.
532 ///
533 /// ```
534 /// struct FilterParams {
535 /// frequency: f32,
536 /// quality: f32,
537 /// }
538 ///
539 /// enum FilterParamsPatch {
540 /// Frequency(f32),
541 /// Quality(f32),
542 /// }
543 /// ```
544 ///
545 /// This type is converted from [`NodeEventType::Param`] in the [`patch`][Patch::patch]
546 /// method.
547 type Patch;
548
549 /// Construct a patch from a parameter event.
550 ///
551 /// This converts the intermediate representation in [`NodeEventType::Param`] into
552 /// a concrete value, making it easy to manipulate the event in audio processors.
553 ///
554 /// ```
555 /// # use firewheel_core::{diff::Patch, event::{NodeEventList, NodeEventType}};
556 /// # fn patching(mut event_list: NodeEventList) {
557 /// #[derive(Patch, Default)]
558 /// struct FilterParams {
559 /// frequency: f32,
560 /// quality: f32,
561 /// }
562 ///
563 /// let mut filter_params = FilterParams::default();
564 ///
565 /// event_list.for_each(|e| {
566 /// match e {
567 /// NodeEventType::Param { data, path } => {
568 /// let Ok(patch) = FilterParams::patch(data, path) else {
569 /// return;
570 /// };
571 ///
572 /// // You can match on the patch directly
573 /// match &patch {
574 /// FilterParamsPatch::Frequency(f) => {
575 /// // Handle frequency event...
576 /// }
577 /// FilterParamsPatch::Quality(q) => {
578 /// // Handle quality event...
579 /// }
580 /// }
581 ///
582 /// // And/or apply it.
583 /// filter_params.apply(patch);
584 /// }
585 /// _ => {}
586 /// }
587 /// });
588 /// # }
589 /// ```
590 fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError>;
591
592 /// Construct a patch from a node event.
593 ///
594 /// This is a convenience wrapper around [`patch`][Patch::patch], discarding
595 /// errors and node events besides [`NodeEventType::Param`].
596 fn patch_event(event: &NodeEventType) -> Option<Self::Patch> {
597 match event {
598 NodeEventType::Param { data, path } => Some(Self::patch(data, path).ok()?),
599 _ => None,
600 }
601 }
602
603 /// Apply a patch.
604 ///
605 /// This will generally be called from within
606 /// the audio thread, so real-time constraints should be respected.
607 ///
608 /// Typically, you'll call this within [`for_each_patch`].
609 ///
610 /// ```
611 /// # use firewheel_core::{diff::Patch, event::{NodeEventList, NodeEventType}};
612 /// # fn patching(mut event_list: NodeEventList) {
613 /// #[derive(Patch, Default)]
614 /// struct FilterParams {
615 /// frequency: f32,
616 /// quality: f32,
617 /// }
618 ///
619 /// let mut filter_params = FilterParams::default();
620 /// event_list.for_each_patch::<FilterParams>(|patch| filter_params.apply(patch));
621 /// # }
622 /// ```
623 ///
624 /// [`for_each_patch`]: crate::event::NodeEventList::for_each_patch
625 fn apply(&mut self, patch: Self::Patch);
626}
627
628// NOTE: Using a `SmallVec` instead of a `Box<[u32]>` yields
629// around an 8% performance uplift for cases where the path
630// is in the range 2..=4.
631//
632// Beyond this range, the performance drops off around 13%.
633//
634// Since this avoids extra allocations in the common < 5
635// scenario, this seems like a reasonable tradeoff.
636
637/// A simple builder for [`ParamPath`].
638///
639/// When performing top-level diffing, you should provide a default
640/// [`PathBuilder`].
641///
642/// ```
643/// # use firewheel_core::{diff::{Diff, PathBuilder}, event::*, node::*};
644/// #[derive(Diff, Default, Clone)]
645/// struct FilterNode {
646/// frequency: f32,
647/// quality: f32,
648/// }
649///
650/// let baseline = FilterNode::default();
651/// let node = baseline.clone();
652///
653/// let mut events = Vec::new();
654/// node.diff(&baseline, PathBuilder::default(), &mut events);
655/// ```
656#[derive(Debug, Default, Clone)]
657pub struct PathBuilder(SmallVec<[u32; 4]>);
658
659impl PathBuilder {
660 /// Clone the path, appending the index to the returned value.
661 pub fn with(&self, index: u32) -> Self {
662 let mut new = self.0.clone();
663 new.push(index);
664 Self(new)
665 }
666
667 /// Convert this path builder into a [`ParamPath`].
668 pub fn build(self) -> ParamPath {
669 if self.0.len() == 1 {
670 ParamPath::Single(self.0[0])
671 } else {
672 ParamPath::Multi(ArcGc::new(self.0.clone()))
673 }
674 }
675}
676
677/// An event queue for diffing.
678pub trait EventQueue {
679 /// Push an event to the queue.
680 fn push(&mut self, data: NodeEventType);
681
682 /// Push an event to the queue.
683 ///
684 /// This is a convenience method for constructing a [`NodeEventType`]
685 /// from param data and a path.
686 #[inline(always)]
687 fn push_param(&mut self, data: impl Into<ParamData>, path: PathBuilder) {
688 self.push(NodeEventType::Param {
689 data: data.into(),
690 path: path.build(),
691 });
692 }
693}
694
695impl EventQueue for Vec<NodeEventType> {
696 fn push(&mut self, data: NodeEventType) {
697 self.push(data);
698 }
699}
700
701/// An error encountered when patching a type
702/// from [`ParamData`].
703#[derive(Debug, Clone)]
704pub enum PatchError {
705 /// The provided path does not match any children.
706 InvalidPath,
707 /// The data supplied for the path did not match the expected type.
708 InvalidData,
709}
710
711#[cfg(test)]
712mod test {
713 use super::*;
714
715 #[derive(Debug, Clone, Diff, Patch, PartialEq)]
716 struct StructDiff {
717 a: f32,
718 b: bool,
719 }
720
721 #[test]
722 fn test_simple_diff() {
723 let mut a = StructDiff { a: 1.0, b: false };
724
725 let mut b = a.clone();
726
727 a.a = 0.5;
728
729 let mut patches = Vec::new();
730 a.diff(&b, PathBuilder::default(), &mut patches);
731
732 assert_eq!(patches.len(), 1);
733
734 for patch in &patches {
735 let patch = StructDiff::patch_event(patch).unwrap();
736
737 assert!(matches!(patch, StructDiffPatch::A(a) if a == 0.5));
738
739 b.apply(patch);
740 }
741
742 assert_eq!(a, b);
743 }
744
745 #[derive(Debug, Clone, Diff, Patch, PartialEq)]
746 enum DiffingExample {
747 Unit,
748 Tuple(f32, f32),
749 Struct { a: f32, b: f32 },
750 }
751
752 #[test]
753 fn test_enum_diff() {
754 let mut baseline = DiffingExample::Tuple(1.0, 0.0);
755 let value = DiffingExample::Tuple(1.0, 1.0);
756
757 let mut messages = Vec::new();
758 value.diff(&baseline, PathBuilder::default(), &mut messages);
759
760 assert_eq!(messages.len(), 1);
761 baseline.apply(DiffingExample::patch_event(&messages[0]).unwrap());
762 assert_eq!(baseline, value);
763 }
764
765 #[test]
766 fn test_enum_switch_variant() {
767 let mut baseline = DiffingExample::Unit;
768 let value = DiffingExample::Struct { a: 1.0, b: 1.0 };
769
770 let mut messages = Vec::new();
771 value.diff(&baseline, PathBuilder::default(), &mut messages);
772
773 assert_eq!(messages.len(), 1);
774 baseline.apply(DiffingExample::patch_event(&messages[0]).unwrap());
775 assert_eq!(baseline, value);
776 }
777}