Skip to main content

melodium_common/executive/value/
packed.rs

1use super::data::GetData;
2use super::Value;
3use crate::descriptor::DataType;
4use std::any::Any;
5use std::sync::Arc;
6
7/// A contiguous, `Arc`-shared array of one primitive scalar type.
8///
9/// Backs `Value::Packed`: the packed counterpart of `Value::Vec(Vec<Value>)` for a
10/// homogeneous scalar array. Storage is `size_of::<T>()` bytes per element instead of
11/// one full `Value` enum instance (~24-32 bytes) per element.
12///
13/// Wraps `Arc<Vec<T>>` rather than `Arc<[T]>`: converting an existing `Vec<T>` into
14/// `Arc<Vec<T>>` (`Arc::new`) never touches the element data — only a small new
15/// allocation for the refcount header, with the `Vec`'s own `{ptr,len,cap}` moved into
16/// it. `Arc<[T]>` looks similar but is a dynamically-sized type that must sit directly
17/// beside its refcount header in one allocation, so `Arc<[T]>::from(vec)` always
18/// reallocates and copies every element (verified empirically). The trade is that a
19/// `Vec` built with spare capacity (e.g. via repeated `push`) keeps that slack for the
20/// `Arc`'s lifetime instead of being trimmed — bounded (typically ≤2x from geometric
21/// growth) and dwarfed by the per-element `Value` overhead this type avoids in the
22/// first place. See ticket #116.
23#[derive(Clone, Debug, PartialEq)]
24pub enum PackedArray {
25    I8(Arc<Vec<i8>>),
26    I16(Arc<Vec<i16>>),
27    I32(Arc<Vec<i32>>),
28    I64(Arc<Vec<i64>>),
29    I128(Arc<Vec<i128>>),
30
31    U8(Arc<Vec<u8>>),
32    U16(Arc<Vec<u16>>),
33    U32(Arc<Vec<u32>>),
34    U64(Arc<Vec<u64>>),
35    U128(Arc<Vec<u128>>),
36
37    F32(Arc<Vec<f32>>),
38    F64(Arc<Vec<f64>>),
39
40    Bool(Arc<Vec<bool>>),
41    /// Distinct from `U8`, mirroring `Value::Byte` vs `Value::U8`: this is the packed
42    /// form of the mel `byte` type specifically, not of generic 8-bit unsigned integers.
43    Byte(Arc<Vec<u8>>),
44    Char(Arc<Vec<char>>),
45}
46
47/// Reusable single-shot `Any` downcast: `Ok` if the caller's `T` is a real match for the
48/// runtime type `U`, `Err` with the input handed back unchanged otherwise. Shared by both
49/// `try_from_vec` and `try_into_vec` so the "probe a chain of concrete candidate types"
50/// pattern is written once.
51fn downcast_vec<T: 'static, U: 'static>(value: Vec<U>) -> Result<Vec<T>, Vec<U>> {
52    let boxed: Box<dyn Any> = Box::new(value);
53    match boxed.downcast::<Vec<T>>() {
54        Ok(value) => Ok(*value),
55        Err(boxed) => Err(*boxed.downcast::<Vec<U>>().unwrap()),
56    }
57}
58
59impl PackedArray {
60    pub fn len(&self) -> usize {
61        match self {
62            PackedArray::I8(a) => a.len(),
63            PackedArray::I16(a) => a.len(),
64            PackedArray::I32(a) => a.len(),
65            PackedArray::I64(a) => a.len(),
66            PackedArray::I128(a) => a.len(),
67            PackedArray::U8(a) => a.len(),
68            PackedArray::U16(a) => a.len(),
69            PackedArray::U32(a) => a.len(),
70            PackedArray::U64(a) => a.len(),
71            PackedArray::U128(a) => a.len(),
72            PackedArray::F32(a) => a.len(),
73            PackedArray::F64(a) => a.len(),
74            PackedArray::Bool(a) => a.len(),
75            PackedArray::Byte(a) => a.len(),
76            PackedArray::Char(a) => a.len(),
77        }
78    }
79
80    pub fn is_empty(&self) -> bool {
81        self.len() == 0
82    }
83
84    /// The logical element type, used by `Value::datatype()` to report `Vec(element)`
85    /// regardless of whether the value happens to be packed or boxed — `Packed` is
86    /// purely an internal representation choice, invisible to the mel type system.
87    pub fn element_datatype(&self) -> DataType {
88        match self {
89            PackedArray::I8(_) => DataType::I8,
90            PackedArray::I16(_) => DataType::I16,
91            PackedArray::I32(_) => DataType::I32,
92            PackedArray::I64(_) => DataType::I64,
93            PackedArray::I128(_) => DataType::I128,
94            PackedArray::U8(_) => DataType::U8,
95            PackedArray::U16(_) => DataType::U16,
96            PackedArray::U32(_) => DataType::U32,
97            PackedArray::U64(_) => DataType::U64,
98            PackedArray::U128(_) => DataType::U128,
99            PackedArray::F32(_) => DataType::F32,
100            PackedArray::F64(_) => DataType::F64,
101            PackedArray::Bool(_) => DataType::Bool,
102            PackedArray::Byte(_) => DataType::Byte,
103            PackedArray::Char(_) => DataType::Char,
104        }
105    }
106
107    /// Rough memory footprint of the array's content, in bytes: element count times
108    /// element width, with no per-element `Value` overhead (see `Value::estimated_size`).
109    pub fn estimated_size(&self) -> usize {
110        match self {
111            PackedArray::I8(a) => a.len() * std::mem::size_of::<i8>(),
112            PackedArray::I16(a) => a.len() * std::mem::size_of::<i16>(),
113            PackedArray::I32(a) => a.len() * std::mem::size_of::<i32>(),
114            PackedArray::I64(a) => a.len() * std::mem::size_of::<i64>(),
115            PackedArray::I128(a) => a.len() * std::mem::size_of::<i128>(),
116            PackedArray::U8(a) => a.len() * std::mem::size_of::<u8>(),
117            PackedArray::U16(a) => a.len() * std::mem::size_of::<u16>(),
118            PackedArray::U32(a) => a.len() * std::mem::size_of::<u32>(),
119            PackedArray::U64(a) => a.len() * std::mem::size_of::<u64>(),
120            PackedArray::U128(a) => a.len() * std::mem::size_of::<u128>(),
121            PackedArray::F32(a) => a.len() * std::mem::size_of::<f32>(),
122            PackedArray::F64(a) => a.len() * std::mem::size_of::<f64>(),
123            PackedArray::Bool(a) => a.len() * std::mem::size_of::<bool>(),
124            PackedArray::Byte(a) => a.len(),
125            PackedArray::Char(a) => a.len() * std::mem::size_of::<char>(),
126        }
127    }
128
129    /// Expands into one `Value` per element. This is the correctness fallback for
130    /// contexts that genuinely need the fully-boxed representation (e.g. `Display`) — it
131    /// reintroduces exactly the per-element overhead `Packed` exists to avoid. Prefer
132    /// `try_into_vec`/`GetData<Arc<Vec<T>>>` wherever the target type is known.
133    pub fn into_values(self) -> Vec<Value> {
134        match self {
135            PackedArray::I8(a) => a.iter().copied().map(Value::I8).collect(),
136            PackedArray::I16(a) => a.iter().copied().map(Value::I16).collect(),
137            PackedArray::I32(a) => a.iter().copied().map(Value::I32).collect(),
138            PackedArray::I64(a) => a.iter().copied().map(Value::I64).collect(),
139            PackedArray::I128(a) => a.iter().copied().map(Value::I128).collect(),
140            PackedArray::U8(a) => a.iter().copied().map(Value::U8).collect(),
141            PackedArray::U16(a) => a.iter().copied().map(Value::U16).collect(),
142            PackedArray::U32(a) => a.iter().copied().map(Value::U32).collect(),
143            PackedArray::U64(a) => a.iter().copied().map(Value::U64).collect(),
144            PackedArray::U128(a) => a.iter().copied().map(Value::U128).collect(),
145            PackedArray::F32(a) => a.iter().copied().map(Value::F32).collect(),
146            PackedArray::F64(a) => a.iter().copied().map(Value::F64).collect(),
147            PackedArray::Bool(a) => a.iter().copied().map(Value::Bool).collect(),
148            PackedArray::Byte(a) => a.iter().copied().map(Value::Byte).collect(),
149            PackedArray::Char(a) => a.iter().copied().map(Value::Char).collect(),
150        }
151    }
152
153    /// Attempts to pack a `Vec<T>` using only `T: 'static` — no trait bound naming the
154    /// packable types is needed, since the check happens at runtime via `Any`. Returns
155    /// the vec back unchanged if `T` isn't one of the packable primitives, so the caller
156    /// can fall back to the ordinary boxed representation. Never produces `Byte`: a
157    /// `Vec<u8>` is ambiguous between "bytes" and "generic small integers", so this
158    /// always resolves it to `U8`, exactly like `Value::from(u8)` never producing
159    /// `Value::Byte` on its own.
160    pub fn try_from_vec<T: 'static>(value: Vec<T>) -> Result<PackedArray, Vec<T>> {
161        let value = match downcast_vec::<i8, T>(value) {
162            Ok(v) => return Ok(PackedArray::I8(Arc::new(v))),
163            Err(v) => v,
164        };
165        let value = match downcast_vec::<i16, T>(value) {
166            Ok(v) => return Ok(PackedArray::I16(Arc::new(v))),
167            Err(v) => v,
168        };
169        let value = match downcast_vec::<i32, T>(value) {
170            Ok(v) => return Ok(PackedArray::I32(Arc::new(v))),
171            Err(v) => v,
172        };
173        let value = match downcast_vec::<i64, T>(value) {
174            Ok(v) => return Ok(PackedArray::I64(Arc::new(v))),
175            Err(v) => v,
176        };
177        let value = match downcast_vec::<i128, T>(value) {
178            Ok(v) => return Ok(PackedArray::I128(Arc::new(v))),
179            Err(v) => v,
180        };
181        let value = match downcast_vec::<u8, T>(value) {
182            Ok(v) => return Ok(PackedArray::U8(Arc::new(v))),
183            Err(v) => v,
184        };
185        let value = match downcast_vec::<u16, T>(value) {
186            Ok(v) => return Ok(PackedArray::U16(Arc::new(v))),
187            Err(v) => v,
188        };
189        let value = match downcast_vec::<u32, T>(value) {
190            Ok(v) => return Ok(PackedArray::U32(Arc::new(v))),
191            Err(v) => v,
192        };
193        let value = match downcast_vec::<u64, T>(value) {
194            Ok(v) => return Ok(PackedArray::U64(Arc::new(v))),
195            Err(v) => v,
196        };
197        let value = match downcast_vec::<u128, T>(value) {
198            Ok(v) => return Ok(PackedArray::U128(Arc::new(v))),
199            Err(v) => v,
200        };
201        let value = match downcast_vec::<f32, T>(value) {
202            Ok(v) => return Ok(PackedArray::F32(Arc::new(v))),
203            Err(v) => v,
204        };
205        let value = match downcast_vec::<f64, T>(value) {
206            Ok(v) => return Ok(PackedArray::F64(Arc::new(v))),
207            Err(v) => v,
208        };
209        let value = match downcast_vec::<bool, T>(value) {
210            Ok(v) => return Ok(PackedArray::Bool(Arc::new(v))),
211            Err(v) => v,
212        };
213        let value = match downcast_vec::<char, T>(value) {
214            Ok(v) => return Ok(PackedArray::Char(Arc::new(v))),
215            Err(v) => v,
216        };
217        Err(value)
218    }
219
220    /// Attempts to extract a `Vec<T>` directly, with no `Value` ever created along the
221    /// way — the fast counterpart to `into_values()` followed by per-element
222    /// `GetData::<T>::try_data`. If the array's element type doesn't already match `T`
223    /// exactly (via `Any`), the original `PackedArray` is handed back unchanged.
224    ///
225    /// Only makes one copy: `Arc::try_unwrap` succeeds for free when this is the sole
226    /// reference to the array (the common case for a value that just arrived off a
227    /// channel); it falls back to cloning only when the array is still shared elsewhere.
228    pub fn try_into_vec<T: 'static>(self) -> Result<Vec<T>, PackedArray> {
229        fn owned<U: Clone>(arc: Arc<Vec<U>>) -> Vec<U> {
230            Arc::try_unwrap(arc).unwrap_or_else(|arc| (*arc).clone())
231        }
232
233        match self {
234            PackedArray::I8(a) => {
235                downcast_vec::<T, i8>(owned(a)).map_err(|v| PackedArray::I8(Arc::new(v)))
236            }
237            PackedArray::I16(a) => {
238                downcast_vec::<T, i16>(owned(a)).map_err(|v| PackedArray::I16(Arc::new(v)))
239            }
240            PackedArray::I32(a) => {
241                downcast_vec::<T, i32>(owned(a)).map_err(|v| PackedArray::I32(Arc::new(v)))
242            }
243            PackedArray::I64(a) => {
244                downcast_vec::<T, i64>(owned(a)).map_err(|v| PackedArray::I64(Arc::new(v)))
245            }
246            PackedArray::I128(a) => {
247                downcast_vec::<T, i128>(owned(a)).map_err(|v| PackedArray::I128(Arc::new(v)))
248            }
249            PackedArray::U8(a) => {
250                downcast_vec::<T, u8>(owned(a)).map_err(|v| PackedArray::U8(Arc::new(v)))
251            }
252            PackedArray::U16(a) => {
253                downcast_vec::<T, u16>(owned(a)).map_err(|v| PackedArray::U16(Arc::new(v)))
254            }
255            PackedArray::U32(a) => {
256                downcast_vec::<T, u32>(owned(a)).map_err(|v| PackedArray::U32(Arc::new(v)))
257            }
258            PackedArray::U64(a) => {
259                downcast_vec::<T, u64>(owned(a)).map_err(|v| PackedArray::U64(Arc::new(v)))
260            }
261            PackedArray::U128(a) => {
262                downcast_vec::<T, u128>(owned(a)).map_err(|v| PackedArray::U128(Arc::new(v)))
263            }
264            PackedArray::F32(a) => {
265                downcast_vec::<T, f32>(owned(a)).map_err(|v| PackedArray::F32(Arc::new(v)))
266            }
267            PackedArray::F64(a) => {
268                downcast_vec::<T, f64>(owned(a)).map_err(|v| PackedArray::F64(Arc::new(v)))
269            }
270            PackedArray::Bool(a) => {
271                downcast_vec::<T, bool>(owned(a)).map_err(|v| PackedArray::Bool(Arc::new(v)))
272            }
273            PackedArray::Byte(a) => {
274                downcast_vec::<T, u8>(owned(a)).map_err(|v| PackedArray::Byte(Arc::new(v)))
275            }
276            PackedArray::Char(a) => {
277                downcast_vec::<T, char>(owned(a)).map_err(|v| PackedArray::Char(Arc::new(v)))
278            }
279        }
280    }
281}
282
283impl From<PackedArray> for Value {
284    fn from(value: PackedArray) -> Self {
285        Value::Packed(value)
286    }
287}
288
289/// Direct bridge from an already-packed array to `Value`, for `send_one_as`/callers
290/// that already hold an `Arc<Vec<T>>` (e.g. forwarding one received via
291/// `GetData<Arc<Vec<T>>>` without ever unpacking it) — composes through `PackedArray`'s
292/// own per-type `From<Arc<Vec<T>>>` impls, so it's only available for `T`s that are
293/// actually packable.
294impl<T> From<Arc<Vec<T>>> for Value
295where
296    PackedArray: From<Arc<Vec<T>>>,
297{
298    fn from(value: Arc<Vec<T>>) -> Self {
299        Value::Packed(PackedArray::from(value))
300    }
301}
302
303impl GetData<PackedArray> for Value {
304    fn try_data(self) -> Result<PackedArray, ()> {
305        match self {
306            Value::Packed(arr) => Ok(arr),
307            _ => Err(()),
308        }
309    }
310}
311
312// `From<Vec<T>>`/`From<Arc<Vec<T>>> for PackedArray` and the matching
313// `GetData<Arc<Vec<T>>> for Value` fast extractor follow the exact same shape for every
314// scalar type, so they're generated rather than hand-duplicated — see ticket #120's
315// precedent for `TransmissionValue`. `U8` is the one type kept hand-written below, for
316// the same reason it is in `TransmissionValue`: `Byte` and `U8` are both `u8`-backed but
317// semantically distinct (mel `byte` vs a generic 8-bit integer), so `From<Vec<u8>>` must
318// stay unambiguous (always `U8`) while extraction accepts either.
319macro_rules! packed_array_scalar_type {
320    ($variant:ident, $ty:ty) => {
321        impl From<Vec<$ty>> for PackedArray {
322            fn from(value: Vec<$ty>) -> Self {
323                PackedArray::$variant(Arc::new(value))
324            }
325        }
326
327        impl From<Arc<Vec<$ty>>> for PackedArray {
328            fn from(value: Arc<Vec<$ty>>) -> Self {
329                PackedArray::$variant(value)
330            }
331        }
332
333        impl GetData<Arc<Vec<$ty>>> for Value {
334            fn try_data(self) -> Result<Arc<Vec<$ty>>, ()> {
335                match self {
336                    Value::Packed(PackedArray::$variant(arr)) => Ok(arr),
337                    _ => Err(()),
338                }
339            }
340        }
341    };
342}
343
344packed_array_scalar_type!(I8, i8);
345packed_array_scalar_type!(I16, i16);
346packed_array_scalar_type!(I32, i32);
347packed_array_scalar_type!(I64, i64);
348packed_array_scalar_type!(I128, i128);
349packed_array_scalar_type!(U16, u16);
350packed_array_scalar_type!(U32, u32);
351packed_array_scalar_type!(U64, u64);
352packed_array_scalar_type!(U128, u128);
353packed_array_scalar_type!(F32, f32);
354packed_array_scalar_type!(F64, f64);
355packed_array_scalar_type!(Bool, bool);
356packed_array_scalar_type!(Char, char);
357
358impl From<Vec<u8>> for PackedArray {
359    fn from(value: Vec<u8>) -> Self {
360        PackedArray::U8(Arc::new(value))
361    }
362}
363
364impl From<Arc<Vec<u8>>> for PackedArray {
365    fn from(value: Arc<Vec<u8>>) -> Self {
366        PackedArray::U8(value)
367    }
368}
369
370impl GetData<Arc<Vec<u8>>> for Value {
371    fn try_data(self) -> Result<Arc<Vec<u8>>, ()> {
372        match self {
373            Value::Packed(PackedArray::U8(arr)) => Ok(arr),
374            Value::Packed(PackedArray::Byte(arr)) => Ok(arr),
375            _ => Err(()),
376        }
377    }
378}
379
380#[cfg(test)]
381mod packed_array_tests {
382    use super::*;
383
384    #[test]
385    fn estimated_size_has_no_per_element_value_overhead() {
386        let arr = PackedArray::U8(Arc::new(vec![0u8; 4096]));
387        assert_eq!(arr.estimated_size(), 4096);
388
389        let arr = PackedArray::F64(Arc::new(vec![0f64; 10]));
390        assert_eq!(arr.estimated_size(), 10 * std::mem::size_of::<f64>());
391    }
392
393    #[test]
394    fn value_packed_reports_the_same_datatype_as_value_vec() {
395        let packed = Value::Packed(PackedArray::Byte(Arc::new(vec![1u8, 2, 3])));
396        let boxed = Value::Vec(vec![Value::Byte(1), Value::Byte(2), Value::Byte(3)]);
397        assert_eq!(packed.datatype(), boxed.datatype());
398    }
399
400    #[test]
401    fn value_packed_estimated_size_has_no_per_element_value_overhead() {
402        let base = std::mem::size_of::<Value>();
403        let value = Value::Packed(PackedArray::Byte(Arc::new(vec![0u8; 4096])));
404        // Contrast with `Value::Vec(Vec<Value::Byte>)`, which costs `base + n * base`
405        // for the same content (see `estimated_size_tests::vec_sums_enum_footprint_of_every_element`).
406        assert_eq!(value.estimated_size(), base + 4096);
407    }
408
409    #[test]
410    fn arc_u8_extraction_accepts_both_u8_and_byte_variants() {
411        let as_u8 = Value::Packed(PackedArray::U8(Arc::new(vec![1u8, 2, 3])));
412        let as_byte = Value::Packed(PackedArray::Byte(Arc::new(vec![4u8, 5, 6])));
413
414        let from_u8: Arc<Vec<u8>> = as_u8.try_data().unwrap();
415        let from_byte: Arc<Vec<u8>> = as_byte.try_data().unwrap();
416
417        assert_eq!(&*from_u8, &vec![1, 2, 3]);
418        assert_eq!(&*from_byte, &vec![4, 5, 6]);
419    }
420
421    #[test]
422    fn from_vec_u8_always_produces_u8_not_byte() {
423        let packed: PackedArray = vec![1u8, 2, 3].into();
424        assert!(matches!(packed, PackedArray::U8(_)));
425    }
426
427    #[test]
428    fn mismatched_type_extraction_fails() {
429        let value = Value::Packed(PackedArray::I64(Arc::new(vec![1i64])));
430        let result: Result<Arc<Vec<u8>>, ()> = value.try_data();
431        assert!(result.is_err());
432    }
433
434    #[test]
435    fn into_values_roundtrips_element_by_element() {
436        let packed = PackedArray::Byte(Arc::new(vec![1u8, 2, 3]));
437        assert_eq!(
438            packed.into_values(),
439            vec![Value::Byte(1), Value::Byte(2), Value::Byte(3)]
440        );
441    }
442
443    #[test]
444    fn try_from_vec_packs_a_recognized_primitive_type() {
445        let packed = PackedArray::try_from_vec(vec![1u8, 2, 3]).unwrap();
446        assert!(matches!(packed, PackedArray::U8(_)));
447    }
448
449    #[test]
450    fn try_from_vec_hands_back_the_vec_unchanged_for_a_non_packable_type() {
451        let original = vec!["a".to_string(), "b".to_string()];
452        let result = PackedArray::try_from_vec(original.clone());
453        assert_eq!(result, Err(original));
454    }
455
456    #[test]
457    fn try_into_vec_extracts_without_creating_any_value() {
458        let packed = PackedArray::I64(Arc::new(vec![1i64, 2, 3]));
459        let extracted: Vec<i64> = packed.try_into_vec().unwrap();
460        assert_eq!(extracted, vec![1, 2, 3]);
461    }
462
463    #[test]
464    fn try_into_vec_hands_back_the_packed_array_unchanged_on_mismatch() {
465        let packed = PackedArray::I64(Arc::new(vec![1i64, 2, 3]));
466        let result = packed.clone().try_into_vec::<u8>();
467        assert_eq!(result, Err(packed));
468    }
469
470    #[test]
471    fn arc_vec_converts_directly_into_value_packed() {
472        let arc = Arc::new(vec![1u8, 2, 3]);
473        let value: Value = arc.into();
474        assert!(matches!(value, Value::Packed(PackedArray::U8(_))));
475    }
476
477    #[test]
478    fn try_into_vec_does_not_copy_when_exclusively_owned() {
479        let arc = Arc::new(vec![1u8, 2, 3]);
480        let data_ptr = arc.as_ptr();
481        let packed = PackedArray::U8(arc);
482        let extracted: Vec<u8> = packed.try_into_vec().unwrap();
483        assert_eq!(extracted.as_ptr(), data_ptr);
484    }
485}