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 bevy_platform::sync::Arc;
205
206#[cfg(not(feature = "std"))]
207use bevy_platform::prelude::Vec;
208
209use crate::{
210    collector::ArcGc,
211    event::{NodeEventType, ParamData},
212};
213
214use smallvec::SmallVec;
215
216mod collections;
217mod leaf;
218mod memo;
219mod notify;
220
221pub use memo::Memo;
222pub use notify::Notify;
223
224/// Derive macros for diffing and patching.
225pub use firewheel_macros::{Diff, Patch, RealtimeClone};
226
227/// Fine-grained parameter diffing.
228///
229/// This trait allows a type to perform diffing on itself,
230/// generating events that another instance can use to patch
231/// itself.
232///
233/// For more information, see the [module docs][self].
234///
235/// # Examples
236///
237/// For most use cases, [`Diff`] is fairly straightforward.
238///
239/// ```
240/// use firewheel_core::diff::{Diff, PathBuilder};
241///
242/// #[derive(Diff, Clone)]
243/// struct MyParams {
244///     a: f32,
245///     b: f32,
246/// }
247///
248/// let mut params = MyParams {
249///     a: 1.0,
250///     b: 1.0,
251/// };
252///
253/// // This "baseline" instance allows us to keep track
254/// // of what's changed over time.
255/// let baseline = params.clone();
256///
257/// // A single mutation to a "leaf" type like `f32` will
258/// // produce a single event.
259/// params.a = 0.5;
260///
261/// // `Vec<NodeEventType>` implements `EventQueue`, meaning we
262/// // don't necessarily need to keep track of `NodeID`s for event generation.
263/// let mut event_queue = Vec::new();
264/// // Top-level calls to diff should always provide a default path builder.
265/// params.diff(&baseline, PathBuilder::default(), &mut event_queue);
266///
267/// assert_eq!(event_queue.len(), 1);
268/// ```
269///
270/// When using Firewheel in a standalone context, the [`Memo`] type can
271/// simplify this process.
272///
273/// ```
274/// # use firewheel_core::diff::{Diff, PathBuilder};
275/// # #[derive(Diff, Clone)]
276/// # struct MyParams {
277/// #     a: f32,
278/// #     b: f32,
279/// # }
280/// use firewheel_core::diff::Memo;
281///
282/// let mut params_memo = Memo::new(MyParams {
283///     a: 1.0,
284///     b: 1.0,
285/// });
286///
287/// // `Memo` implements `DerefMut` on the wrapped type, allowing you
288/// // to use it almost transparently.
289/// params_memo.a = 0.5;
290///
291/// let mut event_queue = Vec::new();
292/// // This generates patches and brings the internally managed
293/// // baseline in sync.
294/// params_memo.update_memo(&mut event_queue);
295/// ```
296///
297/// # Manual implementation
298///
299/// Aggregate types like parameters should prefer the derive macro, but
300/// manual implementations can occasionally be handy. You should strive
301/// to match the derived data model for maximum compatibility.
302///
303/// ```
304/// use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
305/// # struct MyParams {
306/// #     a: f32,
307/// #     b: f32,
308/// # }
309///
310/// impl Diff for MyParams {
311///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
312///         // The diffing data model requires a unique path to each field.
313///         // Because this type can be arbitrarily nested, you should always
314///         // extend the provided path builder using `PathBuilder::with`.
315///         //
316///         // Because this is the first field, we'll extend the path with 0.
317///         self.a.diff(&baseline.a, path.with(0), event_queue);
318///         self.b.diff(&baseline.b, path.with(1), event_queue);
319///     }
320/// }
321/// ```
322///
323/// You can easily override a type's [`Diff`] implementation by simply
324/// doing comparisons by hand.
325///
326/// ```
327/// use firewheel_core::event::ParamData;
328/// # use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
329/// # struct MyParams {
330/// #     a: f32,
331/// #     b: f32,
332/// # }
333///
334/// impl Diff for MyParams {
335///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
336///         // The above is essentially equivalent to:
337///         if self.a != baseline.a {
338///             event_queue.push_param(ParamData::F32(self.a), path.with(0));
339///         }
340///
341///         if self.b != baseline.b {
342///             event_queue.push_param(ParamData::F32(self.b), path.with(1));
343///         }
344///     }
345/// }
346/// ```
347///
348/// If your type has invariants between fields that _must not_ be violated, you
349/// can consider the whole type a "leaf," similar to how [`Diff`] is implemented
350/// on primitives. Depending on the type's data, you may require an allocation.
351///
352/// ```
353/// # use firewheel_core::{diff::{Diff, PathBuilder, EventQueue}, event::ParamData};
354/// # #[derive(PartialEq, Clone)]
355/// # struct MyParams {
356/// #     a: f32,
357/// #     b: f32,
358/// # }
359/// impl Diff for MyParams {
360///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
361///         if self != baseline {
362///             // Note that if we consider the whole type to be a leaf, there
363///             // is no need to extend the path.
364///             event_queue.push_param(ParamData::any(self.clone()), path);
365///         }
366///     }
367/// }
368/// ```
369pub trait Diff {
370    /// Compare `self` to `baseline` and generate events to resolve any differences.
371    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E);
372}
373
374/// A path of indices that uniquely describes an arbitrarily nested field.
375#[derive(PartialEq, Eq, Clone, Debug)]
376pub enum ParamPath {
377    /// A path of one element.
378    ///
379    /// Parameters tend to be shallow structures, so allocations
380    /// can generally be avoided using this variant.
381    Single(u32),
382    /// When paths are more than one element, this variant keeps
383    /// the stack size to two pointers while avoiding double-indirection
384    /// in the range 2..=4.
385    Multi(ArcGc<[u32]>),
386}
387
388impl core::ops::Deref for ParamPath {
389    type Target = [u32];
390
391    fn deref(&self) -> &Self::Target {
392        match self {
393            Self::Single(single) => core::slice::from_ref(single),
394            Self::Multi(multi) => multi.as_ref(),
395        }
396    }
397}
398
399/// Fine-grained parameter patching.
400///
401/// This trait allows a type to perform patching on itself,
402/// applying changes generated from another instance.
403///
404/// For more information, see the [module docs][self].
405///
406/// # Examples
407///
408/// Like with [`Diff`], the typical [`Patch`] usage is simple.
409///
410/// ```
411/// use firewheel_core::{diff::Patch, event::*, node::*, log::*};
412///
413/// #[derive(Patch)]
414/// struct MyParams {
415///     a: f32,
416///     b: f32,
417/// }
418///
419/// struct MyProcessor {
420///     params: MyParams,
421/// }
422///
423/// impl AudioNodeProcessor for MyProcessor {
424///     fn process(
425///         &mut self,
426///         buffers: ProcBuffers,
427///         proc_info: &ProcInfo,
428///         events: &mut ProcEvents,
429///         _logger: &mut RealtimeLogger,
430///     ) -> ProcessStatus {
431///         // Synchronize `params` from the event list.
432///         for patch in events.drain_patches::<MyParams>() {
433///             self.params.apply(patch);
434///         }
435///
436///         // ...
437///
438///         ProcessStatus::outputs_not_silent()
439///     }
440/// }
441/// ```
442///
443/// If you need fine access to each patch, you can
444/// match on the patch type.
445///
446/// ```
447/// # use firewheel_core::{diff::{Patch}, event::*, node::*, log::*};
448/// # #[derive(Patch)]
449/// # struct MyParams {
450/// #     a: f32,
451/// #     b: f32,
452/// # }
453/// # struct MyProcessor {
454/// #    params: MyParams,
455/// # }
456/// impl AudioNodeProcessor for MyProcessor {
457///     fn process(
458///         &mut self,
459///         buffers: ProcBuffers,
460///         proc_info: &ProcInfo,
461///         events: &mut ProcEvents,
462///         _logger: &mut RealtimeLogger,
463///     ) -> ProcessStatus {
464///         for mut patch in events.drain_patches::<MyParams>() {
465///             // When you derive `Patch`, it creates an enum with variants
466///             // for each field.
467///             match &mut patch {
468///                 MyParamsPatch::A(a) => {
469///                     // You can mutate the patch itself if you want
470///                     // to constrain or modify values.
471///                     *a = a.clamp(0.0, 1.0);
472///                 }
473///                 MyParamsPatch::B(b) => {}
474///             }
475///
476///             // And / or apply it directly.
477///             self.params.apply(patch);
478///         }
479///
480///         // ...
481///
482///         ProcessStatus::outputs_not_silent()
483///     }
484/// }
485/// ```
486///
487/// # Manual implementation
488///
489/// Like with [`Diff`], types like parameters should prefer the [`Patch`] derive macro.
490/// Nonetheless, Firewheel provides a few tools to make manual implementations straightforward.
491///
492/// ```
493/// use firewheel_core::{diff::{Patch, PatchError}, event::ParamData};
494///
495/// struct MyParams {
496///     a: f32,
497///     b: bool,
498/// }
499///
500/// // To follow the derive macro convention, create an
501/// // enum with variants for each field.
502/// enum MyParamsPatch {
503///     A(f32),
504///     B(bool),
505/// }
506///
507/// impl Patch for MyParams {
508///     type Patch = MyParamsPatch;
509///
510///     fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError> {
511///         match path {
512///             [0] => {
513///                 // Types that exist in `ParamData`'s variants can use
514///                 // `try_into`.
515///                 let a = data.try_into()?;
516///                 Ok(MyParamsPatch::A(a))
517///             }
518///             [1] => {
519///                 let b = data.try_into()?;
520///                 Ok(MyParamsPatch::B(b))
521///             }
522///             _ => Err(PatchError::InvalidPath)
523///         }
524///     }
525///
526///     fn apply(&mut self, patch: Self::Patch) {
527///         match patch {
528///             MyParamsPatch::A(a) => self.a = a,
529///             MyParamsPatch::B(b) => self.b = b,
530///         }
531///     }
532/// }
533/// ```
534pub trait Patch {
535    /// A type's _patch_.
536    ///
537    /// This is a value that enumerates all the ways a type can be changed.
538    /// For leaf types (values that represent the smallest diffable unit) like `f32`,
539    /// this is just the type itself. For aggregate types like structs, this should
540    /// be an enum over each field.
541    ///
542    /// ```
543    /// struct FilterParams {
544    ///     frequency: f32,
545    ///     quality: f32,
546    /// }
547    ///
548    /// enum FilterParamsPatch {
549    ///     Frequency(f32),
550    ///     Quality(f32),
551    /// }
552    /// ```
553    ///
554    /// This type is converted from [`NodeEventType::Param`] in the [`patch`][Patch::patch]
555    /// method.
556    type Patch;
557
558    /// Construct a patch from a parameter event.
559    ///
560    /// This converts the intermediate representation in [`NodeEventType::Param`] into
561    /// a concrete value, making it easy to manipulate the event in audio processors.
562    ///
563    /// ```
564    /// # use firewheel_core::{diff::Patch, event::{ProcEvents, NodeEventType}};
565    /// # fn patching(mut event_list: ProcEvents) {
566    /// #[derive(Patch, Default)]
567    /// struct FilterParams {
568    ///     frequency: f32,
569    ///     quality: f32,
570    /// }
571    ///
572    /// let mut filter_params = FilterParams::default();
573    ///
574    /// for event in event_list.drain() {
575    ///     match event {
576    ///         NodeEventType::Param { data, path } => {
577    ///             let Ok(patch) = FilterParams::patch(&data, &path) else {
578    ///                 return;
579    ///             };
580    ///
581    ///             // You can match on the patch directly
582    ///             match &patch {
583    ///                 FilterParamsPatch::Frequency(f) => {
584    ///                     // Handle frequency event...
585    ///                 }
586    ///                 FilterParamsPatch::Quality(q) => {
587    ///                     // Handle quality event...
588    ///                 }
589    ///             }
590    ///
591    ///             // And/or apply it.
592    ///             filter_params.apply(patch);
593    ///         }
594    ///         _ => {}
595    ///     }
596    /// }
597    /// # }
598    /// ```
599    fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError>;
600
601    /// Construct a patch from a node event.
602    ///
603    /// This is a convenience wrapper around [`patch`][Patch::patch], discarding
604    /// errors and node events besides [`NodeEventType::Param`].
605    fn patch_event(event: &NodeEventType) -> Option<Self::Patch> {
606        match event {
607            NodeEventType::Param { data, path } => Some(Self::patch(data, path).ok()?),
608            _ => None,
609        }
610    }
611
612    /// Apply a patch.
613    ///
614    /// This will generally be called from within
615    /// the audio thread, so real-time constraints should be respected.
616    ///
617    /// Typically, you'll call this within [`drain_patches`].
618    ///
619    /// ```
620    /// # use firewheel_core::{diff::Patch, event::{ProcEvents, NodeEventType}};
621    /// # fn patching(mut event_list: ProcEvents) {
622    /// #[derive(Patch, Default)]
623    /// struct FilterParams {
624    ///     frequency: f32,
625    ///     quality: f32,
626    /// }
627    ///
628    /// let mut filter_params = FilterParams::default();
629    /// for patch in event_list.drain_patches::<FilterParams>() { filter_params.apply(patch); }
630    /// # }
631    /// ```
632    ///
633    /// [`drain_patches`]: crate::event::ProcEvents::drain_patches
634    fn apply(&mut self, patch: Self::Patch);
635}
636
637/// A trait which signifies that a struct implements `Clone`, cloning
638/// does not allocate or deallocate data, and the data will not be
639/// dropped on the audio thread if the struct is dropped.
640pub trait RealtimeClone: Clone {}
641
642impl<T: ?Sized + Send + Sync + 'static> RealtimeClone for ArcGc<T> {}
643
644// NOTE: Using a `SmallVec` instead of a `Box<[u32]>` yields
645// around an 8% performance uplift for cases where the path
646// is in the range 2..=4.
647//
648// Beyond this range, the performance drops off around 13%.
649//
650// Since this avoids extra allocations in the common < 5
651// scenario, this seems like a reasonable tradeoff.
652
653/// A simple builder for [`ParamPath`].
654///
655/// When performing top-level diffing, you should provide a default
656/// [`PathBuilder`].
657///
658/// ```
659/// # use firewheel_core::{diff::{Diff, PathBuilder}, event::*, node::*};
660/// #[derive(Diff, Default, Clone)]
661/// struct FilterNode {
662///     frequency: f32,
663///     quality: f32,
664/// }
665///
666/// let baseline = FilterNode::default();
667/// let node = baseline.clone();
668///
669/// let mut events = Vec::new();
670/// node.diff(&baseline, PathBuilder::default(), &mut events);
671/// ```
672#[derive(Debug, Default, Clone)]
673pub struct PathBuilder(SmallVec<[u32; 4]>);
674
675impl PathBuilder {
676    /// Clone the path, appending the index to the returned value.
677    pub fn with(&self, index: u32) -> Self {
678        let mut new = self.0.clone();
679        new.push(index);
680        Self(new)
681    }
682
683    /// Convert this path builder into a [`ParamPath`].
684    pub fn build(self) -> ParamPath {
685        if self.0.len() == 1 {
686            ParamPath::Single(self.0[0])
687        } else {
688            ParamPath::Multi(ArcGc::new_unsized(|| Arc::<[u32]>::from(self.0.as_slice())))
689        }
690    }
691}
692
693/// An event queue for diffing.
694pub trait EventQueue {
695    /// Push an event to the queue.
696    fn push(&mut self, data: NodeEventType);
697
698    /// Push an event to the queue.
699    ///
700    /// This is a convenience method for constructing a [`NodeEventType`]
701    /// from param data and a path.
702    #[inline(always)]
703    fn push_param(&mut self, data: impl Into<ParamData>, path: PathBuilder) {
704        self.push(NodeEventType::Param {
705            data: data.into(),
706            path: path.build(),
707        });
708    }
709}
710
711impl EventQueue for Vec<NodeEventType> {
712    fn push(&mut self, data: NodeEventType) {
713        self.push(data);
714    }
715}
716
717/// An error encountered when patching a type
718/// from [`ParamData`].
719#[derive(Debug, Clone)]
720pub enum PatchError {
721    /// The provided path does not match any children.
722    InvalidPath,
723    /// The data supplied for the path did not match the expected type.
724    InvalidData,
725}
726
727#[cfg(test)]
728mod test {
729    use super::*;
730
731    #[derive(Debug, Clone, Diff, Patch, PartialEq)]
732    struct StructDiff {
733        a: f32,
734        b: bool,
735    }
736
737    #[test]
738    fn test_simple_diff() {
739        let mut a = StructDiff { a: 1.0, b: false };
740
741        let mut b = a.clone();
742
743        a.a = 0.5;
744
745        let mut patches = Vec::new();
746        a.diff(&b, PathBuilder::default(), &mut patches);
747
748        assert_eq!(patches.len(), 1);
749
750        for patch in patches.iter() {
751            let patch = StructDiff::patch_event(patch).unwrap();
752
753            assert!(matches!(patch, StructDiffPatch::A(a) if a == 0.5));
754
755            b.apply(patch);
756        }
757
758        assert_eq!(a, b);
759    }
760
761    #[derive(Debug, Clone, Diff, Patch, PartialEq)]
762    enum DiffingExample {
763        Unit,
764        Tuple(f32, f32),
765        Struct { a: f32, b: f32 },
766    }
767
768    #[test]
769    fn test_enum_diff() {
770        let mut baseline = DiffingExample::Tuple(1.0, 0.0);
771        let value = DiffingExample::Tuple(1.0, 1.0);
772
773        let mut messages = Vec::new();
774        value.diff(&baseline, PathBuilder::default(), &mut messages);
775
776        assert_eq!(messages.len(), 1);
777        baseline.apply(DiffingExample::patch_event(&messages.pop().unwrap()).unwrap());
778        assert_eq!(baseline, value);
779    }
780
781    #[test]
782    fn test_enum_switch_variant() {
783        let mut baseline = DiffingExample::Unit;
784        let value = DiffingExample::Struct { a: 1.0, b: 1.0 };
785
786        let mut messages = Vec::new();
787        value.diff(&baseline, PathBuilder::default(), &mut messages);
788
789        assert_eq!(messages.len(), 1);
790        baseline.apply(DiffingExample::patch_event(&messages.pop().unwrap()).unwrap());
791        assert_eq!(baseline, value);
792    }
793}