ad_plugins_rs/
std_arrays.rs1use std::sync::Arc;
9
10use ad_core_rs::ndarray::NDArray;
11use ad_core_rs::ndarray_pool::NDArrayPool;
12use ad_core_rs::plugin::runtime::{NDPluginProcess, PluginRuntimeHandle, ProcessResult};
13use ad_core_rs::plugin::wiring::WiringRegistry;
14use parking_lot::Mutex;
15
16pub struct StdArraysProcessor {
18 latest_data: Arc<Mutex<Option<Arc<NDArray>>>>,
19}
20
21impl StdArraysProcessor {
22 pub fn new() -> Self {
23 Self {
24 latest_data: Arc::new(Mutex::new(None)),
25 }
26 }
27
28 pub fn data_handle(&self) -> Arc<Mutex<Option<Arc<NDArray>>>> {
30 self.latest_data.clone()
31 }
32}
33
34impl Default for StdArraysProcessor {
35 fn default() -> Self {
36 Self::new()
37 }
38}
39
40impl NDPluginProcess for StdArraysProcessor {
41 fn process_array(&self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
42 let out = Arc::new(array.clone());
43 *self.latest_data.lock() = Some(out.clone());
44 ProcessResult::arrays(vec![out])
45 }
46
47 fn plugin_type(&self) -> &str {
48 "NDPluginStdArrays"
49 }
50
51 fn does_array_callbacks(&self) -> bool {
54 false
55 }
56
57 fn array_data_handle(&self) -> Option<Arc<Mutex<Option<Arc<NDArray>>>>> {
58 Some(self.latest_data.clone())
59 }
60}
61
62pub fn create_std_arrays_runtime(
64 port_name: &str,
65 pool: Arc<NDArrayPool>,
66 ndarray_port: &str,
67 wiring: Arc<WiringRegistry>,
68) -> (
69 PluginRuntimeHandle,
70 Arc<Mutex<Option<Arc<NDArray>>>>,
71 std::thread::JoinHandle<()>,
72) {
73 let processor = StdArraysProcessor::new();
74 let data_handle = processor.data_handle();
75
76 let (handle, data_jh) = ad_core_rs::plugin::runtime::create_plugin_runtime(
77 port_name,
78 processor,
79 pool,
80 1, ndarray_port,
82 wiring,
83 );
84
85 (handle, data_handle, data_jh)
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91 use ad_core_rs::ndarray::{NDDataType, NDDimension};
92
93 fn make_array(id: i32) -> Arc<NDArray> {
94 let mut arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
95 arr.unique_id = id;
96 Arc::new(arr)
97 }
98
99 fn params_applied(handle: &PluginRuntimeHandle) {
102 assert!(
103 handle.wait_params_applied(std::time::Duration::from_secs(10)),
104 "data thread did not apply queued param changes"
105 );
106 }
107
108 fn wait_until(what: &str, mut cond: impl FnMut() -> bool) {
109 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10);
110 while !cond() {
111 assert!(
112 std::time::Instant::now() < deadline,
113 "timed out waiting for {what}"
114 );
115 std::thread::sleep(std::time::Duration::from_millis(2));
116 }
117 }
118
119 #[test]
120 fn test_processor_stores_and_passes_through() {
121 let proc = StdArraysProcessor::new();
122 let pool = NDArrayPool::new(1_000_000);
123
124 let arr = NDArray::new(vec![NDDimension::new(4)], NDDataType::UInt8);
125 let result = proc.process_array(&arr, &pool);
126 assert_eq!(result.output_arrays.len(), 1);
127
128 let latest = proc.data_handle().lock().clone();
129 assert!(latest.is_some());
130 }
131
132 #[test]
133 fn test_std_arrays_runtime() {
134 let pool = Arc::new(NDArrayPool::new(1_000_000));
135 let wiring = Arc::new(WiringRegistry::new());
136 let (handle, data, _jh) = create_std_arrays_runtime("IMAGE1", pool, "", wiring);
137
138 handle
140 .port_runtime()
141 .port_handle()
142 .write_int32_blocking(handle.plugin_params.enable_callbacks, 0, 1)
143 .unwrap();
144 params_applied(&handle);
145
146 let rt = tokio::runtime::Builder::new_current_thread()
147 .enable_all()
148 .build()
149 .unwrap();
150 rt.block_on(handle.array_sender().publish(make_array(42)));
151 wait_until("StdArrays to store the published array", || {
152 data.lock().as_ref().is_some_and(|a| a.unique_id == 42)
153 });
154 }
155
156 #[test]
157 fn test_std_arrays_initial_array_callbacks_off() {
158 let pool = Arc::new(NDArrayPool::new(1_000_000));
161 let wiring = Arc::new(WiringRegistry::new());
162 let (handle, _data, _jh) = create_std_arrays_runtime("IMAGE1", pool, "", wiring);
163
164 let val = handle
165 .port_runtime()
166 .port_handle()
167 .read_int32_blocking(handle.ndarray_params.array_callbacks, 0)
168 .unwrap();
169 assert_eq!(val, 0, "StdArrays initial NDArrayCallbacks must be 0");
170 }
171
172 #[test]
173 fn test_terminal_plugins_do_not_do_array_callbacks() {
174 use crate::attribute::AttributeProcessor;
179 use crate::file_hdf5::Hdf5FileProcessor;
180 use crate::file_jpeg::JpegFileProcessor;
181 use crate::file_magick::MagickFileProcessor;
182 use crate::file_netcdf::NetcdfFileProcessor;
183 use crate::file_nexus::NexusFileProcessor;
184 use crate::file_tiff::TiffFileProcessor;
185 use crate::passthrough::PassthroughProcessor;
186
187 assert!(!StdArraysProcessor::new().does_array_callbacks());
188 assert!(!AttributeProcessor::new("attr", 1).does_array_callbacks());
189 assert!(!Hdf5FileProcessor::new().does_array_callbacks());
190 assert!(!JpegFileProcessor::new(85).does_array_callbacks());
191 assert!(!TiffFileProcessor::new().does_array_callbacks());
192 assert!(!NetcdfFileProcessor::new().does_array_callbacks());
193 assert!(!NexusFileProcessor::new().does_array_callbacks());
194 assert!(!MagickFileProcessor::new().does_array_callbacks());
195
196 assert!(PassthroughProcessor::new("NDPluginProcess").does_array_callbacks());
198 }
199
200 #[test]
201 fn test_std_arrays_serves_waveform_with_callbacks_off() {
202 use asyn_rs::param::ParamValue;
208
209 let pool = Arc::new(NDArrayPool::new(1_000_000));
210 let wiring = Arc::new(WiringRegistry::new());
211 let (handle, _data, _jh) = create_std_arrays_runtime("IMAGE1", pool, "", wiring);
212
213 let port = handle.port_runtime().port_handle();
214 port.write_int32_blocking(handle.plugin_params.enable_callbacks, 0, 1)
215 .unwrap();
216 port.write_int32_blocking(handle.ndarray_params.array_callbacks, 0, 0)
219 .unwrap();
220 params_applied(&handle);
221
222 let mut rx = port.interrupts().subscribe_async();
223
224 let rt = tokio::runtime::Builder::new_current_thread()
225 .enable_all()
226 .build()
227 .unwrap();
228 rt.block_on(handle.array_sender().publish(make_array(7)));
229 wait_until("StdArrays to process the frame", || {
232 port.read_int32_blocking(handle.ndarray_params.array_counter, 0)
233 .is_ok_and(|v| v == 1)
234 });
235
236 let mut served = false;
241 loop {
242 match rx.try_recv() {
243 Ok(v) => {
244 if matches!(v.value, ParamValue::Int8Array(_)) {
245 served = true;
246 }
247 }
248 Err(tokio::sync::broadcast::error::TryRecvError::Lagged(_)) => continue,
249 Err(_) => break,
250 }
251 }
252 assert!(
253 served,
254 "StdArrays must serve STD_ARRAY_DATA with NDArrayCallbacks=0"
255 );
256 }
257
258 #[test]
259 fn test_std_arrays_throttled_frame_does_not_advance_array_counter() {
260 let pool = Arc::new(NDArrayPool::new(1_000_000));
266 let wiring = Arc::new(WiringRegistry::new());
267 let (handle, _data, _jh) = create_std_arrays_runtime("IMAGE1", pool, "", wiring);
268 let port = handle.port_runtime().port_handle();
269 port.write_int32_blocking(handle.plugin_params.enable_callbacks, 0, 1)
270 .unwrap();
271 port.write_float64_blocking(handle.plugin_params.max_byte_rate, 0, 4.0)
275 .unwrap();
276 params_applied(&handle);
277
278 let rt = tokio::runtime::Builder::new_current_thread()
279 .enable_all()
280 .build()
281 .unwrap();
282
283 rt.block_on(handle.array_sender().publish(make_array(1)));
284 wait_until("first frame to be served and counted", || {
285 port.read_int32_blocking(handle.ndarray_params.array_counter, 0)
286 .is_ok_and(|v| v == 1)
287 });
288 let counter_after_first = port
289 .read_int32_blocking(handle.ndarray_params.array_counter, 0)
290 .unwrap();
291
292 rt.block_on(handle.array_sender().publish(make_array(2)));
293 wait_until("throttled frame to advance DroppedArrays", || {
296 port.read_int32_blocking(handle.plugin_params.dropped_output_arrays, 0)
297 .is_ok_and(|v| v == 1)
298 });
299 let counter_after_second = port
300 .read_int32_blocking(handle.ndarray_params.array_counter, 0)
301 .unwrap();
302 let dropped = port
303 .read_int32_blocking(handle.plugin_params.dropped_output_arrays, 0)
304 .unwrap();
305
306 assert_eq!(counter_after_first, 1, "first frame is served and counted");
307 assert_eq!(
308 counter_after_second, 1,
309 "throttled frame must NOT advance ArrayCounter"
310 );
311 assert_eq!(dropped, 1, "throttled frame advances DroppedArrays");
312 }
313}