rusty_esp_image_core/
pool.rs1use rusty_esp_core::error::{Error, Result};
10use rusty_esp_core::frame::{Frame, Geometry};
11use rusty_esp_core::time::Micros;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub struct SlotId(u8);
16
17impl SlotId {
18 #[must_use]
20 pub fn index(self) -> usize {
21 usize::from(self.0)
22 }
23}
24
25#[derive(Debug)]
27pub struct FramePool<'m, const N: usize> {
28 buf: &'m mut [u8],
29 slot_len: usize,
30 in_use: [bool; N],
31 used: [usize; N],
32}
33
34impl<'m, const N: usize> FramePool<'m, N> {
35 pub fn new(buf: &'m mut [u8]) -> Result<Self> {
38 if N == 0 || N > 255 {
39 return Err(Error::Unsupported);
40 }
41 let slot_len = buf.len() / N;
42 if slot_len == 0 {
43 return Err(Error::BufferTooSmall { needed: N });
44 }
45 Ok(FramePool {
46 buf,
47 slot_len,
48 in_use: [false; N],
49 used: [0; N],
50 })
51 }
52
53 #[must_use]
55 pub fn slot_len(&self) -> usize {
56 self.slot_len
57 }
58
59 #[must_use]
61 pub fn free_slots(&self) -> usize {
62 self.in_use.iter().filter(|b| !**b).count()
63 }
64
65 pub fn acquire(&mut self) -> Option<SlotId> {
68 let i = self.in_use.iter().position(|b| !*b)?;
69 self.in_use[i] = true;
70 self.used[i] = 0;
71 Some(SlotId(i as u8))
72 }
73
74 pub fn release(&mut self, id: SlotId) -> Result<()> {
76 let i = self.check(id)?;
77 self.in_use[i] = false;
78 self.used[i] = 0;
79 Ok(())
80 }
81
82 pub fn slot_mut(&mut self, id: SlotId) -> Result<&mut [u8]> {
84 let i = self.check(id)?;
85 let start = i * self.slot_len;
86 Ok(&mut self.buf[start..start + self.slot_len])
87 }
88
89 pub fn commit(&mut self, id: SlotId, used: usize) -> Result<()> {
92 let i = self.check(id)?;
93 if used > self.slot_len {
94 return Err(Error::BufferTooSmall { needed: used });
95 }
96 self.used[i] = used;
97 Ok(())
98 }
99
100 pub fn slot(&self, id: SlotId) -> Result<&[u8]> {
102 let i = self.check(id)?;
103 let start = i * self.slot_len;
104 Ok(&self.buf[start..start + self.used[i]])
105 }
106
107 pub fn frame(
109 &self,
110 id: SlotId,
111 geometry: Geometry,
112 timestamp: Micros,
113 sequence: u32,
114 ) -> Result<Frame<'_>> {
115 Frame::packed(geometry, timestamp, sequence, self.slot(id)?)
116 }
117
118 fn check(&self, id: SlotId) -> Result<usize> {
119 let i = id.index();
120 if i >= N || !self.in_use[i] {
121 return Err(Error::InvalidFormat);
122 }
123 Ok(i)
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use rusty_esp_core::frame::PixelFormat;
131
132 #[test]
133 fn acquire_fill_commit_frame_release() {
134 let mut mem = [0u8; 64];
135 let mut pool: FramePool<'_, 2> = FramePool::new(&mut mem).unwrap();
136 assert_eq!(pool.slot_len(), 32);
137 assert_eq!(pool.free_slots(), 2);
138 let a = pool.acquire().unwrap();
139 let b = pool.acquire().unwrap();
140 assert!(pool.acquire().is_none(), "no blocking, no third slot");
141
142 let s = pool.slot_mut(a).unwrap();
144 s[..4].copy_from_slice(&[0xFF, 0xD8, 0xFF, 0xD9]);
145 pool.commit(a, 4).unwrap();
146 let g = Geometry::new(320, 240, PixelFormat::Jpeg).unwrap();
147 let f = pool.frame(a, g, Micros::from_millis(3), 7).unwrap();
148 assert_eq!(f.coded().map(<[u8]>::len), Some(4));
149 assert_eq!(f.sequence, 7);
150
151 let g8 = Geometry::new(8, 8, PixelFormat::Gray8).unwrap();
153 pool.commit(b, 10).unwrap();
154 assert!(matches!(
155 pool.frame(b, g8, Micros::ZERO, 0),
156 Err(Error::BufferTooSmall { needed: 64 })
157 ));
158
159 pool.release(a).unwrap();
160 assert_eq!(pool.free_slots(), 1);
161 assert_eq!(pool.release(a), Err(Error::InvalidFormat), "double release");
162 assert_eq!(
163 pool.commit(b, 33),
164 Err(Error::BufferTooSmall { needed: 33 })
165 );
166 }
167
168 #[test]
169 fn construction_rules() {
170 let mut tiny = [0u8; 1];
171 assert!(matches!(
172 FramePool::<'_, 2>::new(&mut tiny),
173 Err(Error::BufferTooSmall { needed: 2 })
174 ));
175 let mut ok = [0u8; 3];
176 let pool: FramePool<'_, 2> = FramePool::new(&mut ok).unwrap();
177 assert_eq!(pool.slot_len(), 1);
178 }
179}