Skip to main content

reifydb_runtime/context/clock/
native.rs

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