cu29_runtime/
clock_sync.rs1use bincode::{Decode, Encode};
4use cu29_clock::sync::ClockSnapshot;
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
8pub struct ClockSyncRecord {
9 pub culistid: u64,
11 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#[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 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#[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}