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