1use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
16
17use crate::World;
18use crate::driver::Installer;
19use crate::world::{ResourceId, WorldBuilder};
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23#[non_exhaustive]
24pub enum ConfigError {
25 Invalid(&'static str),
27}
28
29impl std::fmt::Display for ConfigError {
30 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31 match self {
32 Self::Invalid(msg) => write!(f, "clock configuration error: {msg}"),
33 }
34 }
35}
36
37impl std::error::Error for ConfigError {}
38
39#[derive(Debug, Clone, Copy)]
57pub struct Clock {
58 instant: Instant,
59 unix_nanos: i128,
60}
61
62impl crate::world::Resource for Clock {}
63
64impl Clock {
65 #[inline]
67 #[must_use]
68 pub fn instant(&self) -> Instant {
69 self.instant
70 }
71
72 #[inline]
74 #[must_use]
75 pub fn unix_nanos(&self) -> i128 {
76 self.unix_nanos
77 }
78}
79
80impl Default for Clock {
81 fn default() -> Self {
82 Self {
83 instant: Instant::now(),
84 unix_nanos: 0,
85 }
86 }
87}
88
89#[cfg(debug_assertions)]
94const DEFAULT_THRESHOLD: Duration = Duration::from_micros(1);
95#[cfg(not(debug_assertions))]
96const DEFAULT_THRESHOLD: Duration = Duration::from_nanos(250);
97
98#[cfg(debug_assertions)]
99const MIN_THRESHOLD: Duration = Duration::from_micros(1);
100#[cfg(not(debug_assertions))]
101const MIN_THRESHOLD: Duration = Duration::from_nanos(100);
102
103pub struct RealtimeClockInstaller {
124 threshold: Duration,
125 max_retries: u32,
126 resync_interval: Duration,
127}
128
129pub struct RealtimeClockInstallerBuilder {
131 threshold: Duration,
132 max_retries: u32,
133 resync_interval: Duration,
134}
135
136impl RealtimeClockInstaller {
137 #[must_use]
139 pub fn builder() -> RealtimeClockInstallerBuilder {
140 RealtimeClockInstallerBuilder::default()
141 }
142}
143
144impl Installer for RealtimeClockInstaller {
145 type Poller = RealtimeClockPoller;
146
147 fn install(self, world: &mut WorldBuilder) -> RealtimeClockPoller {
148 let clock_id = world.register(Clock::default());
149
150 let (base_instant, base_nanos, gap) =
151 RealtimeClockPoller::calibrate(self.threshold, self.max_retries);
152 let accurate = gap <= self.threshold;
153
154 RealtimeClockPoller {
155 clock_id,
156 base_instant,
157 base_nanos,
158 last_resync: base_instant,
159 resync_interval: self.resync_interval,
160 threshold: self.threshold,
161 max_retries: self.max_retries,
162 calibration_gap: gap,
163 accurate,
164 }
165 }
166}
167
168impl Default for RealtimeClockInstaller {
169 fn default() -> Self {
170 Self::builder()
171 .build()
172 .expect("default config is always valid")
173 }
174}
175
176impl std::fmt::Debug for RealtimeClockInstaller {
177 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178 f.debug_struct("RealtimeClockInstaller")
179 .field("threshold", &self.threshold)
180 .field("max_retries", &self.max_retries)
181 .field("resync_interval", &self.resync_interval)
182 .finish()
183 }
184}
185
186pub struct RealtimeClockPoller {
191 clock_id: ResourceId,
192 base_instant: Instant,
193 base_nanos: i128,
194 last_resync: Instant,
195 resync_interval: Duration,
196 threshold: Duration,
197 max_retries: u32,
198 calibration_gap: Duration,
199 accurate: bool,
200}
201
202impl RealtimeClockPoller {
203 #[inline]
205 pub fn sync(&mut self, world: &mut World, now: Instant) {
206 if now.saturating_duration_since(self.last_resync) >= self.resync_interval {
207 self.resync_at(now);
208 }
209
210 let elapsed = now.saturating_duration_since(self.base_instant);
211 let nanos = self.base_nanos + elapsed.as_nanos() as i128;
212
213 let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
215 clock.instant = now;
216 clock.unix_nanos = nanos;
217 }
218
219 pub fn resync(&mut self) {
221 self.resync_at(Instant::now());
222 }
223
224 #[inline]
226 #[must_use]
227 pub fn is_accurate(&self) -> bool {
228 self.accurate
229 }
230
231 #[inline]
233 #[must_use]
234 pub fn calibration_gap(&self) -> Duration {
235 self.calibration_gap
236 }
237
238 fn resync_at(&mut self, now: Instant) {
239 let (best_instant, base_nanos, gap) = Self::calibrate(self.threshold, self.max_retries);
240 let adjustment = now.saturating_duration_since(best_instant);
241 self.base_instant = now;
242 self.base_nanos = base_nanos + adjustment.as_nanos() as i128;
243 self.calibration_gap = gap;
244 self.accurate = gap <= self.threshold;
245 self.last_resync = now;
246 }
247
248 fn calibrate(threshold: Duration, max_retries: u32) -> (Instant, i128, Duration) {
249 let mut best_gap = Duration::MAX;
250 let mut best_instant = Instant::now();
251 let mut best_nanos = 0i128;
252
253 for _ in 0..max_retries {
254 let before = Instant::now();
255 let wall = SystemTime::now();
256 let after = Instant::now();
257
258 let gap = after.duration_since(before);
259 if gap < best_gap {
260 best_gap = gap;
261 best_instant = before + gap / 2;
262 best_nanos = match wall.duration_since(UNIX_EPOCH) {
263 Ok(d) => d.as_nanos() as i128,
264 Err(e) => -(e.duration().as_nanos() as i128),
265 };
266 }
267
268 if gap <= threshold {
269 break;
270 }
271 }
272
273 (best_instant, best_nanos, best_gap)
274 }
275}
276
277impl std::fmt::Debug for RealtimeClockPoller {
278 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279 f.debug_struct("RealtimeClockPoller")
280 .field("calibration_gap", &self.calibration_gap)
281 .field("accurate", &self.accurate)
282 .finish()
283 }
284}
285
286impl RealtimeClockInstallerBuilder {
289 #[must_use]
291 pub fn threshold(mut self, threshold: Duration) -> Self {
292 self.threshold = threshold.max(MIN_THRESHOLD);
293 self
294 }
295
296 #[must_use]
298 pub fn max_retries(mut self, n: u32) -> Self {
299 self.max_retries = n;
300 self
301 }
302
303 #[must_use]
305 pub fn resync_interval(mut self, interval: Duration) -> Self {
306 self.resync_interval = interval;
307 self
308 }
309
310 pub fn build(self) -> Result<RealtimeClockInstaller, ConfigError> {
316 if self.max_retries == 0 {
317 return Err(ConfigError::Invalid("max_retries must be > 0"));
318 }
319 Ok(RealtimeClockInstaller {
320 threshold: self.threshold,
321 max_retries: self.max_retries,
322 resync_interval: self.resync_interval,
323 })
324 }
325}
326
327impl Default for RealtimeClockInstallerBuilder {
328 fn default() -> Self {
329 Self {
330 threshold: DEFAULT_THRESHOLD,
331 max_retries: 20,
332 resync_interval: Duration::from_secs(3600),
333 }
334 }
335}
336
337#[derive(Debug)]
345pub struct TestClockInstaller {
346 base_nanos: i128,
347}
348
349impl TestClockInstaller {
350 #[must_use]
352 pub fn new() -> Self {
353 Self { base_nanos: 0 }
354 }
355
356 #[must_use]
358 pub fn starting_at(nanos: i128) -> Self {
359 Self { base_nanos: nanos }
360 }
361}
362
363impl Default for TestClockInstaller {
364 fn default() -> Self {
365 Self::new()
366 }
367}
368
369impl Installer for TestClockInstaller {
370 type Poller = TestClockPoller;
371
372 fn install(self, world: &mut WorldBuilder) -> TestClockPoller {
373 let clock_id = world.register(Clock::default());
374 TestClockPoller {
375 clock_id,
376 elapsed: Duration::ZERO,
377 base_nanos: self.base_nanos,
378 base_instant: Instant::now(),
379 }
380 }
381}
382
383pub struct TestClockPoller {
388 clock_id: ResourceId,
389 elapsed: Duration,
390 base_nanos: i128,
391 base_instant: Instant,
392}
393
394impl TestClockPoller {
395 #[inline]
397 pub fn sync(&self, world: &mut World) {
398 let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
400 clock.instant = self.base_instant + self.elapsed;
401 clock.unix_nanos = self.base_nanos + self.elapsed.as_nanos() as i128;
402 }
403
404 #[inline]
406 pub fn advance(&mut self, duration: Duration) {
407 self.elapsed += duration;
408 }
409
410 #[inline]
412 pub fn set_elapsed(&mut self, elapsed: Duration) {
413 self.elapsed = elapsed;
414 }
415
416 #[inline]
418 pub fn set_nanos(&mut self, nanos: i128) {
419 self.base_nanos = nanos;
420 self.elapsed = Duration::ZERO;
421 }
422
423 #[inline]
425 #[must_use]
426 pub fn elapsed(&self) -> Duration {
427 self.elapsed
428 }
429
430 #[inline]
432 pub fn reset(&mut self) {
433 self.elapsed = Duration::ZERO;
434 }
435}
436
437impl std::fmt::Debug for TestClockPoller {
438 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
439 f.debug_struct("TestClockPoller")
440 .field("elapsed", &self.elapsed)
441 .field("base_nanos", &self.base_nanos)
442 .finish()
443 }
444}
445
446pub struct HistoricalClockInstaller {
454 start_nanos: i128,
455 end_nanos: i128,
456 step: Duration,
457}
458
459impl HistoricalClockInstaller {
460 pub fn new(start_nanos: i128, end_nanos: i128, step: Duration) -> Result<Self, ConfigError> {
467 if start_nanos >= end_nanos {
468 return Err(ConfigError::Invalid("start_nanos must be < end_nanos"));
469 }
470 if step.is_zero() {
471 return Err(ConfigError::Invalid("step must be > 0"));
472 }
473 Ok(Self {
474 start_nanos,
475 end_nanos,
476 step,
477 })
478 }
479}
480
481impl std::fmt::Debug for HistoricalClockInstaller {
482 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
483 f.debug_struct("HistoricalClockInstaller")
484 .field("start_nanos", &self.start_nanos)
485 .field("end_nanos", &self.end_nanos)
486 .field("step", &self.step)
487 .finish()
488 }
489}
490
491impl Installer for HistoricalClockInstaller {
492 type Poller = HistoricalClockPoller;
493
494 fn install(self, world: &mut WorldBuilder) -> HistoricalClockPoller {
495 let clock_id = world.register(Clock::default());
496 HistoricalClockPoller {
497 clock_id,
498 start_nanos: self.start_nanos,
499 end_nanos: self.end_nanos,
500 current_nanos: self.start_nanos,
501 step_nanos: self.step.as_nanos() as i128,
502 base_instant: Instant::now(),
503 exhausted: false,
504 }
505 }
506}
507
508pub struct HistoricalClockPoller {
510 clock_id: ResourceId,
511 start_nanos: i128,
512 end_nanos: i128,
513 current_nanos: i128,
514 step_nanos: i128,
515 base_instant: Instant,
516 exhausted: bool,
517}
518
519impl HistoricalClockPoller {
520 #[inline]
524 pub fn sync(&mut self, world: &mut World) {
525 let elapsed = (self.current_nanos - self.start_nanos).max(0);
526 let elapsed_nanos = u64::try_from(elapsed).unwrap_or(u64::MAX);
527
528 let clock = unsafe { world.get_mut::<Clock>(self.clock_id) };
530 clock.instant = self.base_instant + Duration::from_nanos(elapsed_nanos);
531 clock.unix_nanos = self.current_nanos;
532
533 if !self.exhausted {
534 self.current_nanos += self.step_nanos;
535 if self.current_nanos >= self.end_nanos {
536 self.current_nanos = self.end_nanos;
537 self.exhausted = true;
538 }
539 }
540 }
541
542 #[inline]
544 #[must_use]
545 pub fn is_exhausted(&self) -> bool {
546 self.exhausted
547 }
548
549 #[inline]
551 #[must_use]
552 pub fn current_nanos(&self) -> i128 {
553 self.current_nanos
554 }
555
556 #[inline]
558 pub fn reset(&mut self) {
559 self.current_nanos = self.start_nanos;
560 self.exhausted = false;
561 }
562}
563
564impl std::fmt::Debug for HistoricalClockPoller {
565 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566 f.debug_struct("HistoricalClockPoller")
567 .field("current_nanos", &self.current_nanos)
568 .field("exhausted", &self.exhausted)
569 .finish()
570 }
571}
572
573#[cfg(test)]
578mod tests {
579 use super::*;
580
581 #[test]
586 fn clock_default() {
587 let clock = Clock::default();
588 assert_eq!(clock.unix_nanos(), 0);
589 }
590
591 #[test]
596 fn realtime_install_and_sync() {
597 let mut wb = WorldBuilder::new();
598 let mut poller = wb.install_driver(RealtimeClockInstaller::default());
599 let mut world = wb.build();
600
601 let now = Instant::now();
602 poller.sync(&mut world, now);
603
604 let clock = world.resource::<Clock>();
605 assert_eq!(clock.instant(), now);
606
607 let expected = SystemTime::now()
608 .duration_since(UNIX_EPOCH)
609 .unwrap()
610 .as_nanos() as i128;
611 let diff = (clock.unix_nanos() - expected).unsigned_abs();
612 assert!(diff < 1_000_000_000, "nanos off by {diff}ns");
613 }
614
615 #[test]
616 fn realtime_nanos_increase() {
617 let mut wb = WorldBuilder::new();
618 let mut poller = wb.install_driver(RealtimeClockInstaller::default());
619 let mut world = wb.build();
620
621 poller.sync(&mut world, Instant::now());
622 let n1 = world.resource::<Clock>().unix_nanos();
623
624 std::thread::sleep(Duration::from_millis(1));
625 poller.sync(&mut world, Instant::now());
626 let n2 = world.resource::<Clock>().unix_nanos();
627
628 assert!(n2 > n1);
629 }
630
631 #[test]
632 fn realtime_resync_no_panic() {
633 let mut wb = WorldBuilder::new();
634 let installer = RealtimeClockInstaller::builder()
635 .resync_interval(Duration::ZERO)
636 .build()
637 .unwrap();
638 let mut poller = wb.install_driver(installer);
639 let mut world = wb.build();
640
641 poller.sync(&mut world, Instant::now());
642 assert!(world.resource::<Clock>().unix_nanos() > 0);
643 }
644
645 #[test]
646 fn realtime_zero_retries_rejected() {
647 let result = RealtimeClockInstaller::builder().max_retries(0).build();
648 assert!(matches!(result, Err(ConfigError::Invalid(_))));
649 }
650
651 #[test]
656 fn test_clock_install_and_sync() {
657 let mut wb = WorldBuilder::new();
658 let poller = wb.install_driver(TestClockInstaller::new());
659 let mut world = wb.build();
660
661 poller.sync(&mut world);
662 assert_eq!(world.resource::<Clock>().unix_nanos(), 0);
663 }
664
665 #[test]
666 fn test_clock_advance() {
667 let mut wb = WorldBuilder::new();
668 let mut poller = wb.install_driver(TestClockInstaller::new());
669 let mut world = wb.build();
670
671 poller.advance(Duration::from_millis(100));
672 poller.sync(&mut world);
673 assert_eq!(world.resource::<Clock>().unix_nanos(), 100_000_000);
674 }
675
676 #[test]
677 fn test_clock_starting_at() {
678 let mut wb = WorldBuilder::new();
679 let poller = wb.install_driver(TestClockInstaller::starting_at(1_000_000_000));
680 let mut world = wb.build();
681
682 poller.sync(&mut world);
683 assert_eq!(world.resource::<Clock>().unix_nanos(), 1_000_000_000);
684 }
685
686 #[test]
687 fn test_clock_set_nanos() {
688 let mut wb = WorldBuilder::new();
689 let mut poller = wb.install_driver(TestClockInstaller::new());
690 let mut world = wb.build();
691
692 poller.set_nanos(42);
693 poller.sync(&mut world);
694 assert_eq!(world.resource::<Clock>().unix_nanos(), 42);
695 }
696
697 #[test]
698 fn test_clock_reset() {
699 let mut wb = WorldBuilder::new();
700 let mut poller = wb.install_driver(TestClockInstaller::new());
701 let mut world = wb.build();
702
703 poller.advance(Duration::from_secs(10));
704 poller.reset();
705 poller.sync(&mut world);
706 assert_eq!(world.resource::<Clock>().unix_nanos(), 0);
707 }
708
709 #[test]
710 fn test_clock_instant_advances() {
711 let mut wb = WorldBuilder::new();
712 let mut poller = wb.install_driver(TestClockInstaller::new());
713 let mut world = wb.build();
714
715 poller.sync(&mut world);
716 let i1 = world.resource::<Clock>().instant();
717
718 poller.advance(Duration::from_millis(100));
719 poller.sync(&mut world);
720 let i2 = world.resource::<Clock>().instant();
721
722 assert_eq!(i2.duration_since(i1), Duration::from_millis(100));
723 }
724
725 #[test]
730 fn historical_install_and_sync() {
731 let installer =
732 HistoricalClockInstaller::new(1000, 2000, Duration::from_nanos(100)).unwrap();
733 let mut wb = WorldBuilder::new();
734 let mut poller = wb.install_driver(installer);
735 let mut world = wb.build();
736
737 poller.sync(&mut world);
738 assert_eq!(world.resource::<Clock>().unix_nanos(), 1000); assert_eq!(poller.current_nanos(), 1100); }
741
742 #[test]
743 fn historical_exhausts() {
744 let installer = HistoricalClockInstaller::new(0, 200, Duration::from_nanos(100)).unwrap();
745 let mut wb = WorldBuilder::new();
746 let mut poller = wb.install_driver(installer);
747 let mut world = wb.build();
748
749 poller.sync(&mut world); poller.sync(&mut world); assert!(poller.is_exhausted());
753 poller.sync(&mut world); assert_eq!(world.resource::<Clock>().unix_nanos(), 200);
755 }
756
757 #[test]
758 fn historical_reset() {
759 let installer = HistoricalClockInstaller::new(100, 500, Duration::from_nanos(100)).unwrap();
760 let mut wb = WorldBuilder::new();
761 let mut poller = wb.install_driver(installer);
762 let mut world = wb.build();
763
764 for _ in 0..10 {
765 poller.sync(&mut world);
766 }
767 assert!(poller.is_exhausted());
768
769 poller.reset();
770 assert!(!poller.is_exhausted());
771 assert_eq!(poller.current_nanos(), 100);
772 }
773
774 #[test]
775 fn historical_rejects_bad_config() {
776 assert!(HistoricalClockInstaller::new(1000, 1000, Duration::from_nanos(100)).is_err());
777 assert!(HistoricalClockInstaller::new(2000, 1000, Duration::from_nanos(100)).is_err());
778 assert!(HistoricalClockInstaller::new(0, 1000, Duration::ZERO).is_err());
779 }
780}