Skip to main content

melodium_common/executive/value/
mod.rs

1mod data;
2mod packed;
3mod traits;
4
5use super::Data;
6use crate::descriptor::DataType;
7pub use data::GetData;
8pub use packed::PackedArray;
9use std::sync::Arc;
10
11#[derive(Clone, Debug)]
12pub enum Value {
13    Void(()),
14
15    I8(i8),
16    I16(i16),
17    I32(i32),
18    I64(i64),
19    I128(i128),
20
21    U8(u8),
22    U16(u16),
23    U32(u32),
24    U64(u64),
25    U128(u128),
26
27    F32(f32),
28    F64(f64),
29
30    Bool(bool),
31    Byte(u8),
32    Char(char),
33    String(String),
34
35    Vec(Vec<Value>),
36    Option(Option<Box<Value>>),
37    /// Packed counterpart of `Vec(Vec<Value>)` for a homogeneous scalar array — see
38    /// `PackedArray` and ticket #116. Purely a representation choice: `datatype()`
39    /// reports the same `DataType::Vec(...)` either way.
40    Packed(PackedArray),
41
42    Data(Arc<dyn Data>),
43}
44
45impl Value {
46    pub fn datatype(&self) -> DataType {
47        match self {
48            Value::Void(_) => DataType::Void,
49
50            Value::I8(_) => DataType::I8,
51            Value::I16(_) => DataType::I16,
52            Value::I32(_) => DataType::I32,
53            Value::I64(_) => DataType::I64,
54            Value::I128(_) => DataType::I128,
55
56            Value::U8(_) => DataType::U8,
57            Value::U16(_) => DataType::U16,
58            Value::U32(_) => DataType::U32,
59            Value::U64(_) => DataType::U64,
60            Value::U128(_) => DataType::U128,
61
62            Value::F32(_) => DataType::F32,
63            Value::F64(_) => DataType::F64,
64
65            Value::Bool(_) => DataType::Bool,
66            Value::Byte(_) => DataType::Byte,
67            Value::Char(_) => DataType::Char,
68            Value::String(_) => DataType::String,
69
70            Value::Option(val) => val
71                .as_ref()
72                .map(|val| DataType::Option(Box::new(val.datatype())))
73                .unwrap_or(DataType::Undetermined),
74            Value::Vec(val) => val
75                .first()
76                .map(|val| DataType::Vec(Box::new(val.datatype())))
77                .unwrap_or(DataType::Undetermined),
78            Value::Packed(arr) => DataType::Vec(Box::new(arr.element_datatype())),
79
80            Value::Data(obj) => DataType::Data(obj.descriptor()),
81        }
82    }
83
84    /// Casts to `T`, e.g. `value.try_data::<HttpStatus>()`. A thin forward to `GetData`,
85    /// but as an inherent method with `T` on the *method* rather than the trait, so the
86    /// turbofish goes where callers naturally reach for it and `GetData` doesn't need to
87    /// be imported just to call this directly (outside a generic context where `T` is
88    /// already fixed, e.g. `InputExt::recv_one_as`, this was previously only reachable via
89    /// the more awkward `GetData::<T>::try_data(value)`).
90    pub fn try_data<T>(self) -> Result<T, ()>
91    where
92        Self: GetData<T>,
93    {
94        GetData::<T>::try_data(self)
95    }
96
97    /// Rough memory footprint of this value, in bytes.
98    ///
99    /// Used to bound how much data a transmission buffer accumulates before flushing
100    /// (see `melodium-engine`'s `Output`), so it favors being cheap to compute over being
101    /// exact. Every `Value` occupies `size_of::<Value>()` inline regardless of variant
102    /// (the enum is sized for its largest payload) plus whatever content it owns on the
103    /// heap; `Data` has no cheap way to know its real size without serializing it, so a
104    /// conservative fixed estimate stands in for it.
105    pub fn estimated_size(&self) -> usize {
106        const DATA_ESTIMATE: usize = 128;
107
108        std::mem::size_of::<Value>()
109            + match self {
110                Value::String(value) => value.len(),
111                Value::Vec(values) => values.iter().map(Value::estimated_size).sum(),
112                Value::Option(Some(value)) => value.estimated_size(),
113                Value::Packed(arr) => arr.estimated_size(),
114                Value::Data(_) => DATA_ESTIMATE,
115                _ => 0,
116            }
117    }
118}
119
120impl PartialEq for Value {
121    fn eq(&self, other: &Self) -> bool {
122        match (self, other) {
123            (Self::Void(l0), Self::Void(r0)) => l0 == r0,
124            (Self::I8(l0), Self::I8(r0)) => l0 == r0,
125            (Self::I16(l0), Self::I16(r0)) => l0 == r0,
126            (Self::I32(l0), Self::I32(r0)) => l0 == r0,
127            (Self::I64(l0), Self::I64(r0)) => l0 == r0,
128            (Self::I128(l0), Self::I128(r0)) => l0 == r0,
129            (Self::U8(l0), Self::U8(r0)) => l0 == r0,
130            (Self::U16(l0), Self::U16(r0)) => l0 == r0,
131            (Self::U32(l0), Self::U32(r0)) => l0 == r0,
132            (Self::U64(l0), Self::U64(r0)) => l0 == r0,
133            (Self::U128(l0), Self::U128(r0)) => l0 == r0,
134            (Self::F32(l0), Self::F32(r0)) => l0 == r0,
135            (Self::F64(l0), Self::F64(r0)) => l0 == r0,
136            (Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
137            (Self::Byte(l0), Self::Byte(r0)) => l0 == r0,
138            (Self::Char(l0), Self::Char(r0)) => l0 == r0,
139            (Self::String(l0), Self::String(r0)) => l0 == r0,
140            (Self::Vec(l0), Self::Vec(r0)) => l0 == r0,
141            (Self::Option(l0), Self::Option(r0)) => l0 == r0,
142            (Self::Packed(l0), Self::Packed(r0)) => l0 == r0,
143            (Self::Data(l0), Self::Data(r0)) => {
144                if l0.descriptor() == r0.descriptor() {
145                    if l0
146                        .descriptor()
147                        .implements()
148                        .contains(&crate::descriptor::DataTrait::PartialEquality)
149                    {
150                        l0.partial_equality_eq(other)
151                    } else {
152                        false
153                    }
154                } else {
155                    false
156                }
157            }
158            _ => false,
159        }
160    }
161}
162
163#[cfg(test)]
164mod estimated_size_tests {
165    use super::Value;
166
167    #[test]
168    fn scalar_costs_only_the_enum_footprint() {
169        let base = std::mem::size_of::<Value>();
170        assert_eq!(Value::U64(42).estimated_size(), base);
171        assert_eq!(Value::Void(()).estimated_size(), base);
172    }
173
174    #[test]
175    fn string_costs_enum_footprint_plus_its_bytes() {
176        let base = std::mem::size_of::<Value>();
177        let text = "hello world".to_string();
178        let expected = base + text.len();
179        assert_eq!(Value::String(text).estimated_size(), expected);
180    }
181
182    #[test]
183    fn vec_sums_enum_footprint_of_every_element() {
184        let base = std::mem::size_of::<Value>();
185        let vec = Value::Vec(vec![Value::Byte(1), Value::Byte(2), Value::Byte(3)]);
186        // Outer Vec's own footprint, plus one full Value-sized slot per byte:
187        // this is exactly the ~30x-per-byte blow-up a packed `Value::Bytes` would avoid.
188        assert_eq!(vec.estimated_size(), base + 3 * base);
189    }
190
191    #[test]
192    fn none_option_costs_only_the_enum_footprint() {
193        let base = std::mem::size_of::<Value>();
194        assert_eq!(Value::Option(None).estimated_size(), base);
195    }
196
197    #[test]
198    fn some_option_adds_the_inner_values_size() {
199        let base = std::mem::size_of::<Value>();
200        let text = "abcdef".to_string();
201        let inner_size = base + text.len();
202        let value = Value::Option(Some(Box::new(Value::String(text))));
203        assert_eq!(value.estimated_size(), base + inner_size);
204    }
205}