Skip to main content

token_value_map/
define_data_macro.rs

1//! Macro for defining custom data type systems.
2//!
3//! This module provides the [`define_data_types!`] macro which generates
4//! all the boilerplate needed to create a custom data system compatible
5//! with [`GenericValue`](crate::GenericValue) and
6//! [`GenericTokenValueMap`](crate::GenericTokenValueMap).
7
8/// Define a custom data type system with full interpolation support.
9///
10/// This macro generates three enums and implements all necessary traits
11/// for use with [`GenericValue`](crate::GenericValue) and
12/// [`GenericTokenValueMap`](crate::GenericTokenValueMap):
13///
14/// 1. A discriminant enum (like `DataType`) with unit variants.
15/// 2. A data enum (like `Data`) holding actual values.
16/// 3. An animated data enum (like `AnimatedData`) holding `TimeDataMap<T>` values.
17///
18/// # Example
19///
20/// ```rust,ignore
21/// use token_value_map::{define_data_types, TimeDataMap, Time, DataSystem};
22///
23/// define_data_types! {
24///     /// My custom data types.
25///     #[derive(Clone, Debug, PartialEq)]
26///     pub MyData / MyAnimatedData / MyDataType {
27///         /// A floating point value.
28///         Float(MyFloat),
29///         /// An integer value.
30///         Int(MyInt),
31///     }
32/// }
33///
34/// // Now you can use GenericValue<MyData> and GenericTokenValueMap<MyData>.
35/// use token_value_map::GenericValue;
36///
37/// let uniform = GenericValue::<MyData>::uniform(MyData::Float(MyFloat(42.0)));
38/// let animated = GenericValue::<MyData>::animated(vec![
39///     (Time::default(), MyData::Float(MyFloat(0.0))),
40///     (Time::from(10.0), MyData::Float(MyFloat(100.0))),
41/// ]).unwrap();
42/// ```
43///
44/// # Generated Types
45///
46/// Given `MyData / MyAnimatedData / MyDataType`:
47///
48/// - `MyDataType`: Discriminant enum with unit variants (`Float`, `Int`, `Text`).
49/// - `MyData`: Data enum holding values (`Float(f32)`, `Int(i32)`, `Text(String)`).
50/// - `MyAnimatedData`: Animated enum holding time maps
51///   (`Float(TimeDataMap<f32>)`, etc.).
52///
53/// # Requirements
54///
55/// Each variant type must implement:
56/// - `Clone + Debug + PartialEq + Eq + Hash + Send + Sync + 'static`
57///
58/// For interpolation support, types should also implement:
59/// - `Add<Output = Self> + Sub<Output = Self>`
60/// - `Mul<f32, Output = Self> + Mul<f64, Output = Self>`
61/// - `Div<f32, Output = Self> + Div<f64, Output = Self>`
62///
63/// Types that don't support interpolation will use sample-and-hold behavior.
64#[macro_export]
65macro_rules! define_data_types {
66    (
67        $(#[$meta:meta])*
68        $vis:vis $data_name:ident / $animated_name:ident / $discriminant_name:ident {
69            $(
70                $(#[$variant_meta:meta])*
71                $variant:ident($inner_ty:ty)
72            ),+ $(,)?
73        }
74    ) => {
75        // Generate the discriminant enum.
76        $(#[$meta])*
77        #[derive(Copy, Eq, Hash)]
78        $vis enum $discriminant_name {
79            $(
80                $(#[$variant_meta])*
81                $variant,
82            )+
83        }
84
85        // Generate the data enum.
86        $(#[$meta])*
87        #[derive(Eq, Hash)]
88        $vis enum $data_name {
89            $(
90                $(#[$variant_meta])*
91                $variant($inner_ty),
92            )+
93        }
94
95        // Generate the animated data enum.
96        $(#[$meta])*
97        #[derive(Eq, Hash)]
98        $vis enum $animated_name {
99            $(
100                $(#[$variant_meta])*
101                $variant($crate::TimeDataMap<$inner_ty>),
102            )+
103        }
104
105        // Implement DataSystem for the data enum.
106        impl $crate::DataSystem for $data_name {
107            type Animated = $animated_name;
108            type DataType = $discriminant_name;
109
110            fn discriminant(&self) -> Self::DataType {
111                match self {
112                    $(
113                        $data_name::$variant(_) => $discriminant_name::$variant,
114                    )+
115                }
116            }
117
118            fn variant_name(&self) -> &'static str {
119                match self {
120                    $(
121                        $data_name::$variant(_) => stringify!($variant),
122                    )+
123                }
124            }
125        }
126
127        // Implement AnimatedDataSystem for the animated data enum.
128        impl $crate::AnimatedDataSystem for $animated_name {
129            type Data = $data_name;
130
131            fn keyframe_count(&self) -> usize {
132                match self {
133                    $(
134                        $animated_name::$variant(map) => map.len(),
135                    )+
136                }
137            }
138
139            fn times(&self) -> ::smallvec::SmallVec<[$crate::Time; 10]> {
140                match self {
141                    $(
142                        $animated_name::$variant(map) => {
143                            map.iter().map(|(t, _)| *t).collect()
144                        }
145                    )+
146                }
147            }
148
149            fn interpolate(&self, time: $crate::Time) -> Self::Data {
150                match self {
151                    $(
152                        $animated_name::$variant(map) => {
153                            $data_name::$variant(map.interpolate(time))
154                        }
155                    )+
156                }
157            }
158
159            fn sample_at(&self, time: $crate::Time) -> ::core::option::Option<Self::Data> {
160                match self {
161                    $(
162                        $animated_name::$variant(map) => {
163                            map.get(&time).cloned().map($data_name::$variant)
164                        }
165                    )+
166                }
167            }
168
169            fn try_insert(
170                &mut self,
171                time: $crate::Time,
172                value: Self::Data,
173            ) -> $crate::Result<()> {
174                match (self, value) {
175                    $(
176                        ($animated_name::$variant(map), $data_name::$variant(v)) => {
177                            map.insert(time, v);
178                            Ok(())
179                        }
180                    )+
181                    #[allow(unreachable_patterns)]
182                    (this, val) => Err($crate::Error::GenericTypeMismatch {
183                        expected: this.variant_name(),
184                        got: val.variant_name(),
185                    }),
186                }
187            }
188
189            fn remove_at(&mut self, time: &$crate::Time) -> ::core::option::Option<Self::Data> {
190                match self {
191                    $(
192                        $animated_name::$variant(map) => {
193                            map.remove(time).map($data_name::$variant)
194                        }
195                    )+
196                }
197            }
198
199            fn discriminant(&self) -> <Self::Data as $crate::DataSystem>::DataType {
200                match self {
201                    $(
202                        $animated_name::$variant(_) => $discriminant_name::$variant,
203                    )+
204                }
205            }
206
207            fn from_single(time: $crate::Time, value: Self::Data) -> Self {
208                match value {
209                    $(
210                        $data_name::$variant(v) => {
211                            let mut map = ::std::collections::BTreeMap::new();
212                            map.insert(time, v);
213                            $animated_name::$variant($crate::TimeDataMap::from(map))
214                        }
215                    )+
216                }
217            }
218
219            fn variant_name(&self) -> &'static str {
220                match self {
221                    $(
222                        $animated_name::$variant(_) => stringify!($variant),
223                    )+
224                }
225            }
226        }
227
228        // Implement From conversions from inner types to Data.
229        $(
230            impl ::core::convert::From<$inner_ty> for $data_name {
231                fn from(value: $inner_ty) -> Self {
232                    $data_name::$variant(value)
233                }
234            }
235        )+
236    };
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242    use crate::{AnimatedDataSystem, DataSystem, GenericValue, Time};
243    use std::ops::{Add, Div, Mul, Sub};
244
245    // Wrapper type that implements all required traits for interpolation.
246    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
247    struct TestFloat(i64); // Store as fixed-point for Eq/Hash.
248
249    impl TestFloat {
250        fn new(v: f32) -> Self {
251            Self((v * 1000.0) as i64)
252        }
253
254        fn value(&self) -> f32 {
255            self.0 as f32 / 1000.0
256        }
257    }
258
259    impl Add for TestFloat {
260        type Output = Self;
261        fn add(self, other: Self) -> Self {
262            Self(self.0 + other.0)
263        }
264    }
265
266    impl Sub for TestFloat {
267        type Output = Self;
268        fn sub(self, other: Self) -> Self {
269            Self(self.0 - other.0)
270        }
271    }
272
273    impl Mul<f32> for TestFloat {
274        type Output = Self;
275        fn mul(self, scalar: f32) -> Self {
276            Self((self.0 as f32 * scalar) as i64)
277        }
278    }
279
280    impl Mul<f64> for TestFloat {
281        type Output = Self;
282        fn mul(self, scalar: f64) -> Self {
283            Self((self.0 as f64 * scalar) as i64)
284        }
285    }
286
287    impl Div<f32> for TestFloat {
288        type Output = Self;
289        fn div(self, scalar: f32) -> Self {
290            Self((self.0 as f32 / scalar) as i64)
291        }
292    }
293
294    impl Div<f64> for TestFloat {
295        type Output = Self;
296        fn div(self, scalar: f64) -> Self {
297            Self((self.0 as f64 / scalar) as i64)
298        }
299    }
300
301    // Integer wrapper that supports interpolation via f32 multiplication.
302    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
303    struct TestInt(i64);
304
305    impl Add for TestInt {
306        type Output = Self;
307        fn add(self, other: Self) -> Self {
308            Self(self.0 + other.0)
309        }
310    }
311
312    impl Sub for TestInt {
313        type Output = Self;
314        fn sub(self, other: Self) -> Self {
315            Self(self.0 - other.0)
316        }
317    }
318
319    impl Mul<f32> for TestInt {
320        type Output = Self;
321        fn mul(self, scalar: f32) -> Self {
322            Self((self.0 as f32 * scalar) as i64)
323        }
324    }
325
326    impl Mul<f64> for TestInt {
327        type Output = Self;
328        fn mul(self, scalar: f64) -> Self {
329            Self((self.0 as f64 * scalar) as i64)
330        }
331    }
332
333    impl Div<f32> for TestInt {
334        type Output = Self;
335        fn div(self, scalar: f32) -> Self {
336            Self((self.0 as f32 / scalar) as i64)
337        }
338    }
339
340    impl Div<f64> for TestInt {
341        type Output = Self;
342        fn div(self, scalar: f64) -> Self {
343            Self((self.0 as f64 / scalar) as i64)
344        }
345    }
346
347    // Define a simple custom data system for testing.
348    define_data_types! {
349        /// Test data types.
350        #[derive(Clone, Debug, PartialEq)]
351        pub TestData / TestAnimatedData / TestDataType {
352            /// A float value.
353            Float(TestFloat),
354            /// An int value.
355            Int(TestInt),
356        }
357    }
358
359    #[test]
360    fn test_discriminant() {
361        let data = TestData::Float(TestFloat::new(42.0));
362        assert_eq!(data.discriminant(), TestDataType::Float);
363        assert_eq!(data.variant_name(), "Float");
364
365        let data = TestData::Int(TestInt(42));
366        assert_eq!(data.discriminant(), TestDataType::Int);
367        assert_eq!(data.variant_name(), "Int");
368    }
369
370    #[test]
371    fn test_from_conversion() {
372        let data: TestData = TestFloat::new(42.0).into();
373        assert!(matches!(data, TestData::Float(_)));
374
375        let data: TestData = TestInt(42).into();
376        assert!(matches!(data, TestData::Int(TestInt(42))));
377    }
378
379    #[test]
380    fn test_generic_value_uniform() {
381        let value = GenericValue::<TestData>::uniform(TestData::Float(TestFloat::new(42.0)));
382        assert!(!value.is_animated());
383
384        if let TestData::Float(f) = value.interpolate(Time::default()) {
385            assert!((f.value() - 42.0).abs() < 0.01);
386        } else {
387            panic!("Expected Float variant");
388        }
389    }
390
391    #[test]
392    fn test_generic_value_animated() {
393        let value = GenericValue::<TestData>::animated(vec![
394            (Time::default(), TestData::Float(TestFloat::new(0.0))),
395            (Time::from(10.0), TestData::Float(TestFloat::new(100.0))),
396        ])
397        .unwrap();
398
399        assert!(value.is_animated());
400        assert_eq!(value.sample_count(), 2);
401
402        // Test interpolation at midpoint.
403        let mid = value.interpolate(Time::from(5.0));
404        if let TestData::Float(v) = mid {
405            assert!((v.value() - 50.0).abs() < 1.0); // Fixed-point has some precision loss.
406        } else {
407            panic!("Expected Float variant");
408        }
409    }
410
411    #[test]
412    fn test_animated_data_system() {
413        let animated =
414            TestAnimatedData::from_single(Time::default(), TestData::Float(TestFloat::new(42.0)));
415        assert_eq!(animated.keyframe_count(), 1);
416        assert_eq!(animated.variant_name(), "Float");
417
418        let sample = animated.sample_at(Time::default());
419        assert!(matches!(sample, Some(TestData::Float(_))));
420    }
421}