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}