Skip to main content

cu29_runtime/
copperlist.rs

1//! CopperList is the main data structure used by Copper to communicate between tasks.
2//! It is a queue that can be used to store preallocated messages between tasks in memory order.
3#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use alloc::boxed::Box;
7use alloc::vec::Vec;
8
9use bincode::{Decode, Encode};
10use core::fmt;
11
12use core::fmt::Display;
13use core::iter::{Chain, Rev};
14use core::slice::{Iter as SliceIter, IterMut as SliceIterMut};
15use cu29_traits::{CopperListTuple, ErasedCuStampedData, ErasedCuStampedDataSet};
16use serde_derive::{Deserialize, Serialize};
17
18const MAX_TASKS: usize = 512;
19
20/// Not implemented yet.
21/// This mask will be used to for example filter out necessary regions of a copper list between remote systems.
22#[derive(Debug, Encode, Decode, PartialEq, Clone, Copy)]
23pub struct CopperLiskMask {
24    #[allow(dead_code)]
25    mask: [u128; MAX_TASKS / 128 + 1],
26}
27
28/// Those are the possible states along the lifetime of a CopperList.
29#[derive(Debug, Encode, Decode, Serialize, Deserialize, PartialEq, Copy, Clone)]
30pub enum CopperListState {
31    Free,
32    Initialized,
33    Processing,
34    DoneProcessing,
35    QueuedForSerialization,
36    BeingSerialized,
37}
38
39impl Display for CopperListState {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        match self {
42            CopperListState::Free => write!(f, "Free"),
43            CopperListState::Initialized => write!(f, "Initialized"),
44            CopperListState::Processing => write!(f, "Processing"),
45            CopperListState::DoneProcessing => write!(f, "DoneProcessing"),
46            CopperListState::QueuedForSerialization => write!(f, "QueuedForSerialization"),
47            CopperListState::BeingSerialized => write!(f, "BeingSerialized"),
48        }
49    }
50}
51
52/// A cycle's messages and identifier. Lifecycle state exists only in memory;
53/// every serialization contains `id` followed by `msgs`.
54#[derive(Debug, Encode, Decode, Serialize, Deserialize)]
55pub struct CopperList<P: CopperListTuple> {
56    pub id: u64,
57    // Runtime bookkeeping only; recorded lists reconstruct the serialization state.
58    #[bincode(skip, default = "deserialized_state")]
59    #[serde(skip, default = "deserialized_state")]
60    state: CopperListState,
61    pub msgs: P, // This is generated from the runtime.
62}
63
64fn deserialized_state() -> CopperListState {
65    CopperListState::BeingSerialized
66}
67
68impl<P: CopperListTuple> Default for CopperList<P> {
69    fn default() -> Self {
70        CopperList {
71            id: 0,
72            state: CopperListState::Free,
73            msgs: P::default(),
74        }
75    }
76}
77
78impl<P: CopperListTuple> CopperList<P> {
79    // This is not the usual way to create a CopperList, this is just for testing.
80    pub fn new(id: u64, msgs: P) -> Self {
81        CopperList {
82            id,
83            state: CopperListState::Initialized,
84            msgs,
85        }
86    }
87
88    pub fn change_state(&mut self, new_state: CopperListState) {
89        self.state = new_state; // TODO: probably wise here to enforce a state machine.
90    }
91
92    pub fn get_state(&self) -> CopperListState {
93        self.state
94    }
95
96    /// Restores the lifecycle state and per-cycle metadata of an initialized slot.
97    /// Existing payloads remain available for reuse by tasks.
98    #[doc(hidden)]
99    pub fn reset_for_runtime_use(&mut self, id: u64)
100    where
101        P: CuListZeroedInit,
102    {
103        self.id = id;
104        self.state = CopperListState::Initialized;
105        self.msgs.init_zeroed();
106    }
107
108    /// Initializes a pool slot directly in its allocated storage.
109    ///
110    /// # Safety
111    /// `dst` must point to aligned, writable storage for one `Self`. Its previous
112    /// contents are overwritten without being dropped.
113    pub(crate) unsafe fn init_in_place(dst: *mut Self)
114    where
115        P: CuListZeroedInit,
116    {
117        // SAFETY: The caller supplies storage for every field. No reference to
118        // the CopperList is formed before its message dataset is initialized.
119        unsafe {
120            core::ptr::addr_of_mut!((*dst).id).write(0);
121            core::ptr::addr_of_mut!((*dst).state).write(CopperListState::Free);
122            let msgs = core::ptr::addr_of_mut!((*dst).msgs);
123            let initialized = P::init_in_place(&mut *msgs.cast::<core::mem::MaybeUninit<P>>());
124            assert!(
125                core::ptr::eq(initialized, msgs),
126                "initializer returned a different slot"
127            );
128        }
129    }
130}
131
132impl<P: CopperListTuple> ErasedCuStampedDataSet for CopperList<P> {
133    fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
134        self.msgs.cumsgs()
135    }
136}
137
138/// This structure maintains the entire memory needed by Copper for one loop for the inter tasks communication within a process.
139/// P or Payload is typically a Tuple of various types of messages that are exchanged between tasks.
140/// N is the maximum number of in flight Copper List the runtime can support.
141pub struct CuListsManager<P: CopperListTuple, const N: usize> {
142    data: Box<[CopperList<P>; N]>,
143    length: usize,
144    insertion_index: usize,
145    current_cl_id: u64,
146}
147
148impl<P: CopperListTuple + fmt::Debug, const N: usize> fmt::Debug for CuListsManager<P, N> {
149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150        f.debug_struct("CuListsManager")
151            .field("data", &self.data)
152            .field("length", &self.length)
153            .field("insertion_index", &self.insertion_index)
154            // Do not include on_drop field
155            .finish()
156    }
157}
158
159pub type Iter<'a, T> = Chain<Rev<SliceIter<'a, T>>, Rev<SliceIter<'a, T>>>;
160pub type IterMut<'a, T> = Chain<Rev<SliceIterMut<'a, T>>, Rev<SliceIterMut<'a, T>>>;
161pub type AscIter<'a, T> = Chain<SliceIter<'a, T>, SliceIter<'a, T>>;
162pub type AscIterMut<'a, T> = Chain<SliceIterMut<'a, T>, SliceIterMut<'a, T>>;
163
164/// Initializes CopperList storage and resets per-cycle metadata on reuse.
165///
166/// Pool slots contain valid values before the runtime borrows them.
167///
168/// Existing implementations use `Default` for startup initialization.
169pub trait CuListZeroedInit: CopperListTuple {
170    /// Resets per-cycle metadata on an already initialized dataset.
171    fn init_zeroed(&mut self);
172
173    /// Constructs a valid dataset in its pool storage, once at startup.
174    /// Generated datasets override this to initialize messages individually.
175    ///
176    /// Returns the initialized value in `dst`. The pool checks that the returned
177    /// reference points to the supplied slot before exposing or dropping it.
178    #[doc(hidden)]
179    fn init_in_place(dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
180        dst.write(Self::default())
181    }
182}
183
184impl<P: CopperListTuple + CuListZeroedInit, const N: usize> Default for CuListsManager<P, N> {
185    fn default() -> Self {
186        Self::new()
187    }
188}
189
190impl<P: CopperListTuple, const N: usize> CuListsManager<P, N> {
191    pub fn new() -> Self
192    where
193        P: CuListZeroedInit,
194    {
195        let mut slots = Vec::<CopperList<P>>::with_capacity(N);
196        for index in 0..N {
197            // SAFETY: Capacity is N and this slot has not been initialized yet.
198            // Advancing the length afterwards also makes Vec drop all completed
199            // slots if initialization of a later slot unwinds.
200            unsafe {
201                CopperList::init_in_place(slots.as_mut_ptr().add(index));
202                slots.set_len(index + 1);
203            }
204        }
205        // SAFETY: Exactly N initialized slots were placed in the boxed slice.
206        let data = unsafe {
207            Box::from_raw(Box::into_raw(slots.into_boxed_slice()) as *mut [CopperList<P>; N])
208        };
209        CuListsManager {
210            data,
211            length: 0,
212            insertion_index: 0,
213            current_cl_id: 0,
214        }
215    }
216
217    /// Returns the current number of elements in the queue.
218    ///
219    #[inline]
220    pub fn len(&self) -> usize {
221        self.length
222    }
223
224    /// Returns `true` if the queue contains no elements.
225    ///
226    #[inline]
227    pub fn is_empty(&self) -> bool {
228        self.length == 0
229    }
230
231    /// Returns `true` if the queue is full.
232    ///
233    #[inline]
234    pub fn is_full(&self) -> bool {
235        N == self.len()
236    }
237
238    /// Clears the queue.
239    ///
240    #[inline]
241    pub fn clear(&mut self) {
242        self.insertion_index = 0;
243        self.length = 0;
244    }
245
246    #[inline]
247    pub fn create(&mut self) -> Option<&mut CopperList<P>>
248    where
249        P: CuListZeroedInit,
250    {
251        if self.is_full() {
252            return None;
253        }
254        let next_id = self.current_cl_id;
255        let result = &mut self.data[self.insertion_index];
256        self.insertion_index = (self.insertion_index + 1) % N;
257        self.length += 1;
258
259        // We assign a unique id to each CopperList to be able to track them across their lifetime.
260        result.reset_for_runtime_use(next_id);
261        self.current_cl_id += 1;
262
263        Some(result)
264    }
265
266    /// Returns the next copper-list id that will be assigned by [`create`](Self::create).
267    #[inline]
268    pub fn next_cl_id(&self) -> u64 {
269        self.current_cl_id
270    }
271
272    pub(crate) fn set_next_replay_id(&mut self, id: u64) {
273        debug_assert_eq!(self.length, 0);
274        self.current_cl_id = id;
275    }
276
277    /// Returns the most recently assigned copper-list id.
278    ///
279    /// Before the first call to [`create`](Self::create), this returns `0`.
280    #[inline]
281    pub fn last_cl_id(&self) -> u64 {
282        self.current_cl_id.saturating_sub(1)
283    }
284
285    /// Peeks at the last element in the queue.
286    #[inline]
287    pub fn peek(&self) -> Option<&CopperList<P>> {
288        if self.length == 0 {
289            return None;
290        }
291        let index = if self.insertion_index == 0 {
292            N - 1
293        } else {
294            self.insertion_index - 1
295        };
296        Some(&self.data[index])
297    }
298
299    #[inline]
300    #[allow(dead_code)]
301    fn drop_last(&mut self) {
302        if self.length == 0 {
303            return;
304        }
305        if self.insertion_index == 0 {
306            self.insertion_index = N - 1;
307        } else {
308            self.insertion_index -= 1;
309        }
310        self.length -= 1;
311    }
312
313    #[inline]
314    pub fn pop(&mut self) -> Option<&mut CopperList<P>> {
315        if self.length == 0 {
316            return None;
317        }
318        if self.insertion_index == 0 {
319            self.insertion_index = N - 1;
320        } else {
321            self.insertion_index -= 1;
322        }
323        self.length -= 1;
324        Some(&mut self.data[self.insertion_index])
325    }
326
327    /// Returns an iterator over the queue's contents.
328    ///
329    /// The iterator goes from the most recently pushed items to the oldest ones.
330    ///
331    #[inline]
332    pub fn iter(&self) -> Iter<'_, CopperList<P>> {
333        let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
334        a.iter().rev().chain(b.iter().rev())
335    }
336
337    /// Returns a mutable iterator over the queue's contents.
338    ///
339    /// The iterator goes from the most recently pushed items to the oldest ones.
340    ///
341    #[inline]
342    pub fn iter_mut(&mut self) -> IterMut<'_, CopperList<P>> {
343        let (a, b) = self.data[0..self.length].split_at_mut(self.insertion_index);
344        a.iter_mut().rev().chain(b.iter_mut().rev())
345    }
346
347    /// Returns an ascending iterator over the queue's contents.
348    ///
349    /// The iterator goes from the least recently pushed items to the newest ones.
350    ///
351    #[inline]
352    pub fn asc_iter(&self) -> AscIter<'_, CopperList<P>> {
353        let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
354        b.iter().chain(a.iter())
355    }
356
357    /// Returns a mutable ascending iterator over the queue's contents.
358    ///
359    /// The iterator goes from the least recently pushed items to the newest ones.
360    ///
361    #[inline]
362    pub fn asc_iter_mut(&mut self) -> AscIterMut<'_, CopperList<P>> {
363        let (a, b) = self.data[0..self.length].split_at_mut(self.insertion_index);
364        b.iter_mut().chain(a.iter_mut())
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use cu29_traits::{ErasedCuStampedData, ErasedCuStampedDataSet, MatchingTasks};
372    use serde::{Deserialize, Serialize, Serializer};
373
374    #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy, Serialize, Deserialize, Default)]
375    struct CuStampedDataSet(i32);
376
377    impl ErasedCuStampedDataSet for CuStampedDataSet {
378        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
379            Vec::new()
380        }
381    }
382
383    impl MatchingTasks for CuStampedDataSet {
384        fn get_all_task_ids() -> &'static [&'static str] {
385            &[]
386        }
387    }
388
389    impl CuListZeroedInit for CuStampedDataSet {
390        fn init_zeroed(&mut self) {}
391    }
392
393    #[derive(Debug, Default, Encode, Decode, Serialize, Deserialize)]
394    struct WrongSlot;
395
396    impl ErasedCuStampedDataSet for WrongSlot {
397        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
398            Vec::new()
399        }
400    }
401
402    impl MatchingTasks for WrongSlot {
403        fn get_all_task_ids() -> &'static [&'static str] {
404            &[]
405        }
406    }
407
408    impl CuListZeroedInit for WrongSlot {
409        fn init_zeroed(&mut self) {}
410
411        fn init_in_place(_dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
412            Box::leak(Box::new(Self))
413        }
414    }
415
416    #[test]
417    #[should_panic(expected = "initializer returned a different slot")]
418    fn rejects_initializer_returning_another_slot() {
419        let _ = CuListsManager::<WrongSlot, 1>::new();
420    }
421
422    #[test]
423    fn serialization_omits_runtime_state() {
424        let expected =
425            bincode::encode_to_vec((42u64, CuStampedDataSet(123)), bincode::config::standard())
426                .unwrap();
427        for state in [
428            CopperListState::Free,
429            CopperListState::Initialized,
430            CopperListState::Processing,
431            CopperListState::DoneProcessing,
432            CopperListState::QueuedForSerialization,
433            CopperListState::BeingSerialized,
434        ] {
435            let mut list = CopperList::new(42, CuStampedDataSet(123));
436            list.change_state(state);
437            let bytes = bincode::encode_to_vec(&list, bincode::config::standard()).unwrap();
438            assert_eq!(bytes, expected);
439            let old_bytes =
440                bincode::encode_to_vec((list.id, state, &list.msgs), bincode::config::standard())
441                    .unwrap();
442            assert_eq!(old_bytes.len(), bytes.len() + 1);
443            let (decoded, used): (CopperList<CuStampedDataSet>, usize) =
444                bincode::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
445            assert_eq!(decoded.id, list.id);
446            assert_eq!(decoded.msgs, list.msgs);
447            assert_eq!(decoded.get_state(), CopperListState::BeingSerialized);
448            assert_eq!(used, bytes.len());
449            let (borrowed, used): (CopperList<CuStampedDataSet>, usize) =
450                bincode::borrow_decode_from_slice(&bytes, bincode::config::standard()).unwrap();
451            assert_eq!(borrowed.get_state(), CopperListState::BeingSerialized);
452            assert_eq!(borrowed.msgs, list.msgs);
453            assert_eq!(used, bytes.len());
454            assert_eq!(list.get_state(), state);
455            #[cfg(feature = "std")]
456            {
457                let json = serde_json::to_value(&list).unwrap();
458                assert_eq!(json, serde_json::json!({"id": 42, "msgs": 123}));
459                let decoded: CopperList<CuStampedDataSet> = serde_json::from_value(json).unwrap();
460                assert_eq!(decoded.get_state(), CopperListState::BeingSerialized);
461                assert_eq!(decoded.msgs, list.msgs);
462            }
463        }
464    }
465
466    #[test]
467    fn empty_queue() {
468        let q = CuListsManager::<CuStampedDataSet, 5>::new();
469
470        assert!(q.is_empty());
471        assert!(q.iter().next().is_none());
472        assert!(q.asc_iter().next().is_none());
473        assert!(q.peek().is_none());
474    }
475
476    #[test]
477    fn partially_full_queue() {
478        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
479        q.create().unwrap().msgs.0 = 1;
480        q.create().unwrap().msgs.0 = 2;
481        q.create().unwrap().msgs.0 = 3;
482
483        assert!(!q.is_empty());
484        assert_eq!(q.len(), 3);
485
486        let res: Vec<i32> = q.iter().map(|x| x.msgs.0).collect();
487        assert_eq!(res, [3, 2, 1]);
488    }
489
490    #[test]
491    fn full_queue() {
492        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
493        q.create().unwrap().msgs.0 = 1;
494        q.create().unwrap().msgs.0 = 2;
495        q.create().unwrap().msgs.0 = 3;
496        q.create().unwrap().msgs.0 = 4;
497        q.create().unwrap().msgs.0 = 5;
498        assert_eq!(q.len(), 5);
499
500        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
501        assert_eq!(res, [5, 4, 3, 2, 1]);
502    }
503
504    #[test]
505    fn over_full_queue() {
506        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
507        q.create().unwrap().msgs.0 = 1;
508        q.create().unwrap().msgs.0 = 2;
509        q.create().unwrap().msgs.0 = 3;
510        q.create().unwrap().msgs.0 = 4;
511        q.create().unwrap().msgs.0 = 5;
512        assert!(q.create().is_none());
513        assert_eq!(q.len(), 5);
514
515        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
516        assert_eq!(res, [5, 4, 3, 2, 1]);
517    }
518
519    #[test]
520    fn clear() {
521        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
522        q.create().unwrap().msgs.0 = 1;
523        q.create().unwrap().msgs.0 = 2;
524        q.create().unwrap().msgs.0 = 3;
525        q.create().unwrap().msgs.0 = 4;
526        q.create().unwrap().msgs.0 = 5;
527        assert!(q.create().is_none());
528        assert_eq!(q.len(), 5);
529
530        q.clear();
531
532        assert_eq!(q.len(), 0);
533        assert!(q.iter().next().is_none());
534
535        q.create().unwrap().msgs.0 = 1;
536        q.create().unwrap().msgs.0 = 2;
537        q.create().unwrap().msgs.0 = 3;
538
539        assert_eq!(q.len(), 3);
540
541        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
542        assert_eq!(res, [3, 2, 1]);
543    }
544
545    #[test]
546    fn create_fresh_slot_starts_initialized_with_zeroed_payload() {
547        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
548
549        let cl = q.create().unwrap();
550        assert_eq!(cl.id, 0);
551        assert_eq!(cl.get_state(), CopperListState::Initialized);
552        assert_eq!(cl.msgs.0, 0);
553        assert_eq!(q.next_cl_id(), 1);
554        assert_eq!(q.last_cl_id(), 0);
555    }
556
557    #[test]
558    fn create_reused_slot_reinitializes_state_but_preserves_payload_storage() {
559        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
560
561        {
562            let cl = q.create().unwrap();
563            cl.msgs.0 = 41;
564            cl.change_state(CopperListState::Processing);
565        }
566
567        let popped = q.pop().unwrap();
568        assert_eq!(popped.id, 0);
569        assert_eq!(popped.get_state(), CopperListState::Processing);
570        assert_eq!(popped.msgs.0, 41);
571
572        let reused = q.create().unwrap();
573        assert_eq!(reused.id, 1);
574        assert_eq!(reused.get_state(), CopperListState::Initialized);
575        assert_eq!(reused.msgs.0, 41);
576        assert_eq!(q.next_cl_id(), 2);
577        assert_eq!(q.last_cl_id(), 1);
578    }
579
580    #[test]
581    fn mutable_iterator() {
582        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
583        q.create().unwrap().msgs.0 = 1;
584        q.create().unwrap().msgs.0 = 2;
585        q.create().unwrap().msgs.0 = 3;
586        q.create().unwrap().msgs.0 = 4;
587        q.create().unwrap().msgs.0 = 5;
588
589        for x in q.iter_mut() {
590            x.msgs.0 *= 2;
591        }
592
593        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
594        assert_eq!(res, [10, 8, 6, 4, 2]);
595    }
596
597    #[test]
598    fn mutable_iterator_non_wrapped_only_visits_active_slots() {
599        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
600        q.create().unwrap().msgs.0 = 1;
601        q.create().unwrap().msgs.0 = 2;
602        q.create().unwrap().msgs.0 = 3;
603
604        let mut visited = Vec::new();
605        for cl in q.iter_mut() {
606            visited.push(cl.id);
607            cl.msgs.0 *= 10;
608        }
609
610        assert_eq!(visited, vec![2, 1, 0]);
611        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
612        assert_eq!(res, [30, 20, 10]);
613    }
614
615    #[test]
616    fn test_drop_last() {
617        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
618        q.create().unwrap().msgs.0 = 1;
619        q.create().unwrap().msgs.0 = 2;
620        q.create().unwrap().msgs.0 = 3;
621        q.create().unwrap().msgs.0 = 4;
622        q.create().unwrap().msgs.0 = 5;
623        assert_eq!(q.len(), 5);
624
625        q.drop_last();
626        assert_eq!(q.len(), 4);
627
628        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
629        assert_eq!(res, [4, 3, 2, 1]);
630    }
631
632    #[test]
633    fn drop_last_on_empty_queue_is_a_noop() {
634        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
635
636        q.drop_last();
637
638        assert!(q.is_empty());
639        assert!(q.peek().is_none());
640        assert!(q.pop().is_none());
641    }
642
643    #[test]
644    fn drop_last_on_non_wrapped_queue_removes_most_recent_slot() {
645        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
646        q.create().unwrap().msgs.0 = 1;
647        q.create().unwrap().msgs.0 = 2;
648        q.create().unwrap().msgs.0 = 3;
649
650        q.drop_last();
651
652        assert_eq!(q.len(), 2);
653        assert_eq!(q.peek().unwrap().msgs.0, 2);
654        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
655        assert_eq!(res, [2, 1]);
656    }
657
658    #[test]
659    fn test_pop() {
660        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
661        q.create().unwrap().msgs.0 = 1;
662        q.create().unwrap().msgs.0 = 2;
663        q.create().unwrap().msgs.0 = 3;
664        q.create().unwrap().msgs.0 = 4;
665        q.create().unwrap().msgs.0 = 5;
666        assert_eq!(q.len(), 5);
667
668        let last = q.pop().unwrap();
669        assert_eq!(last.msgs.0, 5);
670        assert_eq!(q.len(), 4);
671
672        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
673        assert_eq!(res, [4, 3, 2, 1]);
674    }
675
676    #[test]
677    fn pop_on_empty_queue_returns_none() {
678        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
679
680        assert!(q.pop().is_none());
681        assert!(q.is_empty());
682    }
683
684    #[test]
685    fn test_peek() {
686        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
687        q.create().unwrap().msgs.0 = 1;
688        q.create().unwrap().msgs.0 = 2;
689        q.create().unwrap().msgs.0 = 3;
690        q.create().unwrap().msgs.0 = 4;
691        q.create().unwrap().msgs.0 = 5;
692        assert_eq!(q.len(), 5);
693
694        let last = q.peek().unwrap();
695        assert_eq!(last.msgs.0, 5);
696        assert_eq!(q.len(), 5);
697
698        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
699        assert_eq!(res, [5, 4, 3, 2, 1]);
700    }
701
702    #[test]
703    fn peek_on_empty_queue_returns_none() {
704        let q = CuListsManager::<CuStampedDataSet, 5>::new();
705
706        assert!(q.peek().is_none());
707    }
708
709    #[test]
710    fn next_and_last_cl_id_track_assigned_ids() {
711        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
712
713        // Before first allocation, next id is 0 and last id saturates to 0.
714        assert_eq!(q.next_cl_id(), 0);
715        assert_eq!(q.last_cl_id(), 0);
716
717        let cl0 = q.create().unwrap();
718        assert_eq!(cl0.id, 0);
719        assert_eq!(q.next_cl_id(), 1);
720        assert_eq!(q.last_cl_id(), 0);
721
722        let cl1 = q.create().unwrap();
723        assert_eq!(cl1.id, 1);
724        assert_eq!(q.next_cl_id(), 2);
725        assert_eq!(q.last_cl_id(), 1);
726
727        let _ = q.pop().unwrap();
728        let cl2 = q.create().unwrap();
729        assert_eq!(cl2.id, 2);
730        assert_eq!(q.next_cl_id(), 3);
731        assert_eq!(q.last_cl_id(), 2);
732    }
733
734    #[test]
735    fn asc_iter_non_wrapped_returns_oldest_to_newest_without_free_slots() {
736        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
737        q.create().unwrap().msgs.0 = 10;
738        q.create().unwrap().msgs.0 = 20;
739        q.create().unwrap().msgs.0 = 30;
740
741        let res: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
742        assert_eq!(res, [10, 20, 30]);
743    }
744
745    #[test]
746    fn asc_iter_mut_non_wrapped_only_visits_active_slots_in_oldest_first_order() {
747        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
748        q.create().unwrap().msgs.0 = 10;
749        q.create().unwrap().msgs.0 = 20;
750        q.create().unwrap().msgs.0 = 30;
751
752        let mut visited = Vec::new();
753        for (offset, cl) in q.asc_iter_mut().enumerate() {
754            visited.push(cl.id);
755            cl.msgs.0 += offset as i32;
756        }
757
758        assert_eq!(visited, vec![0, 1, 2]);
759        let res: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
760        assert_eq!(res, [10, 21, 32]);
761    }
762
763    #[test]
764    fn asc_iter_wrapped_layout_tracks_reused_slots_in_ascending_order() {
765        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
766        for value in 1..=5 {
767            q.create().unwrap().msgs.0 = value;
768        }
769        assert_eq!(q.pop().unwrap().msgs.0, 5);
770        assert_eq!(q.pop().unwrap().msgs.0, 4);
771        q.create().unwrap().msgs.0 = 6;
772        q.create().unwrap().msgs.0 = 7;
773
774        let desc: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
775        assert_eq!(desc, [7, 6, 3, 2, 1]);
776
777        let asc: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
778        assert_eq!(asc, [1, 2, 3, 6, 7]);
779    }
780
781    #[test]
782    fn asc_iter_mut_wrapped_layout_updates_oldest_to_newest_order() {
783        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
784        for value in 1..=5 {
785            q.create().unwrap().msgs.0 = value;
786        }
787        let _ = q.pop().unwrap();
788        let _ = q.pop().unwrap();
789        q.create().unwrap().msgs.0 = 6;
790        q.create().unwrap().msgs.0 = 7;
791
792        let mut visited = Vec::new();
793        for (offset, cl) in q.asc_iter_mut().enumerate() {
794            visited.push(cl.id);
795            cl.msgs.0 += offset as i32;
796        }
797
798        assert_eq!(visited, vec![0, 1, 2, 5, 6]);
799        let asc: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
800        assert_eq!(asc, [1, 3, 5, 9, 11]);
801    }
802
803    #[derive(Decode, Encode, Debug, PartialEq, Clone, Copy)]
804    struct TestStruct {
805        content: [u8; 10_000_000],
806    }
807
808    impl Default for TestStruct {
809        fn default() -> Self {
810            TestStruct {
811                content: [0; 10_000_000],
812            }
813        }
814    }
815
816    impl ErasedCuStampedDataSet for TestStruct {
817        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
818            Vec::new()
819        }
820    }
821
822    impl Serialize for TestStruct {
823        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
824        where
825            S: Serializer,
826        {
827            serializer.serialize_i8(0)
828        }
829    }
830
831    impl MatchingTasks for TestStruct {
832        fn get_all_task_ids() -> &'static [&'static str] {
833            &[]
834        }
835    }
836
837    impl CuListZeroedInit for TestStruct {
838        fn init_zeroed(&mut self) {}
839
840        fn init_in_place(dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
841            // SAFETY: TestStruct contains only bytes, all initialized to zero.
842            unsafe {
843                dst.as_mut_ptr().write_bytes(0, 1);
844                dst.assume_init_mut()
845            }
846        }
847    }
848
849    #[test]
850    fn be_sure_we_wont_stackoverflow_at_init() {
851        let _ = CuListsManager::<TestStruct, 3>::new();
852    }
853}