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 + Send + Sync + 'static>), // 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//! with high performance 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, NotifyID};
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 events(
425/// &mut self,
426/// info: &ProcInfo,
427/// events: &mut ProcEvents,
428/// extra: &mut ProcExtra,
429/// ) {
430/// // Synchronize `params` from the event list.
431/// for patch in events.drain_patches::<MyParams>() {
432/// self.params.apply(patch);
433/// }
434/// }
435/// }
436/// ```
437///
438/// If you need fine access to each patch, you can
439/// match on the patch type.
440///
441/// ```
442/// # use firewheel_core::{diff::{Patch}, event::*, node::*, log::*};
443/// # #[derive(Patch)]
444/// # struct MyParams {
445/// # a: f32,
446/// # b: f32,
447/// # }
448/// # struct MyProcessor {
449/// # params: MyParams,
450/// # }
451/// impl AudioNodeProcessor for MyProcessor {
452/// fn events(
453/// &mut self,
454/// info: &ProcInfo,
455/// events: &mut ProcEvents,
456/// extra: &mut ProcExtra,
457/// ) {
458/// for mut patch in events.drain_patches::<MyParams>() {
459/// // When you derive `Patch`, it creates an enum with variants
460/// // for each field.
461/// match &mut patch {
462/// MyParamsPatch::A(a) => {
463/// // You can mutate the patch itself if you want
464/// // to constrain or modify values.
465/// *a = a.clamp(0.0, 1.0);
466/// }
467/// MyParamsPatch::B(b) => {}
468/// }
469///
470/// // And / or apply it directly.
471/// self.params.apply(patch);
472/// }
473/// }
474/// }
475/// ```
476///
477/// # Manual implementation
478///
479/// Like with [`Diff`], types like parameters should prefer the [`Patch`] derive macro.
480/// Nonetheless, Firewheel provides a few tools to make manual implementations straightforward.
481///
482/// ```
483/// use firewheel_core::{diff::{Patch, PatchError}, event::ParamData};
484///
485/// struct MyParams {
486/// a: f32,
487/// b: bool,
488/// }
489///
490/// // To follow the derive macro convention, create an
491/// // enum with variants for each field.
492/// enum MyParamsPatch {
493/// A(f32),
494/// B(bool),
495/// }
496///
497/// impl Patch for MyParams {
498/// type Patch = MyParamsPatch;
499///
500/// fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError> {
501/// match path {
502/// [0] => {
503/// // Types that exist in `ParamData`'s variants can use
504/// // `try_into`.
505/// let a = data.try_into()?;
506/// Ok(MyParamsPatch::A(a))
507/// }
508/// [1] => {
509/// let b = data.try_into()?;
510/// Ok(MyParamsPatch::B(b))
511/// }
512/// _ => Err(PatchError::InvalidPath)
513/// }
514/// }
515///
516/// fn apply(&mut self, patch: Self::Patch) {
517/// match patch {
518/// MyParamsPatch::A(a) => self.a = a,
519/// MyParamsPatch::B(b) => self.b = b,
520/// }
521/// }
522/// }
523/// ```
524pub trait Patch {
525 /// A type's _patch_.
526 ///
527 /// This is a value that enumerates all the ways a type can be changed.
528 /// For leaf types (values that represent the smallest diffable unit) like `f32`,
529 /// this is just the type itself. For aggregate types like structs, this should
530 /// be an enum over each field.
531 ///
532 /// ```
533 /// struct FilterParams {
534 /// frequency: f32,
535 /// quality: f32,
536 /// }
537 ///
538 /// enum FilterParamsPatch {
539 /// Frequency(f32),
540 /// Quality(f32),
541 /// }
542 /// ```
543 ///
544 /// This type is converted from [`NodeEventType::Param`] in the [`patch`][Patch::patch]
545 /// method.
546 type Patch;
547
548 /// Construct a patch from a parameter event.
549 ///
550 /// This converts the intermediate representation in [`NodeEventType::Param`] into
551 /// a concrete value, making it easy to manipulate the event in audio processors.
552 ///
553 /// ```
554 /// # use firewheel_core::{diff::Patch, event::{ProcEvents, NodeEventType}};
555 /// # fn patching(mut event_list: ProcEvents) {
556 /// #[derive(Patch, Default)]
557 /// struct FilterParams {
558 /// frequency: f32,
559 /// quality: f32,
560 /// }
561 ///
562 /// let mut filter_params = FilterParams::default();
563 ///
564 /// for event in event_list.drain() {
565 /// match event {
566 /// NodeEventType::Param { data, path } => {
567 /// let Ok(patch) = FilterParams::patch(&data, &path) else {
568 /// return;
569 /// };
570 ///
571 /// // You can match on the patch directly
572 /// match &patch {
573 /// FilterParamsPatch::Frequency(f) => {
574 /// // Handle frequency event...
575 /// }
576 /// FilterParamsPatch::Quality(q) => {
577 /// // Handle quality event...
578 /// }
579 /// }
580 ///
581 /// // And/or apply it.
582 /// filter_params.apply(patch);
583 /// }
584 /// _ => {}
585 /// }
586 /// }
587 /// # }
588 /// ```
589 fn patch(data: &ParamData, path: &[u32]) -> Result<Self::Patch, PatchError>;
590
591 /// Construct a patch from a node event.
592 ///
593 /// This is a convenience wrapper around [`patch`][Patch::patch], discarding
594 /// errors and node events besides [`NodeEventType::Param`].
595 fn patch_event(event: &NodeEventType) -> Option<Self::Patch> {
596 match event {
597 NodeEventType::Param { data, path } => Some(Self::patch(data, path).ok()?),
598 _ => None,
599 }
600 }
601
602 /// Apply a patch.
603 ///
604 /// This will generally be called from within
605 /// the audio thread, so real-time constraints should be respected.
606 ///
607 /// Typically, you'll call this within [`drain_patches`].
608 ///
609 /// ```
610 /// # use firewheel_core::{diff::Patch, event::{ProcEvents, NodeEventType}};
611 /// # fn patching(mut event_list: ProcEvents) {
612 /// #[derive(Patch, Default)]
613 /// struct FilterParams {
614 /// frequency: f32,
615 /// quality: f32,
616 /// }
617 ///
618 /// let mut filter_params = FilterParams::default();
619 /// for patch in event_list.drain_patches::<FilterParams>() { filter_params.apply(patch); }
620 /// # }
621 /// ```
622 ///
623 /// [`drain_patches`]: crate::event::ProcEvents::drain_patches
624 fn apply(&mut self, patch: Self::Patch);
625}
626
627/// A trait which signifies that a struct implements `Clone`, cloning
628/// does not allocate or deallocate data, and the data will not be
629/// dropped on the audio thread if the struct is dropped.
630pub trait RealtimeClone: Clone {}
631
632impl<T: ?Sized + Send + Sync + 'static> RealtimeClone for ArcGc<T> {}
633
634// NOTE: Using a `SmallVec` instead of a `Box<[u32]>` yields
635// around an 8% performance uplift for cases where the path
636// is in the range 2..=4.
637//
638// Beyond this range, the performance drops off around 13%.
639//
640// Since this avoids extra allocations in the common < 5
641// scenario, this seems like a reasonable tradeoff.
642
643/// A simple builder for [`ParamPath`].
644///
645/// When performing top-level diffing, you should provide a default
646/// [`PathBuilder`].
647///
648/// ```
649/// # use firewheel_core::{diff::{Diff, PathBuilder}, event::*, node::*};
650/// #[derive(Diff, Default, Clone)]
651/// struct FilterNode {
652/// frequency: f32,
653/// quality: f32,
654/// }
655///
656/// let baseline = FilterNode::default();
657/// let node = baseline.clone();
658///
659/// let mut events = Vec::new();
660/// node.diff(&baseline, PathBuilder::default(), &mut events);
661/// ```
662#[derive(Debug, Default, Clone)]
663pub struct PathBuilder(SmallVec<[u32; 4]>);
664
665impl PathBuilder {
666 /// Clone the path, appending the index to the returned value.
667 pub fn with(&self, index: u32) -> Self {
668 let mut new = self.0.clone();
669 new.push(index);
670 Self(new)
671 }
672
673 /// Convert this path builder into a [`ParamPath`].
674 pub fn build(self) -> ParamPath {
675 if self.0.len() == 1 {
676 ParamPath::Single(self.0[0])
677 } else {
678 ParamPath::Multi(ArcGc::new_unsized(|| Arc::<[u32]>::from(self.0.as_slice())))
679 }
680 }
681}
682
683/// An event queue for diffing.
684pub trait EventQueue {
685 /// Push an event to the queue.
686 fn push(&mut self, data: NodeEventType);
687
688 /// Push an event to the queue.
689 ///
690 /// This is a convenience method for constructing a [`NodeEventType`]
691 /// from param data and a path.
692 #[inline(always)]
693 fn push_param(&mut self, data: impl Into<ParamData>, path: PathBuilder) {
694 self.push(NodeEventType::Param {
695 data: data.into(),
696 path: path.build(),
697 });
698 }
699}
700
701impl EventQueue for Vec<NodeEventType> {
702 fn push(&mut self, data: NodeEventType) {
703 self.push(data);
704 }
705}
706
707/// An error encountered when patching a type
708/// from [`ParamData`].
709#[derive(Debug, Clone)]
710pub enum PatchError {
711 /// The provided path does not match any children.
712 InvalidPath,
713 /// The data supplied for the path did not match the expected type.
714 InvalidData,
715}
716
717#[cfg(test)]
718mod test {
719 use super::*;
720
721 #[derive(Debug, Clone, Diff, Patch, PartialEq)]
722 struct StructDiff {
723 a: f32,
724 b: bool,
725 }
726
727 #[test]
728 fn test_simple_diff() {
729 let mut a = StructDiff { a: 1.0, b: false };
730
731 let mut b = a.clone();
732
733 a.a = 0.5;
734
735 let mut patches = Vec::new();
736 a.diff(&b, PathBuilder::default(), &mut patches);
737
738 assert_eq!(patches.len(), 1);
739
740 for patch in patches.iter() {
741 let patch = StructDiff::patch_event(patch).unwrap();
742
743 assert!(matches!(patch, StructDiffPatch::A(a) if a == 0.5));
744
745 b.apply(patch);
746 }
747
748 assert_eq!(a, b);
749 }
750
751 #[derive(Debug, Clone, Diff, Patch, PartialEq)]
752 enum DiffingExample {
753 Unit,
754 Tuple(f32, f32),
755 Struct { a: f32, b: f32 },
756 }
757
758 #[test]
759 fn test_enum_diff() {
760 let mut baseline = DiffingExample::Tuple(1.0, 0.0);
761 let value = DiffingExample::Tuple(1.0, 1.0);
762
763 let mut messages = Vec::new();
764 value.diff(&baseline, PathBuilder::default(), &mut messages);
765
766 assert_eq!(messages.len(), 1);
767 baseline.apply(DiffingExample::patch_event(&messages.pop().unwrap()).unwrap());
768 assert_eq!(baseline, value);
769 }
770
771 #[test]
772 fn test_enum_switch_variant() {
773 let mut baseline = DiffingExample::Unit;
774 let value = DiffingExample::Struct { a: 1.0, b: 1.0 };
775
776 let mut messages = Vec::new();
777 value.diff(&baseline, PathBuilder::default(), &mut messages);
778
779 assert_eq!(messages.len(), 1);
780 baseline.apply(DiffingExample::patch_event(&messages.pop().unwrap()).unwrap());
781 assert_eq!(baseline, value);
782 }
783}