devela/data/layout/buffer/ring/define.rs
1// devela/src/data/layout/buffer/ring/define.rs
2
3buffer_ring!(
4 #[doc = crate::_tags!(data_structure)]
5 /// A fixed-capacity ring buffer using `u8` indices.
6 #[doc = crate::_doc_meta!{
7 location("data/layout/buffer", struct BufferRingU8),
8 test_size_of(BufferRingU8<i8, [i8; 8]> = 10|80; niche !Option),
9 }]
10 /// This is the canonical small ring buffer type for queues and short
11 /// circular buffers with capacities up to `255`.
12 ///
13 /// It is generated with [`buffer_ring!`] and implements the static
14 /// `array` and `option` storage backends.
15 ///
16 /// Use the `array` backend for fully initialized storage such as `[u8; CAP]`.
17 /// Use the `option` backend for moving arbitrary values through
18 /// `[Option<T>; CAP]` without replacement values.
19 ///
20 /// See [`BufferRingStaticExample`][crate::BufferRingStaticExample]
21 /// for the full generated method surface.
22 pub struct BufferRingU8: (u8);
23 array, option
24);
25
26#[doc = crate::_tags!(construction data_structure)]
27/// Defines a ring buffer type over contiguous storage backends.
28#[doc = crate::_doc_meta!{
29 location("data/layout", macro buffer_ring),
30}]
31/// The generated type represents a fixed-capacity
32/// circular logical range over contiguous storage.
33///
34/// A ring tracks:
35/// - `head`: the physical start of the logical range.
36/// - `len`: the number of live elements.
37///
38/// The tail is not stored. It is derived as `(head + len) % capacity`.
39///
40/// ## Implemented backends
41///
42/// Currently implemented:
43/// - **static `array`**
44/// Fully initialized array storage (`[T; CAP]`).
45/// All slots always contain a valid `T`;
46/// `len` controls which slots are logically visible.
47/// - **static `option`**
48/// Array of options (`[Option<T>; CAP]`).
49/// Occupied logical slots are `Some`; unused physical slots are `None`.
50///
51/// Reserved for later:
52/// - **static `uninit`**
53/// - **view backends**
54/// - **alloc backends**
55///
56/// ## Index type requirements
57///
58/// The index type must:
59/// - Be non-negative.
60/// - Represent zero.
61/// - Form a contiguous integer range.
62/// - Be able to represent the full capacity.
63///
64/// Primitive unsigned integers and supported niche wrappers are accepted
65/// through [`MaybeNiche`][crate::MaybeNiche].
66///
67/// ## Storage backends
68///
69/// Backends are opt-in and selected after the struct declaration.
70///
71/// - **`array`**
72/// Fully initialized array storage (`[T; CAP]`).
73///
74/// This backend separates *initialization* from *logical membership*:
75/// every physical slot stores a valid `T`, while `len` determines which
76/// elements are visible through the ring API.
77///
78/// Operations that move values out of the array require either:
79/// - copying (`*_copy`),
80/// - an explicit replacement (`*_with`),
81/// - or a convenience replacement (`*_default`, `*_init`).
82///
83/// - **`option`**
84/// Array of options (`[Option<T>; CAP]`).
85///
86/// This backend stores occupancy explicitly: occupied logical slots
87/// are `Some(T)`, and unused physical slots are `None`.
88///
89/// It supports moving arbitrary `T` values in and out
90/// without requiring replacement values.
91///
92/// ## Examples
93/// ```
94/// use devela::buffer_ring;
95///
96/// buffer_ring!(
97/// /// Static ring buffer.
98/// pub struct RingU8: (u8);
99/// array, option
100/// );
101///
102/// let mut array_ring = RingU8::<i32, [i32; 4]>::new_init();
103/// array_ring.push_back(10).unwrap();
104/// array_ring.push_back(20).unwrap();
105///
106/// assert_eq!(array_ring.pop_front_copy(), Some(10));
107/// assert_eq!(array_ring.peek_front(), Some(&20));
108///
109/// let mut option_ring = RingU8::<i32, [Option<i32>; 4]>::new_empty();
110/// option_ring.push_back(10).unwrap();
111/// option_ring.push_back(20).unwrap();
112///
113/// assert_eq!(option_ring.pop_front(), Some(10));
114/// assert_eq!(option_ring.peek_front(), Some(&20));
115/// ```
116///
117/// See also:
118/// [`BufferRingStaticExample`][crate::BufferRingStaticExample],
119//
120// NOTE: The index type is passed as a token group to allow complex or path-qualified types.
121#[doc(hidden)]
122#[macro_export]
123macro_rules! buffer_ring· {
124 (
125 // STATIC (option)
126 // struct definition + optional implementations
127
128 $(#[$attr:meta])*
129 $vis:vis struct $name:ident : $(static)? ($($I:tt)+);
130 $($rest:tt)*
131 ) => {
132 $(#[$attr])*
133 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
134 $vis struct $name<T, S> {
135 storage: S,
136 head: $crate::MaybeNiche<$($I)+>,
137 len: $crate::MaybeNiche<$($I)+>,
138 _m: $crate::PhantomData<T>,
139 }
140
141 $crate::buffer_ring!(%impl_common_static $name, $($I)+, $crate::niche_prim![$($I)+]);
142 $crate::buffer_ring!(%impls_static
143 $name : $($I)+, $crate::niche_prim![$($I)+] ; $($rest)*);
144 };
145 ( // implementations only
146 impl $name:ident : $(static)? ($($I:tt)+); $($rest:tt)*
147 ) => {
148 $crate::buffer_ring!(%impls_static
149 $name : $($I)+, $crate::niche_prim![$($I)+] ; $($rest)*);
150 };
151 (
152 // VIEW: TODO: (slice_mut, slice)
153 // struct definition + optional implementations
154
155 $(#[$_attr:meta])*
156 $_vis:vis struct $_name:ident : view ($($_I:tt)+);
157 $($_rest:tt)*
158 ) => {
159 compile_error!("buffer_ring!: view backends are not implemented yet");
160 };
161 ( // implementations only
162 impl $_name:ident : view ($($_I:tt)+);
163 $($_rest:tt)*
164 ) => {
165 compile_error!("buffer_ring!: view backends are not implemented yet");
166 };
167
168 (
169 // ALLOC TODO: (vec)
170 // struct definition + optional implementations
171
172 $(#[$_attr:meta])*
173 $_vis:vis struct $_name:ident : alloc ($($_I:tt)+);
174 $($_rest:tt)*
175 ) => {
176 compile_error!("buffer_ring!: alloc backends are not implemented yet");
177 };
178 ( // implementations only
179 impl $_name:ident : alloc ($($_I:tt)+);
180 $($_rest:tt)*
181 ) => {
182 compile_error!("buffer_ring!: alloc backends are not implemented yet");
183 };
184
185 // --------------------------------------------------------------------------------------------
186 // static dispatch
187 (
188
189 /* internals */
190 %impls_static $name:ident : $I:ty, $P:ty ;) => {}; // no impls
191 (%impls_static $name:ident : $I:ty, $P:ty ; $(#[$i:meta])* $impl:ident) => { // last impl
192 $crate::buffer_ring!(%impl1_static $(#[$i])* $name : $I, $P ; $impl);
193 };
194 // static: array
195 (%impls_static $name:ident : $I:ty, $P:ty ; $(#[$i:meta])* array , $($rest:tt)*) => {
196 $crate::__buffer_ring_impl_array!($(#[$i])* $name, $I, $P);
197 $crate::buffer_ring!(%impls_static $name : $I, $P ; $($rest)*);
198 };
199 (%impl1_static $(#[$i:meta])* $name:ident : $I:ty, $P:ty ; array) => {
200 $crate::__buffer_ring_impl_array!($(#[$i])* $name, $I, $P);
201 };
202 // static: option
203 (%impls_static $name:ident : $I:ty, $P:ty ; $(#[$i:meta])* option , $($rest:tt)*) => {
204 $crate::__buffer_ring_impl_option!($(#[$i])* $name, $I, $P);
205 $crate::buffer_ring!(%impls_static $name : $I, $P ; $($rest)*);
206 };
207 (%impl1_static $(#[$i:meta])* $name:ident : $I:ty, $P:ty ; option) => {
208 $crate::__buffer_ring_impl_option!($(#[$i])* $name, $I, $P);
209 };
210
211 // safe-guards
212 (%impls_static $_name:ident : $_I:ty, $_P:ty ; $(#[$_i:meta])* $impl:ident , $($_r:tt)*) => {
213 compile_error!(concat!("buffer_ring!: unknown static impl `", stringify!($impl), "`"));
214 };
215 (%impl1_static $(#[$_i:meta])* $_name:ident : $_I:ty, $_P:ty ; $impl:ident) => {
216 compile_error!(concat!("buffer_ring!: unknown static impl `", stringify!($impl), "`"));
217 };
218
219 /* blocks for common private associated items */
220
221 (%impl_common_static $name:ident, $I:ty, $P:ty) => {
222 /// Common methods.
223 impl<T, S> $name<T, S> {
224 $crate::buffer_ring!(%common_tracked $name, $I, $P);
225 }
226 };
227 // common items for tracked rings
228 (%common_tracked $name:ident, $I:ty, $P:ty) => {
229 $crate::buffer_ring!(%guard_index_repr $I);
230
231 /// Constructs a ring from raw components, assuming all invariants hold.
232 const fn _new(storage: S,
233 head: $crate::MaybeNiche<$I>, len: $crate::MaybeNiche<$I>) -> Self {
234 Self { storage, head, len, _m: $crate::PhantomData }
235 }
236
237 /* idx */
238
239 /// Returns the zero value as a MaybeNiche wrapped index type.
240 const fn _idx_zero() -> $crate::MaybeNiche<$I> {
241 // SAFETY-INVARIANT: checked above; buffer indices must represent zero.
242 $crate::unwrap![some_guaranteed_or_ub $crate::MaybeNiche::<$I>::ZERO]
243 }
244
245 /// `a == b`
246 const fn _idx_eq(a: $I, b: $I) -> bool {
247 let (a, b) = ($crate::MaybeNiche(a).prim(), $crate::MaybeNiche(b).prim()); a == b
248 }
249 /// `a <= b`
250 const fn _idx_le(a: $I, b: $I) -> bool {
251 let (a, b) = ($crate::MaybeNiche(a).prim(), $crate::MaybeNiche(b).prim()); a <= b
252 }
253 /// `a >= b`
254 const fn _idx_ge(a: $I, b: $I) -> bool {
255 let (a, b) = ($crate::MaybeNiche(a).prim(), $crate::MaybeNiche(b).prim()); a >= b
256 }
257
258 /* prim */
259
260 /// Returns the given index-typed value as a primitive.
261 const fn _idx_to_prim(from: $I) -> $P { $crate::MaybeNiche(from).prim() }
262
263 /// Returns the given primitive value as an index type.
264 const fn _prim_to_idx(from: $P) -> Result<$I, $crate::InvalidValue> {
265 $crate::unwrap![ok_map? $crate::MaybeNiche::<$I>::try_from_prim(from), |v| v.repr()]
266 }
267 /// Returns the given primitive value as an index type,
268 /// converting invalid inputs to the closest valid number.
269 const fn _prim_to_idx_lossy(from: $P) -> $I {
270 $crate::MaybeNiche::<$I>::from_prim_lossy(from).repr()
271 }
272
273 /* usize */
274
275 /// The maximum representable value of the index type, as a usize.
276 const _IDX_MAX_USIZE: usize = $crate::MaybeNiche(<$I>::MAX).to_usize_saturating();
277
278 /// Returns the current logical length as a `usize`, saturating if necessary.
279 const fn _len_usize(&self) -> usize { self.len.to_usize_saturating()
280 }
281 /// Returns the current physical head as a `usize`, saturating if necessary.
282 const fn _head_usize(&self) -> usize { self.head.to_usize_saturating() }
283
284 /// Returns the given usize value as a MaybeNiche wrapped index type.
285 const fn _usize_to_midx(from: usize) -> $crate::MaybeNiche<$I> {
286 $crate::unwrap![ok $crate::MaybeNiche::<$I>::try_from_usize(from)]
287 }
288 /// Returns the given usize value as a MaybeNiche wrapped saturated index type.
289 const fn _usize_to_midx_sat(from: usize) -> $crate::MaybeNiche<$I> {
290 $crate::MaybeNiche::<$I>::from_usize_saturating(from)
291 }
292 /// Returns the given usize value as an index type.
293 const fn _usize_to_idx(from: usize) -> $I {
294 Self::_usize_to_midx(from).repr()
295 }
296 /// Returns the given index value as a usize.
297 const fn _idx_to_usize(from: $I) -> usize {
298 $crate::unwrap![ok $crate::MaybeNiche(from).try_to_usize()]
299 }
300
301 /* state */
302
303 /// Sets the physical head without checking invariants.
304 const fn _set_head(&mut self, head: $I) { self.head = $crate::MaybeNiche(head); }
305
306 /// Sets the logical length without checking invariants.
307 const fn _set_len(&mut self, len: $I) { self.len = $crate::MaybeNiche(len); }
308
309 /// Returns the next logical length.
310 ///
311 /// Caller must guarantee `len < capacity`.
312 const fn _len_inc(&self) -> $crate::MaybeNiche<$I> {
313 $crate::unwrap![ok $crate::MaybeNiche::<$I>::try_from_prim(self.len.prim() + 1)]
314 }
315 /// Returns the previous logical length.
316 ///
317 /// Caller must guarantee `len > 0`.
318 const fn _len_dec(&self) -> $crate::MaybeNiche<$I> {
319 $crate::unwrap![ok $crate::MaybeNiche::<$I>::try_from_prim(self.len.prim() - 1)]
320 }
321
322 /* public methods */
323
324 /// Returns the number of elements currently stored in the ring.
325 pub const fn len(&self) -> $I { self.len.repr() }
326
327 /// Returns the number of elements currently stored in the ring.
328 pub const fn len_prim(&self) -> $P { self.len.prim() }
329
330 /// Returns `true` if the ring contains no elements.
331 pub const fn is_empty(&self) -> bool { self.len.prim() == 0 }
332 };
333
334 // common items for static rings
335 (%common_static $name:ident, $I:ty, $P:ty) => {
336 const _CHECK_INVARIANTS: () = {
337 assert!(!$crate::MaybeNiche::<$I>::HAS_NEGATIVE,
338 "buffer_ring! index type must be non-negative");
339 assert!($crate::MaybeNiche::<$I>::ZERO.is_some(),
340 "buffer_ring! index type cannot represent zero");
341 assert!($crate::MaybeNiche::<$I>::IS_CONTIGUOUS,
342 "buffer_ring! index type must be contiguous");
343 assert!($crate::MaybeNiche::<$I>::try_from_usize(CAP).is_ok(),
344 "buffer_ring! capacity does not fit in index type");
345 };
346
347 /// The fixed capacity of the ring as the index type.
348 pub const CAP: $I = {
349 let _ = Self::_CHECK_INVARIANTS; // ensure proper eval order
350 Self::_usize_to_midx(CAP).repr()
351 };
352 /// The fixed capacity of the ring as the primitive type.
353 pub const CAP_PRIM: $P = Self::_idx_to_prim(Self::CAP);
354
355 /// Returns the fixed capacity of the ring.
356 pub const fn capacity(&self) -> $I { Self::CAP }
357 /// Returns the fixed capacity of the ring.
358 pub const fn capacity_prim(&self) -> $P { Self::CAP_PRIM }
359
360 /// Returns the number of additional elements that fit within the current capacity.
361 pub const fn remaining_capacity(&self) -> $I {
362 $crate::unwrap![ok_guaranteed_or_ub Self::_prim_to_idx(self.remaining_capacity_prim())]
363 }
364 /// Returns the number of additional elements that fit within the current capacity.
365 pub const fn remaining_capacity_prim(&self) -> $P { self.capacity_prim() - self.len_prim() }
366
367 /// Returns `true` if the ring has reached its capacity.
368 pub const fn is_full(&self) -> bool { Self::_idx_eq(self.len(), self.capacity()) }
369
370 /* internal methods */
371
372 /// Wraps a physical index into the fixed ring capacity.
373 ///
374 /// Caller should only pass values smaller than `2 * CAP`.
375 const fn _wrap_usize(index: usize) -> usize {
376 if CAP == 0 { 0 } else if index >= CAP { index - CAP } else { index }
377 }
378
379 /// Returns the physical index for a logical index.
380 ///
381 /// Caller must guarantee `logical < len`.
382 const fn _physical_usize(&self, logical: usize) -> usize {
383 Self::_wrap_usize(self._head_usize() + logical)
384 }
385 /// Returns the physical insertion index at the back.
386 ///
387 /// This is the derived tail.
388 const fn _tail_usize(&self) -> usize {
389 Self::_wrap_usize(self._head_usize() + self._len_usize())
390 }
391 /// Returns the physical index of the current back element.
392 ///
393 /// Caller must guarantee `len > 0`.
394 const fn _back_usize(&self) -> usize {
395 Self::_wrap_usize(self._head_usize() + self._len_usize() - 1)
396 }
397 /// Returns the physical index before the current head.
398 ///
399 /// Caller must guarantee `CAP > 0`.
400 const fn _prev_head_usize(&self) -> usize {
401 let head = self._head_usize();
402 if head == 0 { CAP - 1 } else { head - 1 }
403 }
404 };
405 // only allow implementations over unsigned integers of size <= pointer-width
406 (%guard_index_repr $I:ty) => {
407 const __GUARD_INDEX_REPR: () = {
408 const fn __index_repr<I: $crate::ReprIndex>() {}
409 __index_repr::<$I>();
410 };
411 };
412}
413#[doc(inline)]
414pub use buffer_ring· as buffer_ring;