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.patch_event(&event_queue[0]);
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)]
72//! enum MyParams {
73//!     Unit,
74//!     Tuple(f32, f32),
75//!     Struct { a: f32, b: f32 },
76//! }
77//! ```
78//!
79//! Changing between tuple or struct fields will incur allocations
80//! in [`Diff`], but changes _within_ a single variant are still fine-grained.
81//!
82//! It's important to note that you can accidentally introduce allocations
83//! in audio processors by including types that allocate on clone.
84//!
85//! ```
86//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
87//! #[derive(Diff, Patch)]
88//! enum MaybeAllocates {
89//!     A(Vec<f32>), // Will cause allocations in `Patch`!
90//!     B(f32),
91//! }
92//! ```
93//!
94//! [`Clone`] types are permitted because [`Clone`] does
95//! not always imply allocation. For example, consider
96//! the type:
97//!
98//! ```
99//! use firewheel_core::{collector::ArcGc, sample_resource::SampleResource};
100//!
101//! # use firewheel_core::diff::{Diff, Patch, PathBuilder};
102//! #[derive(Diff, Patch)]
103//! enum SoundSource {
104//!     Sample(ArcGc<dyn SampleResource>), // Will _not_ cause allocations in `Patch`.
105//!     Frequency(f32),
106//! }
107//! ```
108//!
109//! This bound may be restricted to [`Copy`] in the future.
110//!
111//! # Macro attributes
112//!
113//! [`Diff`] and [`Patch`] each accept a single attribute, `skip`, on
114//! struct fields. Any field annotated with `skip` will not receive
115//! diffing or patching, which is particularly useful for atomically synchronized
116//! types.
117//! ```
118//! use firewheel_core::{collector::ArcGc, diff::{Diff, Patch}};
119//! use core::sync::atomic::AtomicUsize;
120//!
121//! #[derive(Diff, Patch)]
122//! struct MultiParadigm {
123//!     normal_field: f32,
124//!     #[diff(skip)]
125//!     atomic_field: ArcGc<AtomicUsize>,
126//! }
127//! ```
128//!
129//! # Data model
130//!
131//! Diffing events are represented as `(data, path)` pairs. This approach
132//! provides a few important advantages. For one, the fields within nearly
133//! all Rust types can be uniquely addressed with index paths.
134//!
135//! ```
136//! # use firewheel_core::diff::{Diff, Patch};
137//! #[derive(Diff, Patch, Default)]
138//! struct MyParams {
139//!     a: f32,
140//!     b: (bool, bool),
141//! }
142//!
143//! let params = MyParams::default();
144//!
145//! params.a;   // [0]
146//! params.b.0; // [1, 0]
147//! params.b.1; // [1, 1]
148//! ```
149//!
150//! Since these paths can be arbitrarily long, you can arbitrarily
151//! nest implementors of [`Diff`] and [`Patch`].
152//!
153//! ```
154//! # use firewheel_core::diff::{Diff, Patch};
155//! # #[derive(Diff, Patch, Default)]
156//! # struct MyParams {
157//! #     a: f32,
158//! #     b: (bool, bool),
159//! # }
160//! #[derive(Diff, Patch)]
161//! struct Aggregate {
162//!     a: MyParams,
163//!     b: MyParams,
164//!     // Indexable types work great too!
165//!     collection: [MyParams; 8],
166//! }
167//! ```
168//!
169//! Furthermore, since we build up paths during calls to
170//! [`Diff`], the derive macros and implementations only need
171//! to worry about _local indexing._ And, since the paths
172//! are built only during [`Diff`], we can traverse them
173//! highly performantly during [`Patch`] calls in audio processors.
174//!
175//! Firewheel provides a number of primitive types in [`ParamData`]
176//! that cover most use-cases for audio parameters. For anything
177//! not covered in the concrete variants, you can insert arbitrary
178//! data into [`ParamData::Any`]. Since this only incurs allocations
179//! during [`Diff`], this will still be generally performant.
180
181use crate::{
182    collector::ArcGc,
183    event::{NodeEventList, NodeEventType, ParamData},
184};
185
186use smallvec::SmallVec;
187
188mod collections;
189mod leaf;
190mod memo;
191
192pub use memo::Memo;
193
194/// Derive macros for diffing and patching.
195pub use firewheel_macros::{Diff, Patch};
196
197/// Fine-grained parameter diffing.
198///
199/// This trait allows a type to perform diffing on itself,
200/// generating events that another instance can use to patch
201/// itself.
202///
203/// For more information, see the [module docs][self].
204///
205/// # Examples
206///
207/// For most use cases, [`Diff`] is fairly straightforward.
208///
209/// ```
210/// use firewheel_core::diff::{Diff, PathBuilder};
211///
212/// #[derive(Diff, Clone)]
213/// struct MyParams {
214///     a: f32,
215///     b: f32,
216/// }
217///
218/// let mut params = MyParams {
219///     a: 1.0,
220///     b: 1.0,
221/// };
222///
223/// // This "baseline" instance allows us to keep track
224/// // of what's changed over time.
225/// let baseline = params.clone();
226///
227/// // A single mutation to a "leaf" type like `f32` will
228/// // produce a single event.
229/// params.a = 0.5;
230///
231/// // `Vec<NodeEventType>` implements `EventQueue`, meaning we
232/// // don't necessarily need to keep track of `NodeID`s for event generation.
233/// let mut event_queue = Vec::new();
234/// // Top-level calls to diff should always provide a default path builder.
235/// params.diff(&baseline, PathBuilder::default(), &mut event_queue);
236///
237/// assert_eq!(event_queue.len(), 1);
238/// ```
239///
240/// When using Firewheel in a standalone context, the [`Memo`] type can
241/// simplify this process.
242///
243/// ```
244/// # use firewheel_core::diff::{Diff, PathBuilder};
245/// # #[derive(Diff, Clone)]
246/// # struct MyParams {
247/// #     a: f32,
248/// #     b: f32,
249/// # }
250/// use firewheel_core::diff::Memo;
251///
252/// let mut params_memo = Memo::new(MyParams {
253///     a: 1.0,
254///     b: 1.0,
255/// });
256///
257/// // `Memo` implements `DerefMut` on the wrapped type, allowing you
258/// // to use it almost transparently.
259/// params_memo.a = 0.5;
260///
261/// let mut event_queue = Vec::new();
262/// // This generates patches and brings the internally managed
263/// // baseline in sync.
264/// params_memo.update_memo(&mut event_queue);
265/// ```
266///
267/// # Manual implementation
268///
269/// Aggregate types like parameters should prefer the derive macro, but
270/// manual implementations can occasionally be handy. You should strive
271/// to match the derived data model for maximum compatibility.
272///
273/// ```
274/// use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
275/// # struct MyParams {
276/// #     a: f32,
277/// #     b: f32,
278/// # }
279///
280/// impl Diff for MyParams {
281///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
282///         // The diffing data model requires a unique path to each field.
283///         // Because this type can be arbitrarily nested, you should always
284///         // extend the provided path builder using `PathBuilder::with`.
285///         //
286///         // Because this is the first field, we'll extend the path with 0.
287///         self.a.diff(&baseline.a, path.with(0), event_queue);
288///         self.b.diff(&baseline.b, path.with(1), event_queue);
289///     }
290/// }
291/// ```
292///
293/// You can easily override a type's [`Diff`] implementation by simply
294/// doing comparisons by hand.
295///
296/// ```
297/// use firewheel_core::event::ParamData;
298/// # use firewheel_core::diff::{Diff, PathBuilder, EventQueue};
299/// # struct MyParams {
300/// #     a: f32,
301/// #     b: f32,
302/// # }
303///
304/// impl Diff for MyParams {
305///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
306///         // The above is essentially equivalent to:
307///         if self.a != baseline.a {
308///             event_queue.push_param(ParamData::F32(self.a), path.with(0));
309///         }
310///
311///         if self.b != baseline.b {
312///             event_queue.push_param(ParamData::F32(self.b), path.with(1));
313///         }
314///     }
315/// }
316/// ```
317///
318/// If your type has invariants between fields that _must not_ be violated, you
319/// can consider the whole type a "leaf," similar to how [`Diff`] is implemented
320/// on primitives. Depending on the type's data, you may require an allocation.
321///
322/// ```
323/// # use firewheel_core::{diff::{Diff, PathBuilder, EventQueue}, event::ParamData};
324/// # #[derive(PartialEq, Clone)]
325/// # struct MyParams {
326/// #     a: f32,
327/// #     b: f32,
328/// # }
329/// impl Diff for MyParams {
330///     fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E) {
331///         if self != baseline {
332///             // Note that if we consider the whole type to be a leaf, there
333///             // is no need to extend the path.
334///             event_queue.push_param(ParamData::any(self.clone()), path);
335///         }
336///     }
337/// }
338/// ```
339pub trait Diff {
340    /// Compare `self` to `baseline` and generate events to resolve any differences.
341    fn diff<E: EventQueue>(&self, baseline: &Self, path: PathBuilder, event_queue: &mut E);
342}
343
344/// Fine-grained parameter patching.
345///
346/// This trait allows a type to perform patching on itself,
347/// applying patches generated from another instance.
348///
349/// For more information, see the [module docs][self].
350///
351/// # Examples
352///
353/// Like with [`Diff`], the typical [`Patch`] usage is simple.
354///
355/// ```
356/// use firewheel_core::{diff::Patch, event::*, node::*};
357///
358/// #[derive(Patch)]
359/// struct MyParams {
360///     a: f32,
361///     b: f32,
362/// }
363///
364/// struct MyProcessor {
365///     params: MyParams,
366/// }
367///
368/// impl AudioNodeProcessor for MyProcessor {
369///     fn process(
370///         &mut self,
371///         inputs: &[&[f32]],
372///         outputs: &mut [&mut [f32]],
373///         events: NodeEventList,
374///         proc_info: &ProcInfo,
375///         scratch_buffers: ScratchBuffers,
376///     ) -> ProcessStatus {
377///         // Synchronize `params` from the event list.
378///         self.params.patch_list(events);
379///
380///         // ...
381///
382///         ProcessStatus::outputs_not_silent()
383///     }
384/// }
385/// ```
386///
387/// [`Patch::patch_list`] is a convenience trait method
388/// that takes a [`NodeEventList`] by value, applies any
389/// parameter patches that may be present, and returns
390/// a boolean indicating whether any parameters have changed.
391///
392/// If you need finer access to the event list, you can
393/// apply patches more directly.
394///
395/// ```
396/// # use firewheel_core::{diff::{Patch}, event::*, node::*};
397/// # #[derive(Patch)]
398/// # struct MyParams {
399/// #     a: f32,
400/// #     b: f32,
401/// # }
402/// # struct MyProcessor {
403/// #    params: MyParams,
404/// # }
405/// impl AudioNodeProcessor for MyProcessor {
406///     fn process(
407///         &mut self,
408///         inputs: &[&[f32]],
409///         outputs: &mut [&mut [f32]],
410///         mut events: NodeEventList,
411///         proc_info: &ProcInfo,
412///         scratch_buffers: ScratchBuffers,
413///     ) -> ProcessStatus {
414///         events.for_each(|e| {
415///             // You can take the whole event, which may
416///             // or may not actually contain a parameter:
417///             self.params.patch_event(e);
418///
419///             // Or match on the event and provide
420///             // each element directly:
421///             match e {
422///                 NodeEventType::Param { data, path } => {
423///                     // This allows you to handle errors as well.
424///                     let _ = self.params.patch(data, &path);
425///                 }
426///                 _ => {}
427///             }
428///         });
429///
430///         // ...
431///
432///         ProcessStatus::outputs_not_silent()
433///     }
434/// }
435/// ```
436///
437/// # Manual implementation
438///
439/// Like with [`Diff`], types like parameters should prefer the [`Patch`] derive macro.
440/// Nonetheless, Firewheel provides a few tools to make manual implementations easy.
441///
442/// ```
443/// use firewheel_core::{diff::{Patch, PatchError}, event::ParamData};
444///
445/// struct MyParams {
446///     a: f32,
447///     b: (bool, bool)
448/// }
449///
450/// impl Patch for MyParams {
451///     fn patch(&mut self, data: &ParamData, path: &[u32]) -> Result<(), PatchError> {
452///         match path {
453///             [0] => {
454///                 // You can defer to `f32`'s `Patch` implementation, or simply
455///                 // apply the data directly like we do here.
456///                 self.a = data.try_into()?;
457///
458///                 Ok(())
459///             }
460///             // Shortening the path slice one element at a time as we descend the tree
461///             // allows nested types to see the path as they expect it.
462///             [1, tail @ ..] => {
463///                 self.b.patch(data, tail)
464///             }
465///             _ => Err(PatchError::InvalidPath)
466///         }
467///     }
468/// }
469/// ```
470pub trait Patch {
471    /// Patch `self` according to the incoming data.
472    /// This will generally be called from within
473    /// the audio thread.
474    ///
475    /// `data` is intentionally made a shared reference.
476    /// This should make accidental syscalls due to
477    /// additional allocations or drops more difficult.
478    /// If you find yourself reaching for interior
479    /// mutability, consider whether you're building
480    /// realtime-appropriate behavior.
481    fn patch(&mut self, data: &ParamData, path: &[u32]) -> Result<(), PatchError>;
482
483    /// Patch a set of parameters with incoming events.
484    ///
485    /// Returns `true` if any parameters have changed.
486    ///
487    /// This is useful as a convenience method for extracting the path
488    /// and data components from a [`NodeEventType`]. Errors produced
489    /// here are ignored.
490    fn patch_event(&mut self, event: &NodeEventType) -> bool {
491        if let NodeEventType::Param { data, path } = event {
492            // NOTE: It may not be ideal to ignore errors.
493            // Would it be possible to log these in debug mode?
494            self.patch(data, path).is_ok()
495        } else {
496            false
497        }
498    }
499
500    /// Patch a set of parameters with a list of incoming events.
501    ///
502    /// Returns `true` if any parameters have changed.
503    ///
504    /// This is useful as a convenience method for patching parameters
505    /// directly from a [`NodeEventList`]. Errors produced here are ignored.
506    fn patch_list(&mut self, mut event_list: NodeEventList) -> bool {
507        let mut changed = false;
508
509        event_list.for_each(|e| {
510            changed |= self.patch_event(e);
511        });
512
513        changed
514    }
515}
516
517/// A path of indices that uniquely describes an arbitrarily nested field.
518pub enum ParamPath {
519    Single(u32),
520    Multi(ArcGc<SmallVec<[u32; 4]>>),
521}
522
523impl core::ops::Deref for ParamPath {
524    type Target = [u32];
525
526    fn deref(&self) -> &Self::Target {
527        match self {
528            Self::Single(single) => core::slice::from_ref(single),
529            Self::Multi(multi) => multi.as_ref(),
530        }
531    }
532}
533
534// NOTE: Using a `SmallVec` instead of a `Box<[u32]>` yields
535// around an 8% performance uplift for cases where the path
536// is in the range 2..=4.
537//
538// Beyond this range, the performance drops off around 13%.
539//
540// Since this avoids extra allocations in the common < 5
541// scenario, this seems like a reasonable tradeoff.
542
543/// A simple builder for [`ParamPath`].
544#[derive(Debug, Default, Clone)]
545pub struct PathBuilder(SmallVec<[u32; 4]>);
546
547impl PathBuilder {
548    /// Clone the path and append the index.
549    pub fn with(&self, index: u32) -> Self {
550        let mut new = self.0.clone();
551        new.push(index);
552        Self(new)
553    }
554
555    /// Convert this path builder into a [`ParamPath`].
556    pub fn build(self) -> ParamPath {
557        if self.0.len() == 1 {
558            ParamPath::Single(self.0[0])
559        } else {
560            ParamPath::Multi(ArcGc::new(self.0.clone()))
561        }
562    }
563}
564
565/// An event queue for diffing.
566pub trait EventQueue {
567    /// Push an event to the queue.
568    fn push(&mut self, data: NodeEventType);
569
570    /// Push an event to the queue.
571    ///
572    /// This is a convenience method for constructing a [`NodeEventType`]
573    /// from param data and a path.
574    #[inline(always)]
575    fn push_param(&mut self, data: impl Into<ParamData>, path: PathBuilder) {
576        self.push(NodeEventType::Param {
577            data: data.into(),
578            path: path.build(),
579        });
580    }
581}
582
583impl EventQueue for Vec<NodeEventType> {
584    fn push(&mut self, data: NodeEventType) {
585        self.push(data);
586    }
587}
588
589/// An error encountered when patching a type
590/// from [`ParamData`].
591#[derive(Debug, Clone)]
592pub enum PatchError {
593    /// The provided path does not match any children.
594    InvalidPath,
595    /// The data supplied for the path did not match the expected type.
596    InvalidData,
597}
598
599#[cfg(test)]
600mod test {
601    use super::*;
602
603    #[derive(Debug, Clone, Diff, Patch, PartialEq)]
604    enum DiffingExample {
605        Unit,
606        Tuple(f32, f32),
607        Struct { a: f32, b: f32 },
608    }
609
610    #[test]
611    fn test_enum_diff() {
612        let mut baseline = DiffingExample::Tuple(1.0, 0.0);
613        let value = DiffingExample::Tuple(1.0, 1.0);
614
615        let mut messages = Vec::new();
616        value.diff(&baseline, PathBuilder::default(), &mut messages);
617
618        assert_eq!(messages.len(), 1);
619        assert!(baseline.patch_event(&messages[0]));
620        assert_eq!(baseline, value);
621    }
622
623    #[test]
624    fn test_enum_switch_variant() {
625        let mut baseline = DiffingExample::Unit;
626        let value = DiffingExample::Struct { a: 1.0, b: 1.0 };
627
628        let mut messages = Vec::new();
629        value.diff(&baseline, PathBuilder::default(), &mut messages);
630
631        assert_eq!(messages.len(), 1);
632        assert!(baseline.patch_event(&messages[0]));
633        assert_eq!(baseline, value);
634    }
635}