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}