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