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