Skip to main content

reifydb_runtime/context/clock/
native.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4//! Native clock implementation.
5
6use std::{
7	fmt,
8	sync::{
9		Arc,
10		atomic::{AtomicU64, Ordering},
11	},
12	time,
13	time::{Duration, SystemTime, UNIX_EPOCH},
14};
15
16#[inline(always)]
17fn platform_now_nanos() -> u128 {
18	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos()
19}
20
21/// A clock that provides time - either real system time or mock time for testing.
22#[derive(Clone)]
23pub enum Clock {
24	/// Real system clock - delegates to platform time
25	Real,
26	/// Mock clock with controllable time
27	Mock(MockClock),
28}
29
30impl Clock {
31	/// Get current time in nanoseconds since Unix epoch
32	pub fn now_nanos(&self) -> u128 {
33		match self {
34			Clock::Real => platform_now_nanos(),
35			Clock::Mock(mock) => mock.now_nanos(),
36		}
37	}
38
39	/// Get current time in microseconds since Unix epoch
40	pub fn now_micros(&self) -> u64 {
41		(self.now_nanos() / 1_000) as u64
42	}
43
44	/// Get current time in milliseconds since Unix epoch
45	pub fn now_millis(&self) -> u64 {
46		(self.now_nanos() / 1_000_000) as u64
47	}
48
49	/// Get current time in seconds since Unix epoch
50	pub fn now_secs(&self) -> u64 {
51		(self.now_nanos() / 1_000_000_000) as u64
52	}
53
54	pub fn instant(&self) -> Instant {
55		match self {
56			Clock::Real => Instant {
57				inner: InstantInner::Real(time::Instant::now()),
58			},
59			Clock::Mock(mock) => Instant {
60				inner: InstantInner::Mock {
61					captured_nanos: mock.now_nanos(),
62					clock: mock.clone(),
63				},
64			},
65		}
66	}
67}
68
69impl Default for Clock {
70	fn default() -> Self {
71		Clock::Real
72	}
73}
74
75/// Mock clock with atomic time storage for thread-safe access.
76#[derive(Clone)]
77pub struct MockClock {
78	inner: Arc<MockClockInner>,
79}
80
81struct MockClockInner {
82	// Split u128 into two u64s for atomic access
83	time_high: AtomicU64,
84	time_low: AtomicU64,
85}
86
87impl MockClock {
88	/// Create a new mock clock starting at the given nanoseconds
89	pub fn new(initial_nanos: u128) -> Self {
90		Self {
91			inner: Arc::new(MockClockInner {
92				time_high: AtomicU64::new((initial_nanos >> 64) as u64),
93				time_low: AtomicU64::new(initial_nanos as u64),
94			}),
95		}
96	}
97
98	/// Create a new mock clock starting at the given milliseconds
99	pub fn from_millis(millis: u64) -> Self {
100		Self::new(millis as u128 * 1_000_000)
101	}
102
103	/// Get current time in nanoseconds
104	pub fn now_nanos(&self) -> u128 {
105		let high = self.inner.time_high.load(Ordering::Acquire) as u128;
106		let low = self.inner.time_low.load(Ordering::Acquire) as u128;
107		(high << 64) | low
108	}
109
110	/// Get current time in microseconds
111	pub fn now_micros(&self) -> u64 {
112		(self.now_nanos() / 1_000) as u64
113	}
114
115	/// Get current time in milliseconds
116	pub fn now_millis(&self) -> u64 {
117		(self.now_nanos() / 1_000_000) as u64
118	}
119
120	/// Get current time in seconds
121	pub fn now_secs(&self) -> u64 {
122		(self.now_nanos() / 1_000_000_000) as u64
123	}
124
125	/// Set time to specific nanoseconds
126	pub fn set_nanos(&self, nanos: u128) {
127		self.inner.time_high.store((nanos >> 64) as u64, Ordering::Release);
128		self.inner.time_low.store(nanos as u64, Ordering::Release);
129	}
130
131	/// Set time to specific microseconds
132	pub fn set_micros(&self, micros: u64) {
133		self.set_nanos(micros as u128 * 1_000);
134	}
135
136	/// Set time to specific milliseconds
137	pub fn set_millis(&self, millis: u64) {
138		self.set_nanos(millis as u128 * 1_000_000);
139	}
140
141	/// Advance time by nanoseconds
142	pub fn advance_nanos(&self, nanos: u128) {
143		self.set_nanos(self.now_nanos() + nanos);
144	}
145
146	/// Advance time by microseconds
147	pub fn advance_micros(&self, micros: u64) {
148		self.advance_nanos(micros as u128 * 1_000);
149	}
150
151	/// Advance time by milliseconds
152	pub fn advance_millis(&self, millis: u64) {
153		self.advance_nanos(millis as u128 * 1_000_000);
154	}
155
156	/// Advance time by seconds
157	pub fn advance_secs(&self, secs: u64) {
158		self.advance_nanos(secs as u128 * 1_000_000_000);
159	}
160
161	/// Advance time by minutes
162	pub fn advance_minutes(&self, minutes: u64) {
163		self.advance_secs(minutes * 60);
164	}
165
166	/// Advance time by hours
167	pub fn advance_hours(&self, hours: u64) {
168		self.advance_secs(hours * 3600);
169	}
170
171	/// Advance time by days
172	pub fn advance_days(&self, days: u64) {
173		self.advance_secs(days * 86400);
174	}
175}
176
177#[derive(Clone)]
178enum InstantInner {
179	Real(time::Instant),
180	Mock {
181		captured_nanos: u128,
182		clock: MockClock,
183	},
184}
185
186#[derive(Clone)]
187pub struct Instant {
188	inner: InstantInner,
189}
190
191impl Instant {
192	#[inline]
193	pub fn elapsed(&self) -> Duration {
194		match &self.inner {
195			InstantInner::Real(instant) => instant.elapsed(),
196			InstantInner::Mock {
197				captured_nanos,
198				clock,
199			} => {
200				let now = clock.now_nanos();
201				let elapsed_nanos = now.saturating_sub(*captured_nanos);
202				Duration::from_nanos(elapsed_nanos as u64)
203			}
204		}
205	}
206
207	#[inline]
208	pub fn duration_since(&self, earlier: Instant) -> Duration {
209		match (&self.inner, &earlier.inner) {
210			(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
211			(
212				InstantInner::Mock {
213					captured_nanos: this_nanos,
214					..
215				},
216				InstantInner::Mock {
217					captured_nanos: other_nanos,
218					..
219				},
220			) => {
221				let elapsed = this_nanos.saturating_sub(*other_nanos);
222				Duration::from_nanos(elapsed as u64)
223			}
224			_ => panic!("Cannot compare instants from different clock types"),
225		}
226	}
227}
228
229impl fmt::Debug for Instant {
230	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231		match &self.inner {
232			InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
233			InstantInner::Mock {
234				captured_nanos,
235				..
236			} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
237		}
238	}
239}