1use std::collections::VecDeque;
12
13use ad_core::ndarray::NDArray;
14use ad_core::ndarray_pool::NDArrayPool;
15use ad_core::plugin::runtime::{NDPluginProcess, ProcessResult};
16
17pub struct AttrPlotProcessor {
19 attributes: Vec<String>,
21 buffers: Vec<VecDeque<f64>>,
23 uid_buffer: VecDeque<f64>,
25 max_points: usize,
27 initialized: bool,
29 last_uid: i32,
31}
32
33impl AttrPlotProcessor {
34 pub fn new(max_points: usize) -> Self {
36 Self {
37 attributes: Vec::new(),
38 buffers: Vec::new(),
39 uid_buffer: VecDeque::new(),
40 max_points,
41 initialized: false,
42 last_uid: -1,
43 }
44 }
45
46 pub fn attributes(&self) -> &[String] {
48 &self.attributes
49 }
50
51 pub fn buffer(&self, index: usize) -> Option<&VecDeque<f64>> {
53 self.buffers.get(index)
54 }
55
56 pub fn uid_buffer(&self) -> &VecDeque<f64> {
58 &self.uid_buffer
59 }
60
61 pub fn num_attributes(&self) -> usize {
63 self.attributes.len()
64 }
65
66 pub fn find_attribute(&self, name: &str) -> Option<usize> {
68 self.attributes.iter().position(|n| n == name)
69 }
70
71 pub fn reset(&mut self) {
73 self.attributes.clear();
74 self.buffers.clear();
75 self.uid_buffer.clear();
76 self.initialized = false;
77 self.last_uid = -1;
78 }
79
80 fn push_capped(buf: &mut VecDeque<f64>, value: f64, max_points: usize) {
82 if max_points > 0 && buf.len() >= max_points {
83 buf.pop_front();
84 }
85 buf.push_back(value);
86 }
87
88 fn initialize_from_array(&mut self, array: &NDArray) {
90 let mut names: Vec<String> = Vec::new();
91 for attr in array.attributes.iter() {
92 if attr.value.as_f64().is_some() {
93 names.push(attr.name.clone());
94 }
95 }
96 names.sort();
97
98 self.buffers = vec![VecDeque::new(); names.len()];
99 self.attributes = names;
100 self.uid_buffer.clear();
101 self.initialized = true;
102 }
103
104 fn clear_buffers(&mut self) {
106 for buf in &mut self.buffers {
107 buf.clear();
108 }
109 self.uid_buffer.clear();
110 }
111}
112
113impl NDPluginProcess for AttrPlotProcessor {
114 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
115 if self.initialized && array.unique_id < self.last_uid {
117 self.clear_buffers();
118 }
119 self.last_uid = array.unique_id;
120
121 if !self.initialized {
123 self.initialize_from_array(array);
124 }
125
126 Self::push_capped(&mut self.uid_buffer, array.unique_id as f64, self.max_points);
128
129 for (i, name) in self.attributes.iter().enumerate() {
131 let value = array.attributes.get(name)
132 .and_then(|attr| attr.value.as_f64())
133 .unwrap_or(0.0);
134 Self::push_capped(&mut self.buffers[i], value, self.max_points);
135 }
136
137 ProcessResult::sink(vec![])
138 }
139
140 fn plugin_type(&self) -> &str {
141 "NDPluginAttrPlot"
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148 use ad_core::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
149 use ad_core::ndarray::{NDDataType, NDDimension};
150
151 fn make_array_with_attrs(uid: i32, attrs: &[(&str, f64)]) -> NDArray {
152 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
153 arr.unique_id = uid;
154 for (name, value) in attrs {
155 arr.attributes.add(NDAttribute {
156 name: name.to_string(),
157 description: String::new(),
158 source: NDAttrSource::Driver,
159 value: NDAttrValue::Float64(*value),
160 });
161 }
162 arr
163 }
164
165 #[test]
166 fn test_attribute_auto_detection() {
167 let mut proc = AttrPlotProcessor::new(100);
168 let pool = NDArrayPool::new(1_000_000);
169
170 let mut arr = make_array_with_attrs(1, &[("Temp", 25.0), ("Gain", 1.5)]);
171 arr.attributes.add(NDAttribute {
173 name: "Label".to_string(),
174 description: String::new(),
175 source: NDAttrSource::Driver,
176 value: NDAttrValue::String("test".to_string()),
177 });
178
179 proc.process_array(&arr, &pool);
180
181 assert_eq!(proc.num_attributes(), 2);
183 assert_eq!(proc.attributes()[0], "Gain");
184 assert_eq!(proc.attributes()[1], "Temp");
185 }
186
187 #[test]
188 fn test_value_tracking() {
189 let mut proc = AttrPlotProcessor::new(100);
190 let pool = NDArrayPool::new(1_000_000);
191
192 for i in 0..5 {
193 let arr = make_array_with_attrs(i, &[("Value", i as f64 * 10.0)]);
194 proc.process_array(&arr, &pool);
195 }
196
197 let idx = proc.find_attribute("Value").unwrap();
198 let buf = proc.buffer(idx).unwrap();
199 assert_eq!(buf.len(), 5);
200 assert!((buf[0] - 0.0).abs() < 1e-10);
201 assert!((buf[4] - 40.0).abs() < 1e-10);
202 }
203
204 #[test]
205 fn test_uid_buffer() {
206 let mut proc = AttrPlotProcessor::new(100);
207 let pool = NDArrayPool::new(1_000_000);
208
209 for i in 1..=3 {
210 let arr = make_array_with_attrs(i, &[("X", 1.0)]);
211 proc.process_array(&arr, &pool);
212 }
213
214 let uid_buf = proc.uid_buffer();
215 assert_eq!(uid_buf.len(), 3);
216 assert!((uid_buf[0] - 1.0).abs() < 1e-10);
217 assert!((uid_buf[1] - 2.0).abs() < 1e-10);
218 assert!((uid_buf[2] - 3.0).abs() < 1e-10);
219 }
220
221 #[test]
222 fn test_circular_buffer_max_points() {
223 let mut proc = AttrPlotProcessor::new(3);
224 let pool = NDArrayPool::new(1_000_000);
225
226 for i in 0..5 {
227 let arr = make_array_with_attrs(i, &[("Val", i as f64)]);
228 proc.process_array(&arr, &pool);
229 }
230
231 let idx = proc.find_attribute("Val").unwrap();
232 let buf = proc.buffer(idx).unwrap();
233 assert_eq!(buf.len(), 3);
235 assert!((buf[0] - 2.0).abs() < 1e-10);
236 assert!((buf[1] - 3.0).abs() < 1e-10);
237 assert!((buf[2] - 4.0).abs() < 1e-10);
238
239 assert_eq!(proc.uid_buffer().len(), 3);
241 }
242
243 #[test]
244 fn test_uid_decrease_resets_buffers() {
245 let mut proc = AttrPlotProcessor::new(100);
246 let pool = NDArrayPool::new(1_000_000);
247
248 for i in 1..=5 {
250 let arr = make_array_with_attrs(i, &[("X", i as f64)]);
251 proc.process_array(&arr, &pool);
252 }
253
254 let idx = proc.find_attribute("X").unwrap();
255 assert_eq!(proc.buffer(idx).unwrap().len(), 5);
256
257 let arr = make_array_with_attrs(1, &[("X", 100.0)]);
259 proc.process_array(&arr, &pool);
260
261 let buf = proc.buffer(idx).unwrap();
263 assert_eq!(buf.len(), 1);
264 assert!((buf[0] - 100.0).abs() < 1e-10);
265 }
266
267 #[test]
268 fn test_missing_attribute_uses_zero() {
269 let mut proc = AttrPlotProcessor::new(100);
270 let pool = NDArrayPool::new(1_000_000);
271
272 let arr1 = make_array_with_attrs(1, &[("Temp", 25.0)]);
274 proc.process_array(&arr1, &pool);
275
276 let arr2 = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
278 let mut arr2 = arr2;
279 arr2.unique_id = 2;
280 proc.process_array(&arr2, &pool);
281
282 let idx = proc.find_attribute("Temp").unwrap();
283 let buf = proc.buffer(idx).unwrap();
284 assert_eq!(buf.len(), 2);
285 assert!((buf[0] - 25.0).abs() < 1e-10);
286 assert!((buf[1] - 0.0).abs() < 1e-10);
287 }
288
289 #[test]
290 fn test_manual_reset() {
291 let mut proc = AttrPlotProcessor::new(100);
292 let pool = NDArrayPool::new(1_000_000);
293
294 let arr = make_array_with_attrs(1, &[("A", 1.0), ("B", 2.0)]);
295 proc.process_array(&arr, &pool);
296 assert_eq!(proc.num_attributes(), 2);
297
298 proc.reset();
299 assert_eq!(proc.num_attributes(), 0);
300 assert!(proc.uid_buffer().is_empty());
301
302 let arr2 = make_array_with_attrs(1, &[("C", 3.0)]);
304 proc.process_array(&arr2, &pool);
305 assert_eq!(proc.num_attributes(), 1);
306 assert_eq!(proc.attributes()[0], "C");
307 }
308
309 #[test]
310 fn test_unlimited_buffer() {
311 let mut proc = AttrPlotProcessor::new(0);
312 let pool = NDArrayPool::new(1_000_000);
313
314 for i in 0..100 {
315 let arr = make_array_with_attrs(i, &[("X", i as f64)]);
316 proc.process_array(&arr, &pool);
317 }
318
319 let idx = proc.find_attribute("X").unwrap();
320 assert_eq!(proc.buffer(idx).unwrap().len(), 100);
321 }
322
323 #[test]
324 fn test_multiple_attributes_sorted() {
325 let mut proc = AttrPlotProcessor::new(100);
326 let pool = NDArrayPool::new(1_000_000);
327
328 let arr = make_array_with_attrs(1, &[("Zebra", 1.0), ("Alpha", 2.0), ("Mid", 3.0)]);
329 proc.process_array(&arr, &pool);
330
331 assert_eq!(proc.attributes(), &["Alpha", "Mid", "Zebra"]);
332 }
333
334 #[test]
335 fn test_find_attribute() {
336 let mut proc = AttrPlotProcessor::new(100);
337 let pool = NDArrayPool::new(1_000_000);
338
339 let arr = make_array_with_attrs(1, &[("X", 1.0), ("Y", 2.0)]);
340 proc.process_array(&arr, &pool);
341
342 assert_eq!(proc.find_attribute("X"), Some(0));
343 assert_eq!(proc.find_attribute("Y"), Some(1));
344 assert_eq!(proc.find_attribute("Z"), None);
345 }
346
347 #[test]
348 fn test_plugin_type() {
349 let proc = AttrPlotProcessor::new(100);
350 assert_eq!(proc.plugin_type(), "NDPluginAttrPlot");
351 }
352}