qubit_clock/wall/manual_wall_clock.rs
1// =============================================================================
2// Copyright (c) 2025 - 2026 Haixing Hu.
3//
4// SPDX-License-Identifier: Apache-2.0
5//
6// Licensed under the Apache License, Version 2.0.
7// =============================================================================
8//! Defines a manually re-anchorable wall-clock projection.
9
10use crate::{
11 ManualMonotonicClock,
12 MonotonicClock,
13 MonotonicInstant,
14 WallClock,
15};
16use std::sync::{
17 Arc,
18 Mutex,
19 MutexGuard,
20};
21use std::time::SystemTime;
22
23/// A wall clock projected from a shared [`ManualMonotonicClock`].
24///
25/// Advancing the monotonic clock advances this wall clock by the same
26/// duration. Calling [`reanchor()`](Self::reanchor) changes only the wall-time
27/// mapping and never changes the underlying monotonic clock.
28#[derive(Debug)]
29pub struct ManualWallClock {
30 /// Shared monotonic timeline used to project elapsed wall time.
31 clock: Arc<ManualMonotonicClock>,
32 /// Wall time and monotonic instant that define the current projection.
33 anchor: Mutex<(SystemTime, MonotonicInstant)>,
34}
35
36impl ManualWallClock {
37 /// Creates a wall clock whose current reading is `wall_time`.
38 ///
39 /// Future readings advance according to the explicitly shared `clock`.
40 ///
41 /// # Parameters
42 ///
43 /// * `wall_time` - Wall-clock value assigned to the current manual instant.
44 /// * `clock` - Shared manual monotonic timeline driving future readings.
45 ///
46 /// # Returns
47 ///
48 /// A wall clock anchored to the supplied wall and monotonic times.
49 #[must_use]
50 #[inline]
51 pub fn from_clock(
52 wall_time: SystemTime,
53 clock: Arc<ManualMonotonicClock>,
54 ) -> Self {
55 let monotonic_anchor = clock.now();
56 Self {
57 clock,
58 anchor: Mutex::new((wall_time, monotonic_anchor)),
59 }
60 }
61
62 /// Reassigns the current monotonic instant to `wall_time`.
63 ///
64 /// This operation may move wall time forward or backward. It does not
65 /// advance the monotonic clock and does not wake monotonic sleepers. The
66 /// anchor mutex remains held while the monotonic clock is sampled, so
67 /// concurrent calls to [`now()`](WallClock::now) observe either the old or
68 /// the new mapping without combining both snapshots.
69 ///
70 /// # Parameters
71 ///
72 /// * `wall_time` - Replacement wall time for the current monotonic instant.
73 #[inline]
74 pub fn reanchor(&self, wall_time: SystemTime) {
75 let mut anchor = self.lock_anchor();
76 let monotonic_anchor = self.clock.now();
77 *anchor = (wall_time, monotonic_anchor);
78 }
79
80 /// Locks the wall and monotonic anchor pair, recovering after poisoning.
81 ///
82 /// # Returns
83 ///
84 /// A guard granting mutable access to the anchor pair.
85 #[inline]
86 fn lock_anchor(&self) -> MutexGuard<'_, (SystemTime, MonotonicInstant)> {
87 self.anchor
88 .lock()
89 .unwrap_or_else(std::sync::PoisonError::into_inner)
90 }
91}
92
93impl WallClock for ManualWallClock {
94 /// Returns wall time derived from the anchor and current monotonic time.
95 ///
96 /// # Returns
97 ///
98 /// The anchored wall time plus elapsed manual monotonic time.
99 ///
100 /// # Panics
101 ///
102 /// Panics if the manually advanced duration cannot be represented by
103 /// [`SystemTime`]. Normal application and test durations are representable.
104 #[inline]
105 fn now(&self) -> SystemTime {
106 let anchor = self.lock_anchor();
107 let (wall_anchor, monotonic_anchor) = *anchor;
108 let monotonic_now = self.clock.now();
109 drop(anchor);
110 let elapsed = monotonic_now
111 .duration_since(monotonic_anchor)
112 .expect("manual wall clock must retain its monotonic clock domain");
113 wall_anchor
114 .checked_add(elapsed)
115 .expect("manual wall time exceeded SystemTime range")
116 }
117}