Skip to main content

token_value_map/
generic_value.rs

1//! Generic value type that works with any [`DataSystem`].
2
3use crate::{Result, Shutter, Time, traits::*};
4use smallvec::SmallVec;
5use std::hash::{Hash, Hasher};
6
7#[cfg(feature = "rkyv")]
8use rkyv::{Archive, Deserialize as RkyvDeserialize, Serialize as RkyvSerialize};
9#[cfg(feature = "serde")]
10use serde::{Deserialize, Serialize};
11
12/// A value that can be either uniform or animated over time.
13///
14/// This is the generic version of [`Value`](crate::Value) that works with any
15/// [`DataSystem`]. Use this when you have a custom data type system.
16///
17/// For the built-in types, use [`Value`](crate::Value) which is an alias for
18/// `GenericValue<Data>`.
19#[derive(Clone, Debug, PartialEq, Hash)]
20#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
21#[cfg_attr(
22    feature = "serde",
23    serde(bound(
24        serialize = "D: Serialize, D::Animated: Serialize",
25        deserialize = "D: Deserialize<'de>, D::Animated: Deserialize<'de>"
26    ))
27)]
28#[cfg_attr(feature = "rkyv", derive(Archive, RkyvSerialize, RkyvDeserialize))]
29pub enum GenericValue<D: DataSystem> {
30    /// A constant value that does not change over time.
31    Uniform(D),
32    /// A value that changes over time with keyframe interpolation.
33    Animated(D::Animated),
34}
35
36impl<D: DataSystem> GenericValue<D> {
37    /// Creates a uniform value that does not change over time.
38    pub fn uniform(value: D) -> Self {
39        GenericValue::Uniform(value)
40    }
41
42    /// Creates an animated value from time-value pairs.
43    ///
44    /// All samples must have the same data type. Vector samples are padded
45    /// to match the length of the longest vector in the set.
46    pub fn animated<I>(samples: I) -> Result<Self>
47    where
48        I: IntoIterator<Item = (Time, D)>,
49    {
50        let mut samples_vec: Vec<(Time, D)> = samples.into_iter().collect();
51
52        if samples_vec.is_empty() {
53            return Err(crate::Error::EmptySamples);
54        }
55
56        // Get the data type from the first sample.
57        let data_type = samples_vec[0].1.discriminant();
58        let expected_name = samples_vec[0].1.variant_name();
59
60        // Check all samples have the same type and handle length consistency.
61        let mut expected_len: Option<usize> = None;
62        for (time, value) in &mut samples_vec {
63            if value.discriminant() != data_type {
64                return Err(crate::Error::GenericTypeMismatch {
65                    expected: expected_name,
66                    got: value.variant_name(),
67                });
68            }
69
70            // Check vector length consistency.
71            if let Some(vec_len) = value.try_len() {
72                match expected_len {
73                    None => expected_len = Some(vec_len),
74                    Some(expected) => {
75                        if vec_len > expected {
76                            return Err(crate::Error::VectorLengthExceeded {
77                                actual: vec_len,
78                                expected,
79                                time: *time,
80                            });
81                        } else if vec_len < expected {
82                            value.pad_to_length(expected);
83                        }
84                    }
85                }
86            }
87        }
88
89        // Create animated data from the first sample, then insert the rest.
90        let (first_time, first_value) = samples_vec.remove(0);
91        let mut animated = D::Animated::from_single(first_time, first_value);
92
93        for (time, value) in samples_vec {
94            animated.try_insert(time, value)?;
95        }
96
97        Ok(GenericValue::Animated(animated))
98    }
99
100    /// Adds a sample at a specific time.
101    ///
102    /// If the value is uniform, it becomes animated with the new sample.
103    pub fn add_sample(&mut self, time: Time, value: D) -> Result<()> {
104        match self {
105            GenericValue::Uniform(_) => {
106                *self = GenericValue::animated(vec![(time, value)])?;
107                Ok(())
108            }
109            GenericValue::Animated(samples) => {
110                if value.discriminant() != samples.discriminant() {
111                    return Err(crate::Error::GenericTypeMismatch {
112                        expected: samples.variant_name(),
113                        got: value.variant_name(),
114                    });
115                }
116                samples.try_insert(time, value)
117            }
118        }
119    }
120
121    /// Removes a sample at a specific time.
122    ///
123    /// Returns the removed value if it existed. For uniform values, this is a
124    /// no-op and returns `None`. The last sample in an animated value cannot
125    /// be removed (returns `None`). Use [`remove_or_make_uniform`](Self::remove_or_make_uniform)
126    /// to degrade to uniform instead.
127    pub fn remove_sample(&mut self, time: &Time) -> Option<D> {
128        match self {
129            GenericValue::Uniform(_) => None,
130            GenericValue::Animated(samples) => samples.remove_at(time),
131        }
132    }
133
134    /// Remove a sample, converting to uniform if it was the last keyframe.
135    ///
136    /// If the value is uniform, returns `None`. If the removed sample was
137    /// the last keyframe, the value degrades from [`GenericValue::Animated`]
138    /// to [`GenericValue::Uniform`] with that keyframe's value and returns
139    /// `None` (the data is preserved, not lost). Otherwise returns the
140    /// removed value.
141    pub fn remove_or_make_uniform(&mut self, time: &Time) -> Option<D> {
142        match self {
143            GenericValue::Uniform(_) => None,
144            GenericValue::Animated(samples) => {
145                let removed = samples.remove_at(time);
146                if removed.is_some() {
147                    return removed;
148                }
149                // remove_at returned None -- either key not found or last sample.
150                // Check if it's the last sample at this time.
151                if samples.sample_at(*time).is_some() {
152                    // It's the last sample -- degrade to uniform.
153                    let data = samples.interpolate(Time::default());
154                    *self = GenericValue::Uniform(data);
155                }
156                None
157            }
158        }
159    }
160
161    /// Samples the value at an exact time without interpolation.
162    ///
163    /// Returns the exact value if it exists at the given time, or `None` if
164    /// no sample exists at that time for animated values.
165    pub fn sample_at(&self, time: Time) -> Option<D> {
166        match self {
167            GenericValue::Uniform(v) => Some(v.clone()),
168            GenericValue::Animated(samples) => samples.sample_at(time),
169        }
170    }
171
172    /// Interpolates the value at the given time.
173    ///
174    /// For uniform values, returns the constant value. For animated values,
175    /// interpolates between surrounding keyframes.
176    pub fn interpolate(&self, time: Time) -> D {
177        match self {
178            GenericValue::Uniform(v) => v.clone(),
179            GenericValue::Animated(samples) => samples.interpolate(time),
180        }
181    }
182
183    /// Returns `true` if the value is animated (has multiple keyframes).
184    pub fn is_animated(&self) -> bool {
185        match self {
186            GenericValue::Uniform(_) => false,
187            GenericValue::Animated(samples) => samples.has_animation(),
188        }
189    }
190
191    /// Returns the number of time samples.
192    pub fn sample_count(&self) -> usize {
193        match self {
194            GenericValue::Uniform(_) => 1,
195            GenericValue::Animated(samples) => samples.keyframe_count(),
196        }
197    }
198
199    /// Returns all keyframe times.
200    pub fn times(&self) -> SmallVec<[Time; 10]> {
201        match self {
202            GenericValue::Uniform(_) => SmallVec::new_const(),
203            GenericValue::Animated(samples) => samples.times(),
204        }
205    }
206
207    /// Returns the data type discriminant for this value.
208    pub fn discriminant(&self) -> D::DataType {
209        match self {
210            GenericValue::Uniform(data) => data.discriminant(),
211            GenericValue::Animated(animated) => animated.discriminant(),
212        }
213    }
214
215    /// Returns a human-readable type name for this value.
216    pub fn variant_name(&self) -> &'static str {
217        match self {
218            GenericValue::Uniform(data) => data.variant_name(),
219            GenericValue::Animated(animated) => animated.variant_name(),
220        }
221    }
222
223    /// Merges this value with another using a combiner function.
224    ///
225    /// For uniform values, applies the combiner once. For animated values,
226    /// samples both at the union of all keyframe times and applies the
227    /// combiner at each time.
228    pub fn merge_with<F>(&self, other: &GenericValue<D>, combiner: F) -> Result<GenericValue<D>>
229    where
230        F: Fn(&D, &D) -> D,
231    {
232        match (self, other) {
233            (GenericValue::Uniform(a), GenericValue::Uniform(b)) => {
234                Ok(GenericValue::Uniform(combiner(a, b)))
235            }
236            _ => {
237                let mut all_times = std::collections::BTreeSet::new();
238                for t in self.times() {
239                    all_times.insert(t);
240                }
241                for t in other.times() {
242                    all_times.insert(t);
243                }
244
245                if all_times.is_empty() {
246                    let a = self.interpolate(Time::default());
247                    let b = other.interpolate(Time::default());
248                    return Ok(GenericValue::Uniform(combiner(&a, &b)));
249                }
250
251                let combined_samples: Vec<(Time, D)> = all_times
252                    .into_iter()
253                    .map(|time| {
254                        let a = self.interpolate(time);
255                        let b = other.interpolate(time);
256                        (time, combiner(&a, &b))
257                    })
258                    .collect();
259
260                if combined_samples.len() == 1 {
261                    Ok(GenericValue::Uniform(
262                        // SAFETY: Guarded by combined_samples.len() == 1 check above.
263                        combined_samples.into_iter().next().unwrap().1,
264                    ))
265                } else {
266                    GenericValue::animated(combined_samples)
267                }
268            }
269        }
270    }
271
272    /// Hashes the value with shutter context for animation-aware caching.
273    ///
274    /// For animated values, samples at standardized points within the shutter
275    /// range and hashes the interpolated values.
276    pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
277        match self {
278            GenericValue::Uniform(data) => {
279                data.hash(state);
280            }
281            GenericValue::Animated(animated) => {
282                // Sample at 5 standardized points within the shutter.
283                const SAMPLE_POSITIONS: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
284
285                let samples: SmallVec<[D; 5]> = SAMPLE_POSITIONS
286                    .iter()
287                    .map(|&pos| {
288                        let time = shutter.evaluate(pos);
289                        animated.interpolate(time)
290                    })
291                    .collect();
292
293                let all_same = samples.windows(2).all(|w| w[0] == w[1]);
294
295                std::mem::discriminant(self).hash(state);
296
297                if all_same {
298                    1usize.hash(state);
299                    samples[0].hash(state);
300                } else {
301                    samples.len().hash(state);
302                    for sample in &samples {
303                        sample.hash(state);
304                    }
305                }
306            }
307        }
308    }
309}
310
311impl<D: DataSystem> From<D> for GenericValue<D> {
312    fn from(value: D) -> Self {
313        GenericValue::uniform(value)
314    }
315}
316
317impl<D: DataSystem> Eq for GenericValue<D> {}