Skip to main content

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;