1use std::{
19 cell::{Ref, RefCell, RefMut},
20 fmt::Debug,
21 time::Duration,
22};
23
24use jiff::Timestamp;
25use nautilus_core::{
26 DurationNanos, UnixNanos,
27 datetime::{NANOSECONDS_IN_SECOND, try_datetime_to_unix_nanos},
28};
29use ustr::Ustr;
30
31use super::{Clock, duration_to_nanos};
32use crate::{component::ComponentAccessError, timer::TimeEventCallback};
33
34#[derive(Debug)]
39pub struct ClockApi<'a> {
40 backing: ClockApiBacking<'a>,
41}
42
43impl<'a> ClockApi<'a> {
44 pub(crate) fn new(clock: &'a RefCell<dyn Clock>) -> Self {
45 Self {
46 backing: ClockApiBacking::Native(clock),
47 }
48 }
49
50 #[doc(hidden)]
56 #[must_use]
57 #[expect(
58 clippy::too_many_arguments,
59 reason = "clock API backing mirrors the full ClockApi surface"
60 )]
61 pub fn from_handlers<
62 TimestampNs,
63 SetTimeAlertNs,
64 SetTimerNs,
65 TimerNames,
66 TimerCount,
67 TimerExists,
68 NextTimeNs,
69 CancelTimer,
70 CancelTimers,
71 >(
72 timestamp_ns: TimestampNs,
73 set_time_alert_ns: SetTimeAlertNs,
74 set_timer_ns: SetTimerNs,
75 timer_names: TimerNames,
76 timer_count: TimerCount,
77 timer_exists: TimerExists,
78 next_time_ns: NextTimeNs,
79 cancel_timer: CancelTimer,
80 cancel_timers: CancelTimers,
81 ) -> Self
82 where
83 TimestampNs: Fn() -> UnixNanos + 'a,
84 SetTimeAlertNs:
85 Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a,
86 SetTimerNs: Fn(
87 &str,
88 DurationNanos,
89 Option<UnixNanos>,
90 Option<UnixNanos>,
91 Option<TimeEventCallback>,
92 Option<bool>,
93 Option<bool>,
94 ) -> anyhow::Result<()>
95 + 'a,
96 TimerNames: Fn() -> Vec<String> + 'a,
97 TimerCount: Fn() -> usize + 'a,
98 TimerExists: Fn(&str) -> bool + 'a,
99 NextTimeNs: Fn(&str) -> Option<UnixNanos> + 'a,
100 CancelTimer: Fn(&str) + 'a,
101 CancelTimers: Fn() + 'a,
102 {
103 Self {
104 backing: ClockApiBacking::Handlers(ClockApiHandlers {
105 timestamp_ns: Box::new(timestamp_ns),
106 set_time_alert_ns: Box::new(set_time_alert_ns),
107 set_timer_ns: Box::new(set_timer_ns),
108 timer_names: Box::new(timer_names),
109 timer_count: Box::new(timer_count),
110 timer_exists: Box::new(timer_exists),
111 next_time_ns: Box::new(next_time_ns),
112 cancel_timer: Box::new(cancel_timer),
113 cancel_timers: Box::new(cancel_timers),
114 }),
115 }
116 }
117
118 #[must_use]
124 pub fn timestamp_ns(&self) -> UnixNanos {
125 match &self.backing {
126 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_ns").timestamp_ns(),
127 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)(),
128 }
129 }
130
131 #[must_use]
137 pub fn timestamp_us(&self) -> u64 {
138 match &self.backing {
139 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_us").timestamp_us(),
140 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_micros(),
141 }
142 }
143
144 #[must_use]
150 pub fn timestamp_ms(&self) -> u64 {
151 match &self.backing {
152 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_ms").timestamp_ms(),
153 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_millis(),
154 }
155 }
156
157 #[must_use]
163 pub fn timestamp(&self) -> f64 {
164 match &self.backing {
165 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp").timestamp(),
166 ClockApiBacking::Handlers(handlers) => {
167 (handlers.timestamp_ns)().as_f64() / (NANOSECONDS_IN_SECOND as f64)
168 }
169 }
170 }
171
172 #[must_use]
178 pub fn utc_now(&self) -> Timestamp {
179 match &self.backing {
180 ClockApiBacking::Native(clock) => clock_ref(clock, "utc_now").utc_now(),
181 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().to_datetime_utc(),
182 }
183 }
184
185 pub fn set_time_alert(
196 &self,
197 name: &str,
198 alert_time: Timestamp,
199 callback: Option<TimeEventCallback>,
200 allow_past: Option<bool>,
201 ) -> anyhow::Result<()> {
202 match &self.backing {
203 ClockApiBacking::Native(clock) => clock_mut(clock, "set_time_alert")?
204 .set_time_alert(name, alert_time, callback, allow_past),
205 ClockApiBacking::Handlers(handlers) => (handlers.set_time_alert_ns)(
206 name,
207 try_datetime_to_unix_nanos(alert_time)?,
208 callback,
209 allow_past,
210 ),
211 }
212 }
213
214 pub fn set_time_alert_ns(
224 &self,
225 name: &str,
226 alert_time_ns: UnixNanos,
227 callback: Option<TimeEventCallback>,
228 allow_past: Option<bool>,
229 ) -> anyhow::Result<()> {
230 match &self.backing {
231 ClockApiBacking::Native(clock) => clock_mut(clock, "set_time_alert_ns")?
232 .set_time_alert_ns(name, alert_time_ns, callback, allow_past),
233 ClockApiBacking::Handlers(handlers) => {
234 (handlers.set_time_alert_ns)(name, alert_time_ns, callback, allow_past)
235 }
236 }
237 }
238
239 #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
250 pub fn set_timer(
251 &self,
252 name: &str,
253 interval: Duration,
254 start_time: Option<Timestamp>,
255 stop_time: Option<Timestamp>,
256 callback: Option<TimeEventCallback>,
257 allow_past: Option<bool>,
258 fire_immediately: Option<bool>,
259 ) -> anyhow::Result<()> {
260 match &self.backing {
261 ClockApiBacking::Native(clock) => clock_mut(clock, "set_timer")?.set_timer(
262 name,
263 interval,
264 start_time,
265 stop_time,
266 callback,
267 allow_past,
268 fire_immediately,
269 ),
270 ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
271 name,
272 duration_to_nanos(interval)?,
273 start_time.map(try_datetime_to_unix_nanos).transpose()?,
274 stop_time.map(try_datetime_to_unix_nanos).transpose()?,
275 callback,
276 allow_past,
277 fire_immediately,
278 ),
279 }
280 }
281
282 #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
292 pub fn set_timer_ns(
293 &self,
294 name: &str,
295 interval_ns: DurationNanos,
296 start_time_ns: Option<UnixNanos>,
297 stop_time_ns: Option<UnixNanos>,
298 callback: Option<TimeEventCallback>,
299 allow_past: Option<bool>,
300 fire_immediately: Option<bool>,
301 ) -> anyhow::Result<()> {
302 match &self.backing {
303 ClockApiBacking::Native(clock) => clock_mut(clock, "set_timer_ns")?.set_timer_ns(
304 name,
305 interval_ns,
306 start_time_ns,
307 stop_time_ns,
308 callback,
309 allow_past,
310 fire_immediately,
311 ),
312 ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
313 name,
314 interval_ns,
315 start_time_ns,
316 stop_time_ns,
317 callback,
318 allow_past,
319 fire_immediately,
320 ),
321 }
322 }
323
324 #[must_use]
330 pub fn timer_names(&self) -> Vec<String> {
331 match &self.backing {
332 ClockApiBacking::Native(clock) => clock_ref(clock, "timer_names")
333 .timer_names()
334 .into_iter()
335 .map(str::to_string)
336 .collect(),
337 ClockApiBacking::Handlers(handlers) => (handlers.timer_names)(),
338 }
339 }
340
341 #[must_use]
347 pub fn timer_count(&self) -> usize {
348 match &self.backing {
349 ClockApiBacking::Native(clock) => clock_ref(clock, "timer_count").timer_count(),
350 ClockApiBacking::Handlers(handlers) => (handlers.timer_count)(),
351 }
352 }
353
354 #[must_use]
360 pub fn timer_exists(&self, name: &str) -> bool {
361 match &self.backing {
362 ClockApiBacking::Native(clock) => {
363 clock_ref(clock, "timer_exists").timer_exists(&Ustr::from(name))
364 }
365 ClockApiBacking::Handlers(handlers) => (handlers.timer_exists)(name),
366 }
367 }
368
369 #[must_use]
377 pub fn next_time_ns(&self, name: &str) -> Option<UnixNanos> {
378 match &self.backing {
379 ClockApiBacking::Native(clock) => clock_ref(clock, "next_time_ns").next_time_ns(name),
380 ClockApiBacking::Handlers(handlers) => (handlers.next_time_ns)(name),
381 }
382 }
383
384 pub fn cancel_timer(&self, name: &str) {
390 match &self.backing {
391 ClockApiBacking::Native(clock) => clock_mut(clock, "cancel_timer")
392 .unwrap_or_else(|e| panic!("{e}"))
393 .cancel_timer(name),
394 ClockApiBacking::Handlers(handlers) => (handlers.cancel_timer)(name),
395 }
396 }
397
398 pub fn cancel_timers(&self) {
404 match &self.backing {
405 ClockApiBacking::Native(clock) => clock_mut(clock, "cancel_timers")
406 .unwrap_or_else(|e| panic!("{e}"))
407 .cancel_timers(),
408 ClockApiBacking::Handlers(handlers) => (handlers.cancel_timers)(),
409 }
410 }
411}
412
413enum ClockApiBacking<'a> {
414 Native(&'a RefCell<dyn Clock>),
415 Handlers(ClockApiHandlers<'a>),
416}
417
418struct ClockApiHandlers<'a> {
419 timestamp_ns: Box<dyn Fn() -> UnixNanos + 'a>,
420 set_time_alert_ns: Box<SetTimeAlertNsHandler<'a>>,
421 set_timer_ns: Box<SetTimerNsHandler<'a>>,
422 timer_names: Box<dyn Fn() -> Vec<String> + 'a>,
423 timer_count: Box<dyn Fn() -> usize + 'a>,
424 timer_exists: Box<dyn Fn(&str) -> bool + 'a>,
425 next_time_ns: Box<NextTimeNsHandler<'a>>,
426 cancel_timer: Box<dyn Fn(&str) + 'a>,
427 cancel_timers: Box<dyn Fn() + 'a>,
428}
429
430impl Debug for ClockApiBacking<'_> {
431 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432 match self {
433 Self::Native(_) => f.write_str("Native"),
434 Self::Handlers(_) => f.write_str("Handlers"),
435 }
436 }
437}
438
439type SetTimeAlertNsHandler<'a> =
440 dyn Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a;
441type NextTimeNsHandler<'a> = dyn Fn(&str) -> Option<UnixNanos> + 'a;
442type SetTimerNsHandler<'a> = dyn Fn(
443 &str,
444 DurationNanos,
445 Option<UnixNanos>,
446 Option<UnixNanos>,
447 Option<TimeEventCallback>,
448 Option<bool>,
449 Option<bool>,
450 ) -> anyhow::Result<()>
451 + 'a;
452
453fn clock_ref<'a>(clock: &'a RefCell<dyn Clock>, operation: &'static str) -> Ref<'a, dyn Clock> {
454 clock.try_borrow().unwrap_or_else(|_| {
455 panic!(
456 "{}",
457 ComponentAccessError::ReadConflict {
458 resource: "clock",
459 operation,
460 }
461 )
462 })
463}
464
465fn clock_mut<'a>(
466 clock: &'a RefCell<dyn Clock>,
467 operation: &'static str,
468) -> Result<RefMut<'a, dyn Clock>, ComponentAccessError> {
469 clock
470 .try_borrow_mut()
471 .map_err(|_| ComponentAccessError::WriteConflict {
472 resource: "clock",
473 operation,
474 })
475}