Skip to main content

cu29_runtime/
clock_sync.rs

1//! Clock-reference lifecycle and portable correction records.
2
3use bincode::{Decode, Encode};
4use cu29_clock::sync::ClockSnapshot;
5
6/// Recorded correction applied before the named CopperList.
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
8pub struct ClockSyncRecord {
9    /// First CopperList using this published curve.
10    pub culistid: u64,
11    /// Reference mapping and uncertainty at publication.
12    pub snapshot: ClockSnapshot,
13}
14
15#[cfg(feature = "clock-sync")]
16mod maintenance;
17#[cfg(feature = "clock-sync")]
18pub use maintenance::{ClockMaintenance, ClockReference, ClockReferenceBundle, MaintenanceConfig};
19
20/// Reads corrections from a selected run's RuntimeLifecycle stream, offline.
21#[cfg(all(feature = "std", feature = "clock-sync"))]
22pub fn read_clock_sync_records(
23    mut source: impl std::io::Read,
24) -> cu29_traits::CuResult<alloc::vec::Vec<ClockSyncRecord>> {
25    use crate::curuntime::{RuntimeLifecycleEvent, RuntimeLifecycleRecord};
26    use cu29_traits::CuError;
27    use std::io::Read;
28    let mut records = alloc::vec::Vec::new();
29    loop {
30        // Probe a single byte so EOF is accepted only between records. An
31        // incomplete record must remain an error, even if its last field is one byte.
32        let mut first = [0u8; 1];
33        match source.read_exact(&mut first) {
34            Ok(()) => {}
35            Err(error) if error.kind() == std::io::ErrorKind::UnexpectedEof => break,
36            Err(error) => {
37                return Err(CuError::new_with_cause(
38                    "Clock correction read failed",
39                    error,
40                ));
41            }
42        }
43        let mut reader = std::io::Cursor::new(first).chain(&mut source);
44        let record: RuntimeLifecycleRecord =
45            bincode::decode_from_std_read(&mut reader, bincode::config::standard()).map_err(
46                |error| CuError::new_with_cause("Clock correction decode failed", error),
47            )?;
48        if let RuntimeLifecycleEvent::ClockSync(record) = record.event {
49            if records
50                .last()
51                .is_some_and(|last: &ClockSyncRecord| last.culistid > record.culistid)
52            {
53                return Err(CuError::from(
54                    "Clock corrections are out of CopperList order; select one recorded run",
55                ));
56            }
57            records.push(record);
58        }
59    }
60    Ok(records)
61}
62
63#[cfg(all(feature = "std", feature = "clock-sync"))]
64pub(crate) fn load_clock_sync_records(
65    path: &std::path::Path,
66) -> cu29_traits::CuResult<alloc::vec::Vec<ClockSyncRecord>> {
67    let logger = crate::debug::build_read_logger(path)?;
68    read_clock_sync_records(cu29_unifiedlog::UnifiedLoggerIOReader::new(
69        logger,
70        cu29_traits::UnifiedLogType::RuntimeLifecycle,
71    ))
72}
73
74/// Offline clock restoration used by debugger callbacks with a recorded counter.
75#[cfg(all(feature = "std", feature = "clock-sync"))]
76pub(crate) struct ClockReplay {
77    records: alloc::vec::Vec<ClockSyncRecord>,
78    controller: Option<cu29_clock::sync::ClockSync>,
79}
80
81#[cfg(all(feature = "std", feature = "clock-sync"))]
82impl ClockReplay {
83    pub(crate) fn new(records: alloc::vec::Vec<ClockSyncRecord>) -> Self {
84        Self {
85            records,
86            controller: None,
87        }
88    }
89    pub(crate) fn apply(
90        &mut self,
91        clock: &cu29_clock::RobotClock,
92        mock: &cu29_clock::RobotClockMock,
93        id: u64,
94        time: cu29_clock::CuTime,
95    ) -> cu29_traits::CuResult<()> {
96        use cu29_clock::sync::ClockSync;
97        use cu29_traits::CuError;
98        let index = self.records.partition_point(|record| record.culistid <= id);
99        if index == 0 {
100            mock.set_value(time.0);
101            return Ok(());
102        }
103        let snapshot = self.records[index - 1].snapshot;
104        if let Some(controller) = &mut self.controller {
105            controller
106                .restore(snapshot)
107                .map_err(|error| CuError::new_with_cause("Replay clock restore failed", error))?;
108        } else {
109            self.controller =
110                Some(ClockSync::from_snapshot(clock, snapshot).map_err(|error| {
111                    CuError::new_with_cause("Replay clock attach failed", error)
112                })?);
113        }
114        if let Some(controller) = &mut self.controller {
115            controller
116                .set_replay_time(mock, time)
117                .map_err(|error| CuError::new_with_cause("Replay clock time failed", error))?;
118        }
119        Ok(())
120    }
121}