Skip to main content

reifydb_runtime/context/clock/
native.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	cmp, fmt, ops,
6	sync::{
7		Arc,
8		atomic::{AtomicU64, Ordering},
9	},
10	time,
11	time::{Duration, SystemTime, UNIX_EPOCH},
12};
13
14#[allow(clippy::disallowed_methods)]
15#[inline(always)]
16fn platform_now_nanos() -> u64 {
17	SystemTime::now().duration_since(UNIX_EPOCH).expect("System time is before Unix epoch").as_nanos() as u64
18}
19
20#[derive(Clone)]
21pub enum Clock {
22	Real,
23
24	Mock(MockClock),
25}
26
27impl Clock {
28	pub fn now_nanos(&self) -> u64 {
29		match self {
30			Clock::Real => platform_now_nanos(),
31			Clock::Mock(mock) => mock.now_nanos(),
32		}
33	}
34
35	pub fn now_micros(&self) -> u64 {
36		self.now_nanos() / 1_000
37	}
38
39	pub fn now_millis(&self) -> u64 {
40		self.now_nanos() / 1_000_000
41	}
42
43	pub fn now_secs(&self) -> u64 {
44		self.now_nanos() / 1_000_000_000
45	}
46
47	#[allow(clippy::disallowed_methods)]
48	pub fn instant(&self) -> Instant {
49		match self {
50			Clock::Real => Instant {
51				inner: InstantInner::Real(time::Instant::now()),
52			},
53			Clock::Mock(mock) => Instant {
54				inner: InstantInner::Mock {
55					captured_nanos: mock.now_nanos(),
56					clock: mock.clone(),
57				},
58			},
59		}
60	}
61}
62
63#[derive(Clone)]
64pub struct MockClock {
65	inner: Arc<MockClockInner>,
66}
67
68struct MockClockInner {
69	time_nanos: AtomicU64,
70}
71
72impl MockClock {
73	pub fn new(initial_nanos: u64) -> Self {
74		Self {
75			inner: Arc::new(MockClockInner {
76				time_nanos: AtomicU64::new(initial_nanos),
77			}),
78		}
79	}
80
81	pub fn from_millis(millis: u64) -> Self {
82		Self::new(millis * 1_000_000)
83	}
84
85	pub fn now_nanos(&self) -> u64 {
86		self.inner.time_nanos.load(Ordering::Acquire)
87	}
88
89	pub fn now_micros(&self) -> u64 {
90		self.now_nanos() / 1_000
91	}
92
93	pub fn now_millis(&self) -> u64 {
94		self.now_nanos() / 1_000_000
95	}
96
97	pub fn now_secs(&self) -> u64 {
98		self.now_nanos() / 1_000_000_000
99	}
100
101	pub fn set_nanos(&self, nanos: u64) {
102		self.inner.time_nanos.store(nanos, Ordering::Release);
103	}
104
105	pub fn set_micros(&self, micros: u64) {
106		self.set_nanos(micros * 1_000);
107	}
108
109	pub fn set_millis(&self, millis: u64) {
110		self.set_nanos(millis * 1_000_000);
111	}
112
113	pub fn advance_nanos(&self, nanos: u64) {
114		self.set_nanos(self.now_nanos().saturating_add(nanos));
115	}
116
117	pub fn advance_micros(&self, micros: u64) {
118		self.advance_nanos(micros * 1_000);
119	}
120
121	pub fn advance_millis(&self, millis: u64) {
122		self.advance_nanos(millis * 1_000_000);
123	}
124
125	pub fn advance_secs(&self, secs: u64) {
126		self.advance_nanos(secs * 1_000_000_000);
127	}
128
129	pub fn advance_minutes(&self, minutes: u64) {
130		self.advance_secs(minutes * 60);
131	}
132
133	pub fn advance_hours(&self, hours: u64) {
134		self.advance_secs(hours * 3600);
135	}
136
137	pub fn advance_days(&self, days: u64) {
138		self.advance_secs(days * 86400);
139	}
140}
141
142#[derive(Clone)]
143enum InstantInner {
144	Real(time::Instant),
145	Mock {
146		captured_nanos: u64,
147		clock: MockClock,
148	},
149}
150
151#[derive(Clone)]
152pub struct Instant {
153	inner: InstantInner,
154}
155
156impl Instant {
157	#[inline]
158	pub fn elapsed(&self) -> Duration {
159		match &self.inner {
160			InstantInner::Real(instant) => instant.elapsed(),
161			InstantInner::Mock {
162				captured_nanos,
163				clock,
164			} => {
165				let now = clock.now_nanos();
166				let elapsed_nanos = now.saturating_sub(*captured_nanos);
167				Duration::from_nanos(elapsed_nanos)
168			}
169		}
170	}
171
172	#[inline]
173	pub fn duration_since(&self, earlier: &Instant) -> Duration {
174		match (&self.inner, &earlier.inner) {
175			(InstantInner::Real(this), InstantInner::Real(other)) => this.duration_since(*other),
176			(
177				InstantInner::Mock {
178					captured_nanos: this_nanos,
179					..
180				},
181				InstantInner::Mock {
182					captured_nanos: other_nanos,
183					..
184				},
185			) => {
186				let elapsed = this_nanos.saturating_sub(*other_nanos);
187				Duration::from_nanos(elapsed)
188			}
189			_ => panic!("Cannot compare instants from different clock types"),
190		}
191	}
192}
193
194impl PartialEq for Instant {
195	fn eq(&self, other: &Self) -> bool {
196		match (&self.inner, &other.inner) {
197			(InstantInner::Real(a), InstantInner::Real(b)) => a == b,
198			(
199				InstantInner::Mock {
200					captured_nanos: a,
201					..
202				},
203				InstantInner::Mock {
204					captured_nanos: b,
205					..
206				},
207			) => a == b,
208			_ => panic!("Cannot compare instants from different clock types"),
209		}
210	}
211}
212
213impl Eq for Instant {}
214
215impl PartialOrd for Instant {
216	fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
217		Some(self.cmp(other))
218	}
219}
220
221impl Ord for Instant {
222	fn cmp(&self, other: &Self) -> cmp::Ordering {
223		match (&self.inner, &other.inner) {
224			(InstantInner::Real(a), InstantInner::Real(b)) => a.cmp(b),
225			(
226				InstantInner::Mock {
227					captured_nanos: a,
228					..
229				},
230				InstantInner::Mock {
231					captured_nanos: b,
232					..
233				},
234			) => a.cmp(b),
235			_ => panic!("Cannot compare instants from different clock types"),
236		}
237	}
238}
239
240impl ops::Add<Duration> for Instant {
241	type Output = Instant;
242
243	fn add(self, duration: Duration) -> Instant {
244		match self.inner {
245			InstantInner::Real(instant) => Instant {
246				inner: InstantInner::Real(instant + duration),
247			},
248			InstantInner::Mock {
249				captured_nanos,
250				clock,
251			} => Instant {
252				inner: InstantInner::Mock {
253					captured_nanos: captured_nanos.saturating_add(duration.as_nanos() as u64),
254					clock,
255				},
256			},
257		}
258	}
259}
260
261impl ops::Sub for &Instant {
262	type Output = Duration;
263
264	fn sub(self, other: &Instant) -> Duration {
265		self.duration_since(other)
266	}
267}
268
269impl fmt::Debug for Instant {
270	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271		match &self.inner {
272			InstantInner::Real(instant) => f.debug_tuple("Instant::Real").field(instant).finish(),
273			InstantInner::Mock {
274				captured_nanos,
275				..
276			} => f.debug_tuple("Instant::Mock").field(captured_nanos).finish(),
277		}
278	}
279}