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    /// Whether generated execution uses move-only arena slots.
171    #[doc(hidden)]
172    const DISTRIBUTED: bool = false;
173
174    /// Resets per-cycle metadata on an already initialized dataset.
175    fn init_zeroed(&mut self);
176
177    /// Constructs a valid dataset in its pool storage, once at startup.
178    /// Generated datasets override this to initialize messages individually.
179    ///
180    /// Returns the initialized value in `dst`. The pool checks that the returned
181    /// reference points to the supplied slot before exposing or dropping it.
182    #[doc(hidden)]
183    fn init_in_place(dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
184        dst.write(Self::default())
185    }
186}
187
188impl<P: CopperListTuple + CuListZeroedInit, const N: usize> Default for CuListsManager<P, N> {
189    fn default() -> Self {
190        Self::new()
191    }
192}
193
194impl<P: CopperListTuple, const N: usize> CuListsManager<P, N> {
195    pub fn new() -> Self
196    where
197        P: CuListZeroedInit,
198    {
199        let mut slots = Vec::<CopperList<P>>::with_capacity(N);
200        for index in 0..N {
201            // SAFETY: Capacity is N and this slot has not been initialized yet.
202            // Advancing the length afterwards also makes Vec drop all completed
203            // slots if initialization of a later slot unwinds.
204            unsafe {
205                CopperList::init_in_place(slots.as_mut_ptr().add(index));
206                slots.set_len(index + 1);
207            }
208        }
209        // SAFETY: Exactly N initialized slots were placed in the boxed slice.
210        let data = unsafe {
211            Box::from_raw(Box::into_raw(slots.into_boxed_slice()) as *mut [CopperList<P>; N])
212        };
213        CuListsManager {
214            data,
215            length: 0,
216            insertion_index: 0,
217            current_cl_id: 0,
218        }
219    }
220
221    /// Returns the current number of elements in the queue.
222    ///
223    #[inline]
224    pub fn len(&self) -> usize {
225        self.length
226    }
227
228    /// Returns `true` if the queue contains no elements.
229    ///
230    #[inline]
231    pub fn is_empty(&self) -> bool {
232        self.length == 0
233    }
234
235    /// Returns `true` if the queue is full.
236    ///
237    #[inline]
238    pub fn is_full(&self) -> bool {
239        N == self.len()
240    }
241
242    /// Clears the queue.
243    ///
244    #[inline]
245    pub fn clear(&mut self) {
246        self.insertion_index = 0;
247        self.length = 0;
248    }
249
250    #[inline]
251    pub fn create(&mut self) -> Option<&mut CopperList<P>>
252    where
253        P: CuListZeroedInit,
254    {
255        if self.is_full() {
256            return None;
257        }
258        let next_id = self.current_cl_id;
259        let result = &mut self.data[self.insertion_index];
260        self.insertion_index = (self.insertion_index + 1) % N;
261        self.length += 1;
262
263        // We assign a unique id to each CopperList to be able to track them across their lifetime.
264        result.reset_for_runtime_use(next_id);
265        self.current_cl_id += 1;
266
267        Some(result)
268    }
269
270    /// Returns the next copper-list id that will be assigned by [`create`](Self::create).
271    #[inline]
272    pub fn next_cl_id(&self) -> u64 {
273        self.current_cl_id
274    }
275
276    pub(crate) fn set_next_replay_id(&mut self, id: u64) {
277        debug_assert_eq!(self.length, 0);
278        self.current_cl_id = id;
279    }
280
281    /// Returns the most recently assigned copper-list id.
282    ///
283    /// Before the first call to [`create`](Self::create), this returns `0`.
284    #[inline]
285    pub fn last_cl_id(&self) -> u64 {
286        self.current_cl_id.saturating_sub(1)
287    }
288
289    /// Peeks at the last element in the queue.
290    #[inline]
291    pub fn peek(&self) -> Option<&CopperList<P>> {
292        if self.length == 0 {
293            return None;
294        }
295        let index = if self.insertion_index == 0 {
296            N - 1
297        } else {
298            self.insertion_index - 1
299        };
300        Some(&self.data[index])
301    }
302
303    #[inline]
304    #[allow(dead_code)]
305    fn drop_last(&mut self) {
306        if self.length == 0 {
307            return;
308        }
309        if self.insertion_index == 0 {
310            self.insertion_index = N - 1;
311        } else {
312            self.insertion_index -= 1;
313        }
314        self.length -= 1;
315    }
316
317    #[inline]
318    pub fn pop(&mut self) -> Option<&mut CopperList<P>> {
319        if self.length == 0 {
320            return None;
321        }
322        if self.insertion_index == 0 {
323            self.insertion_index = N - 1;
324        } else {
325            self.insertion_index -= 1;
326        }
327        self.length -= 1;
328        Some(&mut self.data[self.insertion_index])
329    }
330
331    /// Returns an iterator over the queue's contents.
332    ///
333    /// The iterator goes from the most recently pushed items to the oldest ones.
334    ///
335    #[inline]
336    pub fn iter(&self) -> Iter<'_, CopperList<P>> {
337        let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
338        a.iter().rev().chain(b.iter().rev())
339    }
340
341    /// Returns a mutable iterator over the queue's contents.
342    ///
343    /// The iterator goes from the most recently pushed items to the oldest ones.
344    ///
345    #[inline]
346    pub fn iter_mut(&mut self) -> IterMut<'_, CopperList<P>> {
347        let (a, b) = self.data[0..self.length].split_at_mut(self.insertion_index);
348        a.iter_mut().rev().chain(b.iter_mut().rev())
349    }
350
351    /// Returns an ascending iterator over the queue's contents.
352    ///
353    /// The iterator goes from the least recently pushed items to the newest ones.
354    ///
355    #[inline]
356    pub fn asc_iter(&self) -> AscIter<'_, CopperList<P>> {
357        let (a, b) = self.data[0..self.length].split_at(self.insertion_index);
358        b.iter().chain(a.iter())
359    }
360
361    /// Returns a mutable ascending iterator over the queue's contents.
362    ///
363    /// The iterator goes from the least recently pushed items to the newest ones.
364    ///
365    #[inline]
366    pub fn asc_iter_mut(&mut self) -> AscIterMut<'_, CopperList<P>> {
367        let (a, b) = self.data[0..self.length].split_at_mut(self.insertion_index);
368        b.iter_mut().chain(a.iter_mut())
369    }
370}
371
372/// One cache-isolated message output owned by a generated execution stage.
373#[repr(C, align(128))]
374#[derive(Default, Debug, Encode, Decode, Serialize, Deserialize)]
375#[serde(transparent)]
376#[doc(hidden)]
377pub struct MessageRegion<T> {
378    pub value: T,
379}
380
381impl<T> From<T> for MessageRegion<T> {
382    fn from(value: T) -> Self {
383        Self { value }
384    }
385}
386
387impl<T> core::ops::Deref for MessageRegion<T> {
388    type Target = T;
389
390    fn deref(&self) -> &Self::Target {
391        &self.value
392    }
393}
394
395impl<T> core::ops::DerefMut for MessageRegion<T> {
396    fn deref_mut(&mut self) -> &mut Self::Target {
397        &mut self.value
398    }
399}
400
401/// Cache-isolated storage for a task or bridge instance used by worker threads.
402#[repr(C, align(128))]
403#[doc(hidden)]
404pub struct ComponentRegion<T> {
405    pub value: T,
406}
407
408impl<T> ComponentRegion<T> {
409    pub const fn new(value: T) -> Self {
410        Self { value }
411    }
412}
413
414impl<T> core::ops::Deref for ComponentRegion<T> {
415    type Target = T;
416
417    fn deref(&self) -> &Self::Target {
418        &self.value
419    }
420}
421
422impl<T> core::ops::DerefMut for ComponentRegion<T> {
423    fn deref_mut(&mut self) -> &mut Self::Target {
424        &mut self.value
425    }
426}
427
428impl<T: crate::cutask::Freezable> crate::cutask::Freezable for ComponentRegion<T> {
429    #[inline(always)]
430    fn freeze<E: bincode::enc::Encoder>(
431        &self,
432        encoder: &mut E,
433    ) -> Result<(), bincode::error::EncodeError> {
434        self.value.freeze(encoder)
435    }
436
437    #[inline(always)]
438    fn thaw<D: bincode::de::Decoder>(
439        &mut self,
440        decoder: &mut D,
441    ) -> Result<(), bincode::error::DecodeError> {
442        self.value.thaw(decoder)
443    }
444}
445
446#[cfg(test)]
447mod tests {
448    use super::*;
449    use cu29_traits::{ErasedCuStampedData, ErasedCuStampedDataSet, MatchingTasks};
450    use serde::{Deserialize, Serialize, Serializer};
451
452    #[derive(Debug, Encode, Decode, PartialEq, Clone, Copy, Serialize, Deserialize, Default)]
453    struct CuStampedDataSet(i32);
454
455    #[test]
456    fn independently_written_regions_are_cache_isolated() {
457        assert_eq!(core::mem::align_of::<MessageRegion<u8>>(), 128);
458        assert_eq!(core::mem::size_of::<MessageRegion<u8>>(), 128);
459        assert_eq!(core::mem::align_of::<ComponentRegion<u8>>(), 128);
460        assert_eq!(core::mem::size_of::<ComponentRegion<u8>>(), 128);
461
462        let messages = [MessageRegion::from(0u8), MessageRegion::from(1u8)];
463        let first = core::ptr::addr_of!(messages[0]) as usize;
464        let second = core::ptr::addr_of!(messages[1]) as usize;
465        assert_eq!(first % 128, 0);
466        assert_eq!(second - first, 128);
467    }
468
469    impl ErasedCuStampedDataSet for CuStampedDataSet {
470        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
471            Vec::new()
472        }
473    }
474
475    impl MatchingTasks for CuStampedDataSet {
476        fn get_all_task_ids() -> &'static [&'static str] {
477            &[]
478        }
479    }
480
481    impl CuListZeroedInit for CuStampedDataSet {
482        fn init_zeroed(&mut self) {}
483    }
484
485    #[derive(Debug, Default, Encode, Decode, Serialize, Deserialize)]
486    struct WrongSlot;
487
488    impl ErasedCuStampedDataSet for WrongSlot {
489        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
490            Vec::new()
491        }
492    }
493
494    impl MatchingTasks for WrongSlot {
495        fn get_all_task_ids() -> &'static [&'static str] {
496            &[]
497        }
498    }
499
500    impl CuListZeroedInit for WrongSlot {
501        fn init_zeroed(&mut self) {}
502
503        fn init_in_place(_dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
504            Box::leak(Box::new(Self))
505        }
506    }
507
508    #[test]
509    #[should_panic(expected = "initializer returned a different slot")]
510    fn rejects_initializer_returning_another_slot() {
511        let _ = CuListsManager::<WrongSlot, 1>::new();
512    }
513
514    #[test]
515    fn serialization_omits_runtime_state() {
516        let expected =
517            bincode::encode_to_vec((42u64, CuStampedDataSet(123)), bincode::config::standard())
518                .unwrap();
519        for state in [
520            CopperListState::Free,
521            CopperListState::Initialized,
522            CopperListState::Processing,
523            CopperListState::DoneProcessing,
524            CopperListState::QueuedForSerialization,
525            CopperListState::BeingSerialized,
526        ] {
527            let mut list = CopperList::new(42, CuStampedDataSet(123));
528            list.change_state(state);
529            let bytes = bincode::encode_to_vec(&list, bincode::config::standard()).unwrap();
530            assert_eq!(bytes, expected);
531            let old_bytes =
532                bincode::encode_to_vec((list.id, state, &list.msgs), bincode::config::standard())
533                    .unwrap();
534            assert_eq!(old_bytes.len(), bytes.len() + 1);
535            let (decoded, used): (CopperList<CuStampedDataSet>, usize) =
536                bincode::decode_from_slice(&bytes, bincode::config::standard()).unwrap();
537            assert_eq!(decoded.id, list.id);
538            assert_eq!(decoded.msgs, list.msgs);
539            assert_eq!(decoded.get_state(), CopperListState::BeingSerialized);
540            assert_eq!(used, bytes.len());
541            let (borrowed, used): (CopperList<CuStampedDataSet>, usize) =
542                bincode::borrow_decode_from_slice(&bytes, bincode::config::standard()).unwrap();
543            assert_eq!(borrowed.get_state(), CopperListState::BeingSerialized);
544            assert_eq!(borrowed.msgs, list.msgs);
545            assert_eq!(used, bytes.len());
546            assert_eq!(list.get_state(), state);
547            #[cfg(feature = "std")]
548            {
549                let json = serde_json::to_value(&list).unwrap();
550                assert_eq!(json, serde_json::json!({"id": 42, "msgs": 123}));
551                let decoded: CopperList<CuStampedDataSet> = serde_json::from_value(json).unwrap();
552                assert_eq!(decoded.get_state(), CopperListState::BeingSerialized);
553                assert_eq!(decoded.msgs, list.msgs);
554            }
555        }
556    }
557
558    #[test]
559    fn empty_queue() {
560        let q = CuListsManager::<CuStampedDataSet, 5>::new();
561
562        assert!(q.is_empty());
563        assert!(q.iter().next().is_none());
564        assert!(q.asc_iter().next().is_none());
565        assert!(q.peek().is_none());
566    }
567
568    #[test]
569    fn partially_full_queue() {
570        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
571        q.create().unwrap().msgs.0 = 1;
572        q.create().unwrap().msgs.0 = 2;
573        q.create().unwrap().msgs.0 = 3;
574
575        assert!(!q.is_empty());
576        assert_eq!(q.len(), 3);
577
578        let res: Vec<i32> = q.iter().map(|x| x.msgs.0).collect();
579        assert_eq!(res, [3, 2, 1]);
580    }
581
582    #[test]
583    fn full_queue() {
584        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
585        q.create().unwrap().msgs.0 = 1;
586        q.create().unwrap().msgs.0 = 2;
587        q.create().unwrap().msgs.0 = 3;
588        q.create().unwrap().msgs.0 = 4;
589        q.create().unwrap().msgs.0 = 5;
590        assert_eq!(q.len(), 5);
591
592        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
593        assert_eq!(res, [5, 4, 3, 2, 1]);
594    }
595
596    #[test]
597    fn over_full_queue() {
598        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
599        q.create().unwrap().msgs.0 = 1;
600        q.create().unwrap().msgs.0 = 2;
601        q.create().unwrap().msgs.0 = 3;
602        q.create().unwrap().msgs.0 = 4;
603        q.create().unwrap().msgs.0 = 5;
604        assert!(q.create().is_none());
605        assert_eq!(q.len(), 5);
606
607        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
608        assert_eq!(res, [5, 4, 3, 2, 1]);
609    }
610
611    #[test]
612    fn clear() {
613        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
614        q.create().unwrap().msgs.0 = 1;
615        q.create().unwrap().msgs.0 = 2;
616        q.create().unwrap().msgs.0 = 3;
617        q.create().unwrap().msgs.0 = 4;
618        q.create().unwrap().msgs.0 = 5;
619        assert!(q.create().is_none());
620        assert_eq!(q.len(), 5);
621
622        q.clear();
623
624        assert_eq!(q.len(), 0);
625        assert!(q.iter().next().is_none());
626
627        q.create().unwrap().msgs.0 = 1;
628        q.create().unwrap().msgs.0 = 2;
629        q.create().unwrap().msgs.0 = 3;
630
631        assert_eq!(q.len(), 3);
632
633        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
634        assert_eq!(res, [3, 2, 1]);
635    }
636
637    #[test]
638    fn create_fresh_slot_starts_initialized_with_zeroed_payload() {
639        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
640
641        let cl = q.create().unwrap();
642        assert_eq!(cl.id, 0);
643        assert_eq!(cl.get_state(), CopperListState::Initialized);
644        assert_eq!(cl.msgs.0, 0);
645        assert_eq!(q.next_cl_id(), 1);
646        assert_eq!(q.last_cl_id(), 0);
647    }
648
649    #[test]
650    fn create_reused_slot_reinitializes_state_but_preserves_payload_storage() {
651        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
652
653        {
654            let cl = q.create().unwrap();
655            cl.msgs.0 = 41;
656            cl.change_state(CopperListState::Processing);
657        }
658
659        let popped = q.pop().unwrap();
660        assert_eq!(popped.id, 0);
661        assert_eq!(popped.get_state(), CopperListState::Processing);
662        assert_eq!(popped.msgs.0, 41);
663
664        let reused = q.create().unwrap();
665        assert_eq!(reused.id, 1);
666        assert_eq!(reused.get_state(), CopperListState::Initialized);
667        assert_eq!(reused.msgs.0, 41);
668        assert_eq!(q.next_cl_id(), 2);
669        assert_eq!(q.last_cl_id(), 1);
670    }
671
672    #[test]
673    fn mutable_iterator() {
674        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
675        q.create().unwrap().msgs.0 = 1;
676        q.create().unwrap().msgs.0 = 2;
677        q.create().unwrap().msgs.0 = 3;
678        q.create().unwrap().msgs.0 = 4;
679        q.create().unwrap().msgs.0 = 5;
680
681        for x in q.iter_mut() {
682            x.msgs.0 *= 2;
683        }
684
685        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
686        assert_eq!(res, [10, 8, 6, 4, 2]);
687    }
688
689    #[test]
690    fn mutable_iterator_non_wrapped_only_visits_active_slots() {
691        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
692        q.create().unwrap().msgs.0 = 1;
693        q.create().unwrap().msgs.0 = 2;
694        q.create().unwrap().msgs.0 = 3;
695
696        let mut visited = Vec::new();
697        for cl in q.iter_mut() {
698            visited.push(cl.id);
699            cl.msgs.0 *= 10;
700        }
701
702        assert_eq!(visited, vec![2, 1, 0]);
703        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
704        assert_eq!(res, [30, 20, 10]);
705    }
706
707    #[test]
708    fn test_drop_last() {
709        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
710        q.create().unwrap().msgs.0 = 1;
711        q.create().unwrap().msgs.0 = 2;
712        q.create().unwrap().msgs.0 = 3;
713        q.create().unwrap().msgs.0 = 4;
714        q.create().unwrap().msgs.0 = 5;
715        assert_eq!(q.len(), 5);
716
717        q.drop_last();
718        assert_eq!(q.len(), 4);
719
720        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
721        assert_eq!(res, [4, 3, 2, 1]);
722    }
723
724    #[test]
725    fn drop_last_on_empty_queue_is_a_noop() {
726        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
727
728        q.drop_last();
729
730        assert!(q.is_empty());
731        assert!(q.peek().is_none());
732        assert!(q.pop().is_none());
733    }
734
735    #[test]
736    fn drop_last_on_non_wrapped_queue_removes_most_recent_slot() {
737        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
738        q.create().unwrap().msgs.0 = 1;
739        q.create().unwrap().msgs.0 = 2;
740        q.create().unwrap().msgs.0 = 3;
741
742        q.drop_last();
743
744        assert_eq!(q.len(), 2);
745        assert_eq!(q.peek().unwrap().msgs.0, 2);
746        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
747        assert_eq!(res, [2, 1]);
748    }
749
750    #[test]
751    fn test_pop() {
752        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
753        q.create().unwrap().msgs.0 = 1;
754        q.create().unwrap().msgs.0 = 2;
755        q.create().unwrap().msgs.0 = 3;
756        q.create().unwrap().msgs.0 = 4;
757        q.create().unwrap().msgs.0 = 5;
758        assert_eq!(q.len(), 5);
759
760        let last = q.pop().unwrap();
761        assert_eq!(last.msgs.0, 5);
762        assert_eq!(q.len(), 4);
763
764        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
765        assert_eq!(res, [4, 3, 2, 1]);
766    }
767
768    #[test]
769    fn pop_on_empty_queue_returns_none() {
770        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
771
772        assert!(q.pop().is_none());
773        assert!(q.is_empty());
774    }
775
776    #[test]
777    fn test_peek() {
778        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
779        q.create().unwrap().msgs.0 = 1;
780        q.create().unwrap().msgs.0 = 2;
781        q.create().unwrap().msgs.0 = 3;
782        q.create().unwrap().msgs.0 = 4;
783        q.create().unwrap().msgs.0 = 5;
784        assert_eq!(q.len(), 5);
785
786        let last = q.peek().unwrap();
787        assert_eq!(last.msgs.0, 5);
788        assert_eq!(q.len(), 5);
789
790        let res: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
791        assert_eq!(res, [5, 4, 3, 2, 1]);
792    }
793
794    #[test]
795    fn peek_on_empty_queue_returns_none() {
796        let q = CuListsManager::<CuStampedDataSet, 5>::new();
797
798        assert!(q.peek().is_none());
799    }
800
801    #[test]
802    fn next_and_last_cl_id_track_assigned_ids() {
803        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
804
805        // Before first allocation, next id is 0 and last id saturates to 0.
806        assert_eq!(q.next_cl_id(), 0);
807        assert_eq!(q.last_cl_id(), 0);
808
809        let cl0 = q.create().unwrap();
810        assert_eq!(cl0.id, 0);
811        assert_eq!(q.next_cl_id(), 1);
812        assert_eq!(q.last_cl_id(), 0);
813
814        let cl1 = q.create().unwrap();
815        assert_eq!(cl1.id, 1);
816        assert_eq!(q.next_cl_id(), 2);
817        assert_eq!(q.last_cl_id(), 1);
818
819        let _ = q.pop().unwrap();
820        let cl2 = q.create().unwrap();
821        assert_eq!(cl2.id, 2);
822        assert_eq!(q.next_cl_id(), 3);
823        assert_eq!(q.last_cl_id(), 2);
824    }
825
826    #[test]
827    fn asc_iter_non_wrapped_returns_oldest_to_newest_without_free_slots() {
828        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
829        q.create().unwrap().msgs.0 = 10;
830        q.create().unwrap().msgs.0 = 20;
831        q.create().unwrap().msgs.0 = 30;
832
833        let res: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
834        assert_eq!(res, [10, 20, 30]);
835    }
836
837    #[test]
838    fn asc_iter_mut_non_wrapped_only_visits_active_slots_in_oldest_first_order() {
839        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
840        q.create().unwrap().msgs.0 = 10;
841        q.create().unwrap().msgs.0 = 20;
842        q.create().unwrap().msgs.0 = 30;
843
844        let mut visited = Vec::new();
845        for (offset, cl) in q.asc_iter_mut().enumerate() {
846            visited.push(cl.id);
847            cl.msgs.0 += offset as i32;
848        }
849
850        assert_eq!(visited, vec![0, 1, 2]);
851        let res: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
852        assert_eq!(res, [10, 21, 32]);
853    }
854
855    #[test]
856    fn asc_iter_wrapped_layout_tracks_reused_slots_in_ascending_order() {
857        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
858        for value in 1..=5 {
859            q.create().unwrap().msgs.0 = value;
860        }
861        assert_eq!(q.pop().unwrap().msgs.0, 5);
862        assert_eq!(q.pop().unwrap().msgs.0, 4);
863        q.create().unwrap().msgs.0 = 6;
864        q.create().unwrap().msgs.0 = 7;
865
866        let desc: Vec<_> = q.iter().map(|x| x.msgs.0).collect();
867        assert_eq!(desc, [7, 6, 3, 2, 1]);
868
869        let asc: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
870        assert_eq!(asc, [1, 2, 3, 6, 7]);
871    }
872
873    #[test]
874    fn asc_iter_mut_wrapped_layout_updates_oldest_to_newest_order() {
875        let mut q = CuListsManager::<CuStampedDataSet, 5>::new();
876        for value in 1..=5 {
877            q.create().unwrap().msgs.0 = value;
878        }
879        let _ = q.pop().unwrap();
880        let _ = q.pop().unwrap();
881        q.create().unwrap().msgs.0 = 6;
882        q.create().unwrap().msgs.0 = 7;
883
884        let mut visited = Vec::new();
885        for (offset, cl) in q.asc_iter_mut().enumerate() {
886            visited.push(cl.id);
887            cl.msgs.0 += offset as i32;
888        }
889
890        assert_eq!(visited, vec![0, 1, 2, 5, 6]);
891        let asc: Vec<_> = q.asc_iter().map(|x| x.msgs.0).collect();
892        assert_eq!(asc, [1, 3, 5, 9, 11]);
893    }
894
895    #[derive(Decode, Encode, Debug, PartialEq, Clone, Copy)]
896    struct TestStruct {
897        content: [u8; 10_000_000],
898    }
899
900    impl Default for TestStruct {
901        fn default() -> Self {
902            TestStruct {
903                content: [0; 10_000_000],
904            }
905        }
906    }
907
908    impl ErasedCuStampedDataSet for TestStruct {
909        fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
910            Vec::new()
911        }
912    }
913
914    impl Serialize for TestStruct {
915        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
916        where
917            S: Serializer,
918        {
919            serializer.serialize_i8(0)
920        }
921    }
922
923    impl MatchingTasks for TestStruct {
924        fn get_all_task_ids() -> &'static [&'static str] {
925            &[]
926        }
927    }
928
929    impl CuListZeroedInit for TestStruct {
930        fn init_zeroed(&mut self) {}
931
932        fn init_in_place(dst: &mut core::mem::MaybeUninit<Self>) -> &mut Self {
933            // SAFETY: TestStruct contains only bytes, all initialized to zero.
934            unsafe {
935                dst.as_mut_ptr().write_bytes(0, 1);
936                dst.assume_init_mut()
937            }
938        }
939    }
940
941    #[test]
942    fn be_sure_we_wont_stackoverflow_at_init() {
943        let _ = CuListsManager::<TestStruct, 3>::new();
944    }
945}