use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
use visual_cortex::{
Brightness, Detector, DetectorError, DetectorOutput, EventKind, FakeSource, Frame, FrameView,
Preprocessor, Rate, Region, Session,
};
struct Scripted {
outputs: Vec<u8>,
calls: Arc<AtomicU32>,
}
impl Preprocessor for Scripted {
fn process(&mut self, view: &FrameView<'_>) -> Result<Arc<Frame>, DetectorError> {
let i = self.calls.fetch_add(1, Ordering::SeqCst) as usize;
let lum = *self.outputs.get(i).or(self.outputs.last()).unwrap_or(&0);
Ok(Arc::new(Frame::solid(
view.width(),
view.height(),
[lum, lum, lum, 255],
)))
}
}
async fn next_event(stream: &mut visual_cortex::EventStream) -> visual_cortex::Event {
tokio::time::timeout(Duration::from_secs(10), stream.next())
.await
.expect("event")
.expect("stream open")
}
#[tokio::test(start_paused = true)]
async fn detector_sees_preprocessed_pixels() {
let session = Session::builder()
.source(FakeSource::new([Frame::solid(8, 8, [0, 0, 0, 255])]))
.build()
.await
.unwrap();
let mut stream = session
.watch("bright")
.region(Region::Full)
.rate(Rate::hz(10.0))
.preprocess(Scripted {
outputs: vec![250],
calls: Arc::default(),
})
.detector(Brightness)
.subscribe()
.unwrap();
let ev = next_event(&mut stream).await;
let EventKind::Changed { new, .. } = &ev.kind else {
panic!("expected Changed, got {:?}", ev.kind);
};
assert!(
new.as_number().unwrap() > 200.0,
"detector must see the preprocessor's bright output, got {new:?}"
);
}
#[tokio::test(start_paused = true)]
async fn static_preprocessor_output_holds_the_diff_gate() {
let churning: Vec<Frame> = (0..40u8)
.map(|i| Frame::solid(8, 8, [i * 6, 0, 0, 255]))
.collect();
let session = Session::builder()
.source(FakeSource::new(churning))
.build()
.await
.unwrap();
let detector_calls = Arc::new(AtomicU32::new(0));
struct Counting(Arc<AtomicU32>);
impl Detector for Counting {
fn evaluate(&mut self, _v: &FrameView<'_>) -> Result<DetectorOutput, DetectorError> {
self.0.fetch_add(1, Ordering::SeqCst);
Ok(DetectorOutput::Number(self.0.load(Ordering::SeqCst) as f64))
}
}
let mut stream = session
.watch("gated")
.region(Region::Full)
.rate(Rate::hz(20.0))
.preprocess(Scripted {
outputs: vec![128], calls: Arc::default(),
})
.detector(Counting(detector_calls.clone()))
.subscribe()
.unwrap();
let _ = next_event(&mut stream).await;
tokio::time::sleep(Duration::from_secs(2)).await;
assert_eq!(
detector_calls.load(Ordering::SeqCst),
1,
"diff gate must hold while preprocessed output is static"
);
}