cu29 1.0.0-rc2

Copper Runtime prelude crate. Copper is a Rust engine for robotics.
Documentation
use crate::bincode::{Decode, Encode};
use crate::copperlist::{CopperList, CopperListState, CuListZeroedInit, CuListsManager};
use crate::curuntime::SyncCopperListsManager;
use crate::{
    CuResult, ErasedCuStampedData, ErasedCuStampedDataSet, MatchingTasks, WriteStream, safety_case,
    safety_check, safety_check_eq,
};
use serde::{Deserialize, Serialize};
use std::boxed::Box;
use std::sync::{Arc, Mutex};
use std::vec::Vec;

#[derive(Debug, Default, Encode, Decode, Serialize, Deserialize)]
struct IntMsgs(i32);

impl ErasedCuStampedDataSet for IntMsgs {
    fn cumsgs(&self) -> Vec<&dyn ErasedCuStampedData> {
        Vec::new()
    }
}

impl MatchingTasks for IntMsgs {
    fn get_all_task_ids() -> &'static [&'static str] {
        &[]
    }
}

impl CuListZeroedInit for IntMsgs {
    fn init_zeroed(&mut self) {}
}

#[derive(Debug)]
struct RecordingSyncWriter {
    ids: Arc<Mutex<Vec<u64>>>,
}

impl RecordingSyncWriter {
    fn new(ids: Arc<Mutex<Vec<u64>>>) -> Self {
        Self { ids }
    }
}

impl WriteStream<CopperList<IntMsgs>> for RecordingSyncWriter {
    fn log(&mut self, culist: &CopperList<IntMsgs>) -> CuResult<()> {
        self.ids.lock().unwrap().push(culist.id);
        Ok(())
    }
}

fn desc_values<const N: usize>(q: &CuListsManager<IntMsgs, N>) -> Vec<i32> {
    q.iter().map(|cl| cl.msgs.0).collect()
}

fn asc_values<const N: usize>(q: &CuListsManager<IntMsgs, N>) -> Vec<i32> {
    q.asc_iter().map(|cl| cl.msgs.0).collect()
}

#[cfg_attr(test, test)]
#[safety_case("CLM-TEST-001")]
fn copperlist_capacity_is_bounded() {
    let mut q = CuListsManager::<IntMsgs, 2>::new();
    let _ = q.create().unwrap();
    let _ = q.create().unwrap();

    safety_check!(
        "CLM-TEST-001-C1",
        "CLM-REQ-001",
        "CuListsManager create returns None once every slot is active",
        q.create().is_none(),
    );

    let mut sync = SyncCopperListsManager::<IntMsgs, 1>::new(None).unwrap();
    let _ = sync.create().unwrap();
    let exhausted = sync.create().unwrap_err();

    safety_check!(
        "CLM-TEST-001-C2",
        "CLM-REQ-001",
        "SyncCopperListsManager create returns an explicit exhaustion error",
        exhausted
            .to_string()
            .contains("Ran out of space for copper lists"),
    );
}

#[cfg_attr(test, test)]
#[safety_case("CLM-TEST-002")]
fn copperlist_ids_are_monotonic_across_reuse() {
    let mut q = CuListsManager::<IntMsgs, 3>::new();
    let before = (q.next_cl_id(), q.last_cl_id());

    let cl0 = q.create().unwrap().id;
    let cl1 = q.create().unwrap().id;
    let _ = q.pop().unwrap();
    let cl2 = q.create().unwrap().id;
    let after = (cl0, cl1, cl2, q.next_cl_id(), q.last_cl_id());

    safety_check_eq!(
        "CLM-TEST-002-C1",
        "CLM-REQ-002",
        "CopperList ids and next/last tracking stay monotonic across slot reuse",
        (before, after),
        ((0, 0), (0, 1, 2, 3, 2)),
    );
}

#[cfg_attr(test, test)]
#[safety_case("CLM-TEST-003")]
fn copperlist_accessors_preserve_active_slot_ordering() {
    let mut empty = CuListsManager::<IntMsgs, 3>::new();
    let empty_peek = empty.peek().is_none();
    let empty_pop = empty.pop().is_none();
    let empty_asc = empty.asc_iter().next().is_none();

    safety_check!(
        "CLM-TEST-003-C1",
        "CLM-REQ-003",
        "Empty peek/pop/ascending iteration expose no active CopperLists",
        empty_peek && empty_pop && empty_asc,
    );

    let mut desc = CuListsManager::<IntMsgs, 5>::new();
    desc.create().unwrap().msgs.0 = 1;
    desc.create().unwrap().msgs.0 = 2;
    desc.create().unwrap().msgs.0 = 3;

    let mut visited_desc = Vec::new();
    for cl in desc.iter_mut() {
        visited_desc.push(cl.id);
        cl.msgs.0 *= 10;
    }

    safety_check_eq!(
        "CLM-TEST-003-C2",
        "CLM-REQ-003",
        "Descending mutable iteration visits only active slots from newest to oldest",
        (visited_desc, desc_values(&desc)),
        (vec![2, 1, 0], vec![30, 20, 10]),
    );

    let mut asc_nonwrapped = CuListsManager::<IntMsgs, 5>::new();
    asc_nonwrapped.create().unwrap().msgs.0 = 10;
    asc_nonwrapped.create().unwrap().msgs.0 = 20;
    asc_nonwrapped.create().unwrap().msgs.0 = 30;

    let mut visited_asc = Vec::new();
    for (offset, cl) in asc_nonwrapped.asc_iter_mut().enumerate() {
        visited_asc.push(cl.id);
        cl.msgs.0 += offset as i32;
    }

    safety_check_eq!(
        "CLM-TEST-003-C3",
        "CLM-REQ-003",
        "Ascending mutable iteration visits active slots from oldest to newest",
        (visited_asc, asc_values(&asc_nonwrapped)),
        (vec![0, 1, 2], vec![10, 21, 32]),
    );

    let mut wrapped = CuListsManager::<IntMsgs, 5>::new();
    for value in 1..=5 {
        wrapped.create().unwrap().msgs.0 = value;
    }
    let _ = wrapped.pop().unwrap();
    let _ = wrapped.pop().unwrap();
    wrapped.create().unwrap().msgs.0 = 6;
    wrapped.create().unwrap().msgs.0 = 7;

    safety_check_eq!(
        "CLM-TEST-003-C4",
        "CLM-REQ-003",
        "Wrapped access preserves descending and ascending order across slot reuse",
        (desc_values(&wrapped), asc_values(&wrapped)),
        (vec![7, 6, 3, 2, 1], vec![1, 2, 3, 6, 7]),
    );
}

