Skip to main content

ad_core/
attributes.rs

1/// Source of an NDAttribute value.
2#[derive(Debug, Clone, PartialEq)]
3pub enum NDAttrSource {
4    Driver,
5    Param { port_name: String, param_name: String },
6    Constant,
7}
8
9/// Data type tags for NDAttribute values.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum NDAttrDataType {
12    Int8,
13    UInt8,
14    Int16,
15    UInt16,
16    Int32,
17    UInt32,
18    Int64,
19    UInt64,
20    Float32,
21    Float64,
22    String,
23}
24
25/// Typed value stored in an NDAttribute.
26#[derive(Debug, Clone, PartialEq)]
27pub enum NDAttrValue {
28    Int8(i8),
29    UInt8(u8),
30    Int16(i16),
31    UInt16(u16),
32    Int32(i32),
33    UInt32(u32),
34    Int64(i64),
35    UInt64(u64),
36    Float32(f32),
37    Float64(f64),
38    String(String),
39}
40
41impl NDAttrValue {
42    pub fn data_type(&self) -> NDAttrDataType {
43        match self {
44            Self::Int8(_) => NDAttrDataType::Int8,
45            Self::UInt8(_) => NDAttrDataType::UInt8,
46            Self::Int16(_) => NDAttrDataType::Int16,
47            Self::UInt16(_) => NDAttrDataType::UInt16,
48            Self::Int32(_) => NDAttrDataType::Int32,
49            Self::UInt32(_) => NDAttrDataType::UInt32,
50            Self::Int64(_) => NDAttrDataType::Int64,
51            Self::UInt64(_) => NDAttrDataType::UInt64,
52            Self::Float32(_) => NDAttrDataType::Float32,
53            Self::Float64(_) => NDAttrDataType::Float64,
54            Self::String(_) => NDAttrDataType::String,
55        }
56    }
57
58    pub fn as_f64(&self) -> Option<f64> {
59        match self {
60            Self::Int8(v) => Some(*v as f64),
61            Self::UInt8(v) => Some(*v as f64),
62            Self::Int16(v) => Some(*v as f64),
63            Self::UInt16(v) => Some(*v as f64),
64            Self::Int32(v) => Some(*v as f64),
65            Self::UInt32(v) => Some(*v as f64),
66            Self::Int64(v) => Some(*v as f64),
67            Self::UInt64(v) => Some(*v as f64),
68            Self::Float32(v) => Some(*v as f64),
69            Self::Float64(v) => Some(*v),
70            Self::String(_) => None,
71        }
72    }
73
74    pub fn as_i64(&self) -> Option<i64> {
75        match self {
76            Self::Int8(v) => Some(*v as i64),
77            Self::UInt8(v) => Some(*v as i64),
78            Self::Int16(v) => Some(*v as i64),
79            Self::UInt16(v) => Some(*v as i64),
80            Self::Int32(v) => Some(*v as i64),
81            Self::UInt32(v) => Some(*v as i64),
82            Self::Int64(v) => Some(*v),
83            Self::UInt64(v) => Some(*v as i64),
84            Self::Float32(v) => Some(*v as i64),
85            Self::Float64(v) => Some(*v as i64),
86            Self::String(_) => None,
87        }
88    }
89
90    pub fn as_string(&self) -> String {
91        match self {
92            Self::Int8(v) => v.to_string(),
93            Self::UInt8(v) => v.to_string(),
94            Self::Int16(v) => v.to_string(),
95            Self::UInt16(v) => v.to_string(),
96            Self::Int32(v) => v.to_string(),
97            Self::UInt32(v) => v.to_string(),
98            Self::Int64(v) => v.to_string(),
99            Self::UInt64(v) => v.to_string(),
100            Self::Float32(v) => v.to_string(),
101            Self::Float64(v) => v.to_string(),
102            Self::String(v) => v.clone(),
103        }
104    }
105}
106
107/// A named attribute attached to an NDArray.
108#[derive(Debug, Clone)]
109pub struct NDAttribute {
110    pub name: String,
111    pub description: String,
112    pub source: NDAttrSource,
113    pub value: NDAttrValue,
114}
115
116/// Collection of NDAttributes on an NDArray.
117#[derive(Debug, Clone, Default)]
118pub struct NDAttributeList {
119    attrs: Vec<NDAttribute>,
120}
121
122impl NDAttributeList {
123    pub fn new() -> Self {
124        Self::default()
125    }
126
127    pub fn add(&mut self, attr: NDAttribute) {
128        if let Some(existing) = self.attrs.iter_mut().find(|a| a.name == attr.name) {
129            *existing = attr;
130        } else {
131            self.attrs.push(attr);
132        }
133    }
134
135    pub fn get(&self, name: &str) -> Option<&NDAttribute> {
136        self.attrs.iter().find(|a| a.name == name)
137    }
138
139    pub fn remove(&mut self, name: &str) -> bool {
140        let len_before = self.attrs.len();
141        self.attrs.retain(|a| a.name != name);
142        self.attrs.len() < len_before
143    }
144
145    pub fn clear(&mut self) {
146        self.attrs.clear();
147    }
148
149    pub fn iter(&self) -> impl Iterator<Item = &NDAttribute> {
150        self.attrs.iter()
151    }
152
153    pub fn len(&self) -> usize {
154        self.attrs.len()
155    }
156
157    pub fn is_empty(&self) -> bool {
158        self.attrs.is_empty()
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165
166    #[test]
167    fn test_add_get() {
168        let mut list = NDAttributeList::new();
169        list.add(NDAttribute {
170            name: "ColorMode".into(),
171            description: "Color mode".into(),
172            source: NDAttrSource::Driver,
173            value: NDAttrValue::Int32(0),
174        });
175        let attr = list.get("ColorMode").unwrap();
176        assert_eq!(attr.value, NDAttrValue::Int32(0));
177    }
178
179    #[test]
180    fn test_replace_existing() {
181        let mut list = NDAttributeList::new();
182        list.add(NDAttribute {
183            name: "Gain".into(),
184            description: "".into(),
185            source: NDAttrSource::Driver,
186            value: NDAttrValue::Float64(1.0),
187        });
188        list.add(NDAttribute {
189            name: "Gain".into(),
190            description: "".into(),
191            source: NDAttrSource::Driver,
192            value: NDAttrValue::Float64(2.5),
193        });
194        assert_eq!(list.len(), 1);
195        assert_eq!(list.get("Gain").unwrap().value, NDAttrValue::Float64(2.5));
196    }
197
198    #[test]
199    fn test_iter() {
200        let mut list = NDAttributeList::new();
201        list.add(NDAttribute {
202            name: "A".into(),
203            description: "".into(),
204            source: NDAttrSource::Constant,
205            value: NDAttrValue::Int32(1),
206        });
207        list.add(NDAttribute {
208            name: "B".into(),
209            description: "".into(),
210            source: NDAttrSource::Constant,
211            value: NDAttrValue::String("hello".into()),
212        });
213        let names: Vec<_> = list.iter().map(|a| a.name.as_str()).collect();
214        assert_eq!(names, vec!["A", "B"]);
215    }
216
217    #[test]
218    fn test_get_missing() {
219        let list = NDAttributeList::new();
220        assert!(list.get("nope").is_none());
221    }
222
223    #[test]
224    fn test_empty() {
225        let list = NDAttributeList::new();
226        assert!(list.is_empty());
227        assert_eq!(list.len(), 0);
228    }
229
230    #[test]
231    fn test_all_data_types() {
232        let values = vec![
233            NDAttrValue::Int8(-1),
234            NDAttrValue::UInt8(255),
235            NDAttrValue::Int16(-100),
236            NDAttrValue::UInt16(1000),
237            NDAttrValue::Int32(-50000),
238            NDAttrValue::UInt32(50000),
239            NDAttrValue::Int64(-1_000_000),
240            NDAttrValue::UInt64(1_000_000),
241            NDAttrValue::Float32(3.14),
242            NDAttrValue::Float64(2.718),
243            NDAttrValue::String("test".into()),
244        ];
245        for v in &values {
246            assert_eq!(v.data_type(), v.data_type());
247        }
248    }
249
250    #[test]
251    fn test_source_tracking() {
252        let attr = NDAttribute {
253            name: "temp".into(),
254            description: "temperature".into(),
255            source: NDAttrSource::Param {
256                port_name: "SIM1".into(),
257                param_name: "TEMPERATURE".into(),
258            },
259            value: NDAttrValue::Float64(25.0),
260        };
261        match &attr.source {
262            NDAttrSource::Param { port_name, param_name } => {
263                assert_eq!(port_name, "SIM1");
264                assert_eq!(param_name, "TEMPERATURE");
265            }
266            _ => panic!("wrong source type"),
267        }
268    }
269
270    #[test]
271    fn test_value_conversions() {
272        let v = NDAttrValue::Int32(42);
273        assert_eq!(v.as_f64(), Some(42.0));
274        assert_eq!(v.as_i64(), Some(42));
275        assert_eq!(v.as_string(), "42");
276
277        let s = NDAttrValue::String("hello".into());
278        assert_eq!(s.as_f64(), None);
279        assert_eq!(s.as_i64(), None);
280        assert_eq!(s.as_string(), "hello");
281    }
282
283    #[test]
284    fn test_remove() {
285        let mut list = NDAttributeList::new();
286        list.add(NDAttribute {
287            name: "A".into(),
288            description: "".into(),
289            source: NDAttrSource::Driver,
290            value: NDAttrValue::Int32(1),
291        });
292        assert!(list.remove("A"));
293        assert!(list.is_empty());
294        assert!(!list.remove("A"));
295    }
296}