1use std::{
19 cell::{Ref, RefCell, RefMut},
20 fmt::Debug,
21 sync::Arc,
22 time::Duration,
23};
24
25use jiff::{Span, Timestamp, Zoned};
26use nautilus_core::{
27 DurationNanos, UnixNanos,
28 datetime::{NANOSECONDS_IN_SECOND, try_datetime_to_unix_nanos},
29};
30use ustr::Ustr;
31
32use super::{Clock, duration_to_nanos};
33use crate::{
34 calendar::CalendarSchedule,
35 component::ComponentAccessError,
36 timer::{TimeEventCallback, TimerSchedule},
37};
38
39#[derive(Debug)]
44pub struct ClockApi<'a> {
45 backing: ClockApiBacking<'a>,
46}
47
48impl<'a> ClockApi<'a> {
49 pub(crate) fn new(clock: &'a RefCell<dyn Clock>) -> Self {
50 Self {
51 backing: ClockApiBacking::Native(clock),
52 }
53 }
54
55 #[doc(hidden)]
61 #[must_use]
62 #[expect(
63 clippy::too_many_arguments,
64 reason = "clock API backing mirrors the full ClockApi surface"
65 )]
66 pub fn from_handlers<
67 TimestampNs,
68 SetTimeAlertNs,
69 SetTimerNs,
70 TimerNames,
71 TimerCount,
72 TimerExists,
73 NextTimeNs,
74 CancelTimer,
75 CancelTimers,
76 >(
77 timestamp_ns: TimestampNs,
78 set_time_alert_ns: SetTimeAlertNs,
79 set_timer_ns: SetTimerNs,
80 timer_names: TimerNames,
81 timer_count: TimerCount,
82 timer_exists: TimerExists,
83 next_time_ns: NextTimeNs,
84 cancel_timer: CancelTimer,
85 cancel_timers: CancelTimers,
86 ) -> Self
87 where
88 TimestampNs: Fn() -> UnixNanos + 'a,
89 SetTimeAlertNs:
90 Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a,
91 SetTimerNs: Fn(
92 &str,
93 DurationNanos,
94 Option<UnixNanos>,
95 Option<UnixNanos>,
96 Option<TimeEventCallback>,
97 Option<bool>,
98 Option<bool>,
99 ) -> anyhow::Result<()>
100 + 'a,
101 TimerNames: Fn() -> Vec<String> + 'a,
102 TimerCount: Fn() -> usize + 'a,
103 TimerExists: Fn(&str) -> bool + 'a,
104 NextTimeNs: Fn(&str) -> Option<UnixNanos> + 'a,
105 CancelTimer: Fn(&str) + 'a,
106 CancelTimers: Fn() + 'a,
107 {
108 Self {
109 backing: ClockApiBacking::Handlers(ClockApiHandlers {
110 timestamp_ns: Box::new(timestamp_ns),
111 set_time_alert_ns: Box::new(set_time_alert_ns),
112 set_timer_ns: Box::new(set_timer_ns),
113 timer_names: Box::new(timer_names),
114 timer_count: Box::new(timer_count),
115 timer_exists: Box::new(timer_exists),
116 next_time_ns: Box::new(next_time_ns),
117 cancel_timer: Box::new(cancel_timer),
118 cancel_timers: Box::new(cancel_timers),
119 }),
120 }
121 }
122
123 #[must_use]
129 pub fn timestamp_ns(&self) -> UnixNanos {
130 match &self.backing {
131 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_ns").timestamp_ns(),
132 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)(),
133 }
134 }
135
136 #[must_use]
142 pub fn timestamp_us(&self) -> u64 {
143 match &self.backing {
144 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_us").timestamp_us(),
145 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_micros(),
146 }
147 }
148
149 #[must_use]
155 pub fn timestamp_ms(&self) -> u64 {
156 match &self.backing {
157 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp_ms").timestamp_ms(),
158 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().as_millis(),
159 }
160 }
161
162 #[must_use]
168 pub fn timestamp(&self) -> f64 {
169 match &self.backing {
170 ClockApiBacking::Native(clock) => clock_ref(clock, "timestamp").timestamp(),
171 ClockApiBacking::Handlers(handlers) => {
172 (handlers.timestamp_ns)().as_f64() / (NANOSECONDS_IN_SECOND as f64)
173 }
174 }
175 }
176
177 #[must_use]
183 pub fn utc_now(&self) -> Timestamp {
184 match &self.backing {
185 ClockApiBacking::Native(clock) => clock_ref(clock, "utc_now").utc_now(),
186 ClockApiBacking::Handlers(handlers) => (handlers.timestamp_ns)().to_datetime_utc(),
187 }
188 }
189
190 pub fn set_time_alert(
201 &self,
202 name: &str,
203 alert_time: Timestamp,
204 callback: Option<TimeEventCallback>,
205 allow_past: Option<bool>,
206 ) -> anyhow::Result<()> {
207 match &self.backing {
208 ClockApiBacking::Native(clock) => clock_mut(clock, "set_time_alert")?
209 .set_time_alert(name, alert_time, callback, allow_past),
210 ClockApiBacking::Handlers(handlers) => (handlers.set_time_alert_ns)(
211 name,
212 try_datetime_to_unix_nanos(alert_time)?,
213 callback,
214 allow_past,
215 ),
216 }
217 }
218
219 pub fn set_time_alert_ns(
229 &self,
230 name: &str,
231 alert_time_ns: UnixNanos,
232 callback: Option<TimeEventCallback>,
233 allow_past: Option<bool>,
234 ) -> anyhow::Result<()> {
235 match &self.backing {
236 ClockApiBacking::Native(clock) => clock_mut(clock, "set_time_alert_ns")?
237 .set_time_alert_ns(name, alert_time_ns, callback, allow_past),
238 ClockApiBacking::Handlers(handlers) => {
239 (handlers.set_time_alert_ns)(name, alert_time_ns, callback, allow_past)
240 }
241 }
242 }
243
244 #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
255 pub fn set_timer(
256 &self,
257 name: &str,
258 interval: Duration,
259 start_time: Option<Timestamp>,
260 stop_time: Option<Timestamp>,
261 callback: Option<TimeEventCallback>,
262 allow_past: Option<bool>,
263 fire_immediately: Option<bool>,
264 ) -> anyhow::Result<()> {
265 match &self.backing {
266 ClockApiBacking::Native(clock) => clock_mut(clock, "set_timer")?.set_timer(
267 name,
268 interval,
269 start_time,
270 stop_time,
271 callback,
272 allow_past,
273 fire_immediately,
274 ),
275 ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
276 name,
277 duration_to_nanos(interval)?,
278 start_time.map(try_datetime_to_unix_nanos).transpose()?,
279 stop_time.map(try_datetime_to_unix_nanos).transpose()?,
280 callback,
281 allow_past,
282 fire_immediately,
283 ),
284 }
285 }
286
287 #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
297 pub fn set_timer_ns(
298 &self,
299 name: &str,
300 interval_ns: DurationNanos,
301 start_time_ns: Option<UnixNanos>,
302 stop_time_ns: Option<UnixNanos>,
303 callback: Option<TimeEventCallback>,
304 allow_past: Option<bool>,
305 fire_immediately: Option<bool>,
306 ) -> anyhow::Result<()> {
307 match &self.backing {
308 ClockApiBacking::Native(clock) => clock_mut(clock, "set_timer_ns")?.set_timer_ns(
309 name,
310 interval_ns,
311 start_time_ns,
312 stop_time_ns,
313 callback,
314 allow_past,
315 fire_immediately,
316 ),
317 ClockApiBacking::Handlers(handlers) => (handlers.set_timer_ns)(
318 name,
319 interval_ns,
320 start_time_ns,
321 stop_time_ns,
322 callback,
323 allow_past,
324 fire_immediately,
325 ),
326 }
327 }
328
329 pub fn set_timer_schedule(
339 &self,
340 name: &str,
341 schedule: Arc<dyn TimerSchedule>,
342 stop_time_ns: Option<UnixNanos>,
343 callback: Option<TimeEventCallback>,
344 allow_past: Option<bool>,
345 fire_immediately: Option<bool>,
346 ) -> anyhow::Result<()> {
347 match &self.backing {
348 ClockApiBacking::Native(clock) => clock_mut(clock, "set_timer_schedule")?
349 .set_timer_schedule(
350 name,
351 schedule,
352 stop_time_ns,
353 callback,
354 allow_past,
355 fire_immediately,
356 ),
357 ClockApiBacking::Handlers(_) => {
358 anyhow::bail!("Timer '{name}' needs a native clock for a deadline schedule")
359 }
360 }
361 }
362
363 #[expect(clippy::too_many_arguments, reason = "timer scheduling mirrors Clock")]
377 pub fn set_timer_zoned(
378 &self,
379 name: &str,
380 interval: Span,
381 start_time: &Zoned,
382 stop_time: Option<Timestamp>,
383 callback: Option<TimeEventCallback>,
384 allow_past: Option<bool>,
385 fire_immediately: Option<bool>,
386 ) -> anyhow::Result<()> {
387 let schedule = CalendarSchedule::new(start_time, interval)?;
388 self.set_timer_schedule(
389 name,
390 Arc::new(schedule),
391 stop_time.map(try_datetime_to_unix_nanos).transpose()?,
392 callback,
393 allow_past,
394 fire_immediately,
395 )
396 }
397
398 #[must_use]
404 pub fn timer_names(&self) -> Vec<String> {
405 match &self.backing {
406 ClockApiBacking::Native(clock) => clock_ref(clock, "timer_names")
407 .timer_names()
408 .into_iter()
409 .map(str::to_string)
410 .collect(),
411 ClockApiBacking::Handlers(handlers) => (handlers.timer_names)(),
412 }
413 }
414
415 #[must_use]
421 pub fn timer_count(&self) -> usize {
422 match &self.backing {
423 ClockApiBacking::Native(clock) => clock_ref(clock, "timer_count").timer_count(),
424 ClockApiBacking::Handlers(handlers) => (handlers.timer_count)(),
425 }
426 }
427
428 #[must_use]
434 pub fn timer_exists(&self, name: &str) -> bool {
435 match &self.backing {
436 ClockApiBacking::Native(clock) => {
437 clock_ref(clock, "timer_exists").timer_exists(&Ustr::from(name))
438 }
439 ClockApiBacking::Handlers(handlers) => (handlers.timer_exists)(name),
440 }
441 }
442
443 #[must_use]
451 pub fn next_time_ns(&self, name: &str) -> Option<UnixNanos> {
452 match &self.backing {
453 ClockApiBacking::Native(clock) => clock_ref(clock, "next_time_ns").next_time_ns(name),
454 ClockApiBacking::Handlers(handlers) => (handlers.next_time_ns)(name),
455 }
456 }
457
458 pub fn cancel_timer(&self, name: &str) {
464 match &self.backing {
465 ClockApiBacking::Native(clock) => clock_mut(clock, "cancel_timer")
466 .unwrap_or_else(|e| panic!("{e}"))
467 .cancel_timer(name),
468 ClockApiBacking::Handlers(handlers) => (handlers.cancel_timer)(name),
469 }
470 }
471
472 pub fn cancel_timers(&self) {
478 match &self.backing {
479 ClockApiBacking::Native(clock) => clock_mut(clock, "cancel_timers")
480 .unwrap_or_else(|e| panic!("{e}"))
481 .cancel_timers(),
482 ClockApiBacking::Handlers(handlers) => (handlers.cancel_timers)(),
483 }
484 }
485}
486
487enum ClockApiBacking<'a> {
488 Native(&'a RefCell<dyn Clock>),
489 Handlers(ClockApiHandlers<'a>),
490}
491
492struct ClockApiHandlers<'a> {
493 timestamp_ns: Box<dyn Fn() -> UnixNanos + 'a>,
494 set_time_alert_ns: Box<SetTimeAlertNsHandler<'a>>,
495 set_timer_ns: Box<SetTimerNsHandler<'a>>,
496 timer_names: Box<dyn Fn() -> Vec<String> + 'a>,
497 timer_count: Box<dyn Fn() -> usize + 'a>,
498 timer_exists: Box<dyn Fn(&str) -> bool + 'a>,
499 next_time_ns: Box<NextTimeNsHandler<'a>>,
500 cancel_timer: Box<dyn Fn(&str) + 'a>,
501 cancel_timers: Box<dyn Fn() + 'a>,
502}
503
504impl Debug for ClockApiBacking<'_> {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 match self {
507 Self::Native(_) => f.write_str("Native"),
508 Self::Handlers(_) => f.write_str("Handlers"),
509 }
510 }
511}
512
513type SetTimeAlertNsHandler<'a> =
514 dyn Fn(&str, UnixNanos, Option<TimeEventCallback>, Option<bool>) -> anyhow::Result<()> + 'a;
515type NextTimeNsHandler<'a> = dyn Fn(&str) -> Option<UnixNanos> + 'a;
516type SetTimerNsHandler<'a> = dyn Fn(
517 &str,
518 DurationNanos,
519 Option<UnixNanos>,
520 Option<UnixNanos>,
521 Option<TimeEventCallback>,
522 Option<bool>,
523 Option<bool>,
524 ) -> anyhow::Result<()>
525 + 'a;
526
527fn clock_ref<'a>(clock: &'a RefCell<dyn Clock>, operation: &'static str) -> Ref<'a, dyn Clock> {
528 clock.try_borrow().unwrap_or_else(|_| {
529 panic!(
530 "{}",
531 ComponentAccessError::ReadConflict {
532 resource: "clock",
533 operation,
534 }
535 )
536 })
537}
538
539fn clock_mut<'a>(
540 clock: &'a RefCell<dyn Clock>,
541 operation: &'static str,
542) -> Result<RefMut<'a, dyn Clock>, ComponentAccessError> {
543 clock
544 .try_borrow_mut()
545 .map_err(|_| ComponentAccessError::WriteConflict {
546 resource: "clock",
547 operation,
548 })
549}