#[cfg_attr(test, test)]
#[safety_case("CLM-TEST-004")]
fn copperlist_allocation_resets_runtime_state() {
    let mut fresh_manager = CuListsManager::<IntMsgs, 1>::new();
    let fresh = {
        let cl = fresh_manager.create().unwrap();
        (cl.id, cl.get_state(), cl.msgs.0)
    };
    let fresh_ids = (fresh_manager.next_cl_id(), fresh_manager.last_cl_id());

    safety_check_eq!(
        "CLM-TEST-004-C1",
        "CLM-REQ-004",
        "Fresh allocation starts initialized with a fixed-up payload",
        (fresh, fresh_ids),
        ((0, CopperListState::Initialized, 0), (1, 0)),
    );

    let mut reused_manager = CuListsManager::<IntMsgs, 1>::new();
    {
        let cl = reused_manager.create().unwrap();
        cl.msgs.0 = 41;
        cl.change_state(CopperListState::Processing);
    }
    let popped = {
        let cl = reused_manager.pop().unwrap();
        (cl.id, cl.get_state(), cl.msgs.0)
    };
    assert_eq!(popped, (0, CopperListState::Processing, 41));

    let reused = {
        let cl = reused_manager.create().unwrap();
        (cl.id, cl.get_state(), cl.msgs.0)
    };
    let reused_ids = (reused_manager.next_cl_id(), reused_manager.last_cl_id());

    safety_check_eq!(
        "CLM-TEST-004-C2",
        "CLM-REQ-004",
        "Reused CuListsManager slots restore Initialized and retain payload storage",
        (reused, reused_ids),
        ((1, CopperListState::Initialized, 41), (2, 1)),
    );

    let mut sync = SyncCopperListsManager::<IntMsgs, 1>::new(None).unwrap();
    {
        let cl = sync.create().unwrap();
        cl.msgs.0 = 41;
        cl.change_state(CopperListState::Processing);
    }
    sync.end_of_processing(0).unwrap();
    let sync_reused = {
        let cl = sync.create().unwrap();
        (cl.id, cl.get_state(), cl.msgs.0)
    };

    safety_check_eq!(
        "CLM-TEST-004-C3",
        "CLM-REQ-004",
        "Synchronous runtime slot reuse restores Initialized and retains payload storage",
        sync_reused,
        (1, CopperListState::Initialized, 41),
    );
}

#[cfg_attr(test, test)]
#[safety_case("CLM-TEST-005")]
fn sync_runtime_reclaims_only_the_completed_top_suffix() {
    let ids = Arc::new(Mutex::new(Vec::new()));
    let mut sync = SyncCopperListsManager::<IntMsgs, 2>::new(Some(Box::new(
        RecordingSyncWriter::new(ids.clone()),
    )))
    .unwrap();

    {
        let cl = sync.create().unwrap();
        cl.msgs.0 = 10;
        cl.change_state(CopperListState::Processing);
    }
    {
        let cl = sync.create().unwrap();
        cl.msgs.0 = 20;
        cl.change_state(CopperListState::Processing);
    }

    sync.end_of_processing(0).unwrap();
    let pre_ids = ids.lock().unwrap().clone();
    let pre_available = sync.available_copper_lists().unwrap();

    sync.end_of_processing(1).unwrap();
    let post_ids = ids.lock().unwrap().clone();
    let post_available = sync.available_copper_lists().unwrap();

    safety_check_eq!(
        "CLM-TEST-005-C1",
        "CLM-REQ-005",
        "The synchronous runtime reclaims only the completed top suffix in newest-first order",
        ((pre_ids, pre_available), (post_ids, post_available)),
        ((Vec::<u64>::new(), 0usize), (vec![1, 0], 2usize)),
    );
}

#[cfg(feature = "safety-ids")]
pub fn link_safety_ids() {
    let _ = copperlist_capacity_is_bounded as fn();
    let _ = copperlist_ids_are_monotonic_across_reuse as fn();
    let _ = copperlist_accessors_preserve_active_slot_ordering as fn();
    let _ = copperlist_allocation_resets_runtime_state as fn();
    let _ = sync_runtime_reclaims_only_the_completed_top_suffix as fn();
}