Skip to main content

vmb_core/
callback.rs

1//! User frame-callback wrapper.
2//!
3//! [`FrameCallback`] type-erases a user closure into a boxed trait object
4//! that accepts a [`Frame`] for any lifetime. It is delivered to the user
5//! on whatever thread the adapter fires the callback on, so the closure
6//! must be `Send + Sync + 'static`.
7//!
8//! The adapter-side trampolines (`extern "C" fn` in `vmb-ffi`; synchronous
9//! dispatch in `vmb-fake`) hold references to `FrameCallback` values via a
10//! registry keyed by [`crate::FrameCallbackId`] and invoke them via
11//! [`FrameCallback::invoke`].
12
13use crate::frame::Frame;
14
15/// User-provided frame callback, type-erased into a boxed trait object.
16///
17/// The closure is higher-rank over the frame lifetime: it accepts a
18/// `&Frame<'a>` for any `'a`, which matches how adapters conjure up a
19/// fresh lifetime on every invocation.
20pub struct FrameCallback {
21    inner: Box<dyn for<'a> Fn(&Frame<'a>) + Send + Sync + 'static>,
22}
23
24impl FrameCallback {
25    /// Wrap a closure.
26    pub fn new<F>(f: F) -> Self
27    where
28        F: for<'a> Fn(&Frame<'a>) + Send + Sync + 'static,
29    {
30        Self { inner: Box::new(f) }
31    }
32
33    /// Invoke the closure with the given frame. Called by adapters.
34    pub fn invoke(&self, frame: &Frame<'_>) {
35        (self.inner)(frame);
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::frame::PixelFormat;
43    use std::sync::atomic::{AtomicUsize, Ordering};
44    use std::sync::Arc;
45
46    #[test]
47    fn frame_callback_dispatches_to_closure() {
48        let counter = Arc::new(AtomicUsize::new(0));
49        let counter_clone = counter.clone();
50        let cb = FrameCallback::new(move |_frame| {
51            counter_clone.fetch_add(1, Ordering::SeqCst);
52        });
53
54        let data = vec![0u8; 16];
55        let frame = Frame::new(&data, 4, 4, PixelFormat::Mono8, 0, 0);
56
57        cb.invoke(&frame);
58        cb.invoke(&frame);
59        assert_eq!(counter.load(Ordering::SeqCst), 2);
60    }
61
62    #[test]
63    fn frame_callback_receives_frame_metadata() {
64        let seen = Arc::new(std::sync::Mutex::new(None));
65        let seen_clone = seen.clone();
66        let cb = FrameCallback::new(move |frame: &Frame<'_>| {
67            *seen_clone.lock().unwrap() = Some((
68                frame.width,
69                frame.height,
70                frame.pixel_format,
71                frame.frame_id,
72                frame.timestamp_ns,
73                frame.data().to_vec(),
74            ));
75        });
76
77        let data = vec![9u8, 8, 7];
78        cb.invoke(&Frame::new(&data, 3, 1, PixelFormat::Bgr8, 42, 11));
79
80        let got = seen.lock().unwrap().clone().expect("callback not called");
81        assert_eq!(got.0, 3);
82        assert_eq!(got.1, 1);
83        assert_eq!(got.2, PixelFormat::Bgr8);
84        assert_eq!(got.3, 11);
85        assert_eq!(got.4, 42);
86        assert_eq!(got.5, vec![9, 8, 7]);
87    }
88}