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}