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).ok()?.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                            $animated_name::$variant($crate::KeyDataMap::from_single(time, v))
212                        }
213                    )+
214                }
215            }
216
217            fn variant_name(&self) -> &'static str {
218                match self {
219                    $(
220                        $animated_name::$variant(_) => stringify!($variant),
221                    )+
222                }
223            }
224        }
225
226        // Implement From conversions from inner types to Data.
227        $(
228            impl ::core::convert::From<$inner_ty> for $data_name {
229                fn from(value: $inner_ty) -> Self {
230                    $data_name::$variant(value)
231                }
232            }
233        )+
234    };
235}
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use crate::{AnimatedDataSystem, DataSystem, GenericValue, Time};
241    use std::ops::{Add, Div, Mul, Sub};
242
243    // Wrapper type that implements all required traits for interpolation.
244    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
245    struct TestFloat(i64); // Store as fixed-point for Eq/Hash.
246
247    impl TestFloat {
248        fn new(v: f32) -> Self {
249            Self((v * 1000.0) as i64)
250        }
251
252        fn value(&self) -> f32 {
253            self.0 as f32 / 1000.0
254        }
255    }
256
257    impl Add for TestFloat {
258        type Output = Self;
259        fn add(self, other: Self) -> Self {
260            Self(self.0 + other.0)
261        }
262    }
263
264    impl Sub for TestFloat {
265        type Output = Self;
266        fn sub(self, other: Self) -> Self {
267            Self(self.0 - other.0)
268        }
269    }
270
271    impl Mul<f32> for TestFloat {
272        type Output = Self;
273        fn mul(self, scalar: f32) -> Self {
274            Self((self.0 as f32 * scalar) as i64)
275        }
276    }
277
278    impl Mul<f64> for TestFloat {
279        type Output = Self;
280        fn mul(self, scalar: f64) -> Self {
281            Self((self.0 as f64 * scalar) as i64)
282        }
283    }
284
285    impl Div<f32> for TestFloat {
286        type Output = Self;
287        fn div(self, scalar: f32) -> Self {
288            Self((self.0 as f32 / scalar) as i64)
289        }
290    }
291
292    impl Div<f64> for TestFloat {
293        type Output = Self;
294        fn div(self, scalar: f64) -> Self {
295            Self((self.0 as f64 / scalar) as i64)
296        }
297    }
298
299    // Integer wrapper that supports interpolation via f32 multiplication.
300    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
301    struct TestInt(i64);
302
303    impl Add for TestInt {
304        type Output = Self;
305        fn add(self, other: Self) -> Self {
306            Self(self.0 + other.0)
307        }
308    }
309
310    impl Sub for TestInt {
311        type Output = Self;
312        fn sub(self, other: Self) -> Self {
313            Self(self.0 - other.0)
314        }
315    }
316
317    impl Mul<f32> for TestInt {
318        type Output = Self;
319        fn mul(self, scalar: f32) -> Self {
320            Self((self.0 as f32 * scalar) as i64)
321        }
322    }
323
324    impl Mul<f64> for TestInt {
325        type Output = Self;
326        fn mul(self, scalar: f64) -> Self {
327            Self((self.0 as f64 * scalar) as i64)
328        }
329    }
330
331    impl Div<f32> for TestInt {
332        type Output = Self;
333        fn div(self, scalar: f32) -> Self {
334            Self((self.0 as f32 / scalar) as i64)
335        }
336    }
337
338    impl Div<f64> for TestInt {
339        type Output = Self;
340        fn div(self, scalar: f64) -> Self {
341            Self((self.0 as f64 / scalar) as i64)
342        }
343    }
344
345    // Define a simple custom data system for testing.
346    define_data_types! {
347        /// Test data types.
348        #[derive(Clone, Debug, PartialEq)]
349        pub TestData / TestAnimatedData / TestDataType {
350            /// A float value.
351            Float(TestFloat),
352            /// An int value.
353            Int(TestInt),
354        }
355    }
356
357    #[test]
358    fn test_discriminant() {
359        let data = TestData::Float(TestFloat::new(42.0));
360        assert_eq!(data.discriminant(), TestDataType::Float);
361        assert_eq!(data.variant_name(), "Float");
362
363        let data = TestData::Int(TestInt(42));
364        assert_eq!(data.discriminant(), TestDataType::Int);
365        assert_eq!(data.variant_name(), "Int");
366    }
367
368    #[test]
369    fn test_from_conversion() {
370        let data: TestData = TestFloat::new(42.0).into();
371        assert!(matches!(data, TestData::Float(_)));
372
373        let data: TestData = TestInt(42).into();
374        assert!(matches!(data, TestData::Int(TestInt(42))));
375    }
376
377    #[test]
378    fn test_generic_value_uniform() {
379        let value = GenericValue::<TestData>::uniform(TestData::Float(TestFloat::new(42.0)));
380        assert!(!value.is_animated());
381
382        if let TestData::Float(f) = value.interpolate(Time::default()) {
383            assert!((f.value() - 42.0).abs() < 0.01);
384        } else {
385            panic!("Expected Float variant");
386        }
387    }
388
389    #[test]
390    fn test_generic_value_animated() {
391        let value = GenericValue::<TestData>::animated(vec![
392            (Time::default(), TestData::Float(TestFloat::new(0.0))),
393            (Time::from(10.0), TestData::Float(TestFloat::new(100.0))),
394        ])
395        .unwrap();
396
397        assert!(value.is_animated());
398        assert_eq!(value.sample_count(), 2);
399
400        // Test interpolation at midpoint.
401        let mid = value.interpolate(Time::from(5.0));
402        if let TestData::Float(v) = mid {
403            assert!((v.value() - 50.0).abs() < 1.0); // Fixed-point has some precision loss.
404        } else {
405            panic!("Expected Float variant");
406        }
407    }
408
409    #[test]
410    fn test_animated_data_system() {
411        let animated =
412            TestAnimatedData::from_single(Time::default(), TestData::Float(TestFloat::new(42.0)));
413        assert_eq!(animated.keyframe_count(), 1);
414        assert_eq!(animated.variant_name(), "Float");
415
416        let sample = animated.sample_at(Time::default());
417        assert!(matches!(sample, Some(TestData::Float(_))));
418    }
419}