chalkydri/subsystems/
mod.rs

1use std::{fmt::Debug, marker::PhantomData};
2
3use minint::NtConn;
4use tokio::sync::watch;
5
6use crate::{cameras::pipeline::Preprocessor, config};
7
8#[cfg(feature = "apriltags")]
9pub mod apriltags;
10#[cfg(feature = "capriltags")]
11pub mod capriltags;
12//mod manager;
13#[cfg(feature = "ml")]
14pub mod ml;
15#[cfg(feature = "python")]
16pub mod python;
17
18//pub use manager::SubsysManager;
19
20/// A processing subsystem
21///
22/// Subsystems implement different computer vision tasks, such as AprilTags or object detection.
23///
24/// A subsystem should be generic, not something that is only used for some specific aspect of a
25/// game.
26/// For example, note detection for the 2024 game, Crescendo, would go under the object detection
27/// subsystem, rather than a brand new subsystem.
28///
29/// Make sure to pay attention to and respect each subsystem's documentation and structure.
30pub trait Subsystem: Sized {
31    const NAME: &'static str;
32
33    type Config: Debug + Send + Sync + Clone + 'static;
34    type Preproc: Preprocessor;
35    type Output: Send + 'static;
36    type Error: Debug + Send + 'static;
37
38    /// Initialize the subsystem
39    async fn init() -> Result<Self, Self::Error>;
40
41    /// Process a frame
42    async fn process(
43        &self,
44        nt: NtConn,
45        cam_config: config::Camera,
46        rx: watch::Receiver<Option<<<Self as Subsystem>::Preproc as Preprocessor>::Frame>>,
47    ) -> Result<Self::Output, Self::Error>;
48}
49
50pub struct NoopSubsys<P: Preprocessor>(PhantomData<P>);
51impl<P: Preprocessor> NoopSubsys<P> {
52    #[inline(always)]
53    pub const fn new() -> Self {
54        Self(PhantomData)
55    }
56}
57impl<P: Preprocessor> Subsystem for NoopSubsys<P> {
58    const NAME: &'static str = "noop";
59
60    type Config = ();
61    type Preproc = P;
62    type Output = ();
63    type Error = ();
64
65    async fn init() -> Result<Self, Self::Error> {
66        Ok(Self::new())
67    }
68    async fn process(
69        &self,
70        _nt: NtConn,
71        _cam_config: config::Camera,
72        _rx: watch::Receiver<Option<<<Self as Subsystem>::Preproc as Preprocessor>::Frame>>,
73    ) -> Result<Self::Output, Self::Error> {
74        Ok(())
75    }
76}
77
78/// Run frame processing loop
79pub async fn frame_proc_loop<P: Preprocessor, F: AsyncFnMut(P::Frame) + Sync + Send + 'static>(
80    mut rx: watch::Receiver<Option<P::Frame>>,
81    mut func: F,
82) {
83    loop {
84        'inner: loop {
85            match rx.changed().await {
86                Ok(()) => match rx.borrow_and_update().clone() {
87                    Some(frame) => {
88                        futures_executor::block_on(async { func(frame).await });
89                    }
90                    None => {
91                        warn!("waiting on first frame...");
92                    }
93                },
94                Err(err) => {
95                    error!("error waiting for new frame: {err:?}");
96                    break 'inner;
97                }
98            }
99        }
100        tokio::task::yield_now().await;
101    }
102}