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_at_or_to_uniform`](Self::remove_at_or_to_uniform)
126    /// to degrade to uniform instead.
127    pub fn remove_at(&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    /// Deprecated alias for [`remove_at`](Self::remove_at).
135    #[deprecated(since = "0.2.2", note = "renamed to `remove_at`")]
136    pub fn remove_sample(&mut self, time: &Time) -> Option<D> {
137        self.remove_at(time)
138    }
139
140    /// Remove a sample, converting to uniform if it was the last keyframe.
141    ///
142    /// If the value is uniform, returns `None`. If the removed sample was
143    /// the last keyframe, the value degrades from [`GenericValue::Animated`]
144    /// to [`GenericValue::Uniform`] with that keyframe's value and returns
145    /// `None` (the data is preserved, not lost). Otherwise returns the
146    /// removed value.
147    pub fn remove_at_or_to_uniform(&mut self, time: &Time) -> Option<D> {
148        match self {
149            GenericValue::Uniform(_) => None,
150            GenericValue::Animated(samples) => {
151                let removed = samples.remove_at(time);
152                if removed.is_some() {
153                    return removed;
154                }
155                // remove_at returned None -- either key not found or last sample.
156                // Check if it's the last sample at this time.
157                if samples.sample_at(*time).is_some() {
158                    // It's the last sample -- degrade to uniform.
159                    let data = samples.interpolate(Time::default());
160                    *self = GenericValue::Uniform(data);
161                }
162                None
163            }
164        }
165    }
166
167    /// Samples the value at an exact time without interpolation.
168    ///
169    /// Returns the exact value if it exists at the given time, or `None` if
170    /// no sample exists at that time for animated values.
171    pub fn sample_at(&self, time: Time) -> Option<D> {
172        match self {
173            GenericValue::Uniform(v) => Some(v.clone()),
174            GenericValue::Animated(samples) => samples.sample_at(time),
175        }
176    }
177
178    /// Interpolates the value at the given time.
179    ///
180    /// For uniform values, returns the constant value. For animated values,
181    /// interpolates between surrounding keyframes.
182    pub fn interpolate(&self, time: Time) -> D {
183        match self {
184            GenericValue::Uniform(v) => v.clone(),
185            GenericValue::Animated(samples) => samples.interpolate(time),
186        }
187    }
188
189    /// Returns `true` if the value is animated (has multiple keyframes).
190    pub fn is_animated(&self) -> bool {
191        match self {
192            GenericValue::Uniform(_) => false,
193            GenericValue::Animated(samples) => samples.has_animation(),
194        }
195    }
196
197    /// Returns the number of time samples.
198    pub fn sample_count(&self) -> usize {
199        match self {
200            GenericValue::Uniform(_) => 1,
201            GenericValue::Animated(samples) => samples.keyframe_count(),
202        }
203    }
204
205    /// Returns all keyframe times.
206    pub fn times(&self) -> SmallVec<[Time; 10]> {
207        match self {
208            GenericValue::Uniform(_) => SmallVec::new_const(),
209            GenericValue::Animated(samples) => samples.times(),
210        }
211    }
212
213    /// Returns the data type discriminant for this value.
214    pub fn discriminant(&self) -> D::DataType {
215        match self {
216            GenericValue::Uniform(data) => data.discriminant(),
217            GenericValue::Animated(animated) => animated.discriminant(),
218        }
219    }
220
221    /// Returns a human-readable type name for this value.
222    pub fn variant_name(&self) -> &'static str {
223        match self {
224            GenericValue::Uniform(data) => data.variant_name(),
225            GenericValue::Animated(animated) => animated.variant_name(),
226        }
227    }
228
229    /// Merges this value with another using a combiner function.
230    ///
231    /// For uniform values, applies the combiner once. For animated values,
232    /// samples both at the union of all keyframe times and applies the
233    /// combiner at each time.
234    pub fn merge_with<F>(&self, other: &GenericValue<D>, combiner: F) -> Result<GenericValue<D>>
235    where
236        F: Fn(&D, &D) -> D,
237    {
238        match (self, other) {
239            (GenericValue::Uniform(a), GenericValue::Uniform(b)) => {
240                Ok(GenericValue::Uniform(combiner(a, b)))
241            }
242            _ => {
243                let mut all_times = std::collections::BTreeSet::new();
244                for t in self.times() {
245                    all_times.insert(t);
246                }
247                for t in other.times() {
248                    all_times.insert(t);
249                }
250
251                if all_times.is_empty() {
252                    let a = self.interpolate(Time::default());
253                    let b = other.interpolate(Time::default());
254                    return Ok(GenericValue::Uniform(combiner(&a, &b)));
255                }
256
257                let combined_samples: Vec<(Time, D)> = all_times
258                    .into_iter()
259                    .map(|time| {
260                        let a = self.interpolate(time);
261                        let b = other.interpolate(time);
262                        (time, combiner(&a, &b))
263                    })
264                    .collect();
265
266                if combined_samples.len() == 1 {
267                    Ok(GenericValue::Uniform(
268                        // SAFETY: Guarded by combined_samples.len() == 1 check above.
269                        combined_samples.into_iter().next().unwrap().1,
270                    ))
271                } else {
272                    GenericValue::animated(combined_samples)
273                }
274            }
275        }
276    }
277
278    /// Hashes the value with shutter context for animation-aware caching.
279    ///
280    /// For animated values, samples at standardized points within the shutter
281    /// range and hashes the interpolated values.
282    pub fn hash_with_shutter<H: Hasher>(&self, state: &mut H, shutter: &Shutter) {
283        match self {
284            GenericValue::Uniform(data) => {
285                data.hash(state);
286            }
287            GenericValue::Animated(animated) => {
288                // Sample at 5 standardized points within the shutter.
289                const SAMPLE_POSITIONS: [f32; 5] = [0.0, 0.25, 0.5, 0.75, 1.0];
290
291                let samples: SmallVec<[D; 5]> = SAMPLE_POSITIONS
292                    .iter()
293                    .map(|&pos| {
294                        let time = shutter.evaluate(pos);
295                        animated.interpolate(time)
296                    })
297                    .collect();
298
299                let all_same = samples.windows(2).all(|w| w[0] == w[1]);
300
301                std::mem::discriminant(self).hash(state);
302
303                if all_same {
304                    1usize.hash(state);
305                    samples[0].hash(state);
306                } else {
307                    samples.len().hash(state);
308                    for sample in &samples {
309                        sample.hash(state);
310                    }
311                }
312            }
313        }
314    }
315}
316
317impl<D: DataSystem> From<D> for GenericValue<D> {
318    fn from(value: D) -> Self {
319        GenericValue::uniform(value)
320    }
321}
322
323impl<D: DataSystem> Eq for GenericValue<D> {}