1#[cfg(not(feature = "std"))]
10use alloc::string::{String, ToString};
11use alloc::{
12 boxed::Box,
13 collections::btree_map::BTreeMap,
14 sync::{Arc, Weak},
15 vec::Vec,
16};
17use core::{
18 ffi::c_void,
19 fmt,
20 mem::ManuallyDrop,
21 sync::atomic::{AtomicUsize, Ordering},
22};
23#[cfg(feature = "std")]
24use std::sync::mpsc::{Receiver, Sender};
25#[cfg(feature = "std")]
26use std::sync::Mutex;
27#[cfg(feature = "std")]
28use std::thread::{self, JoinHandle};
29#[cfg(feature = "std")]
30use std::time::Duration as StdDuration;
31#[cfg(feature = "std")]
32use std::time::Instant as StdInstant;
33
34use azul_css::{props::property::CssProperty, AzString};
35use rust_fontconfig::FcFontCache;
36
37use crate::{
38 callbacks::{FocusTarget, TimerCallbackReturn, Update},
39 dom::{DomId, DomNodeId, OptionDomNodeId},
40 geom::{LogicalPosition, OptionLogicalPosition},
41 gl::OptionGlContextPtr,
42 hit_test::ScrollPosition,
43 id::NodeId,
44 refany::{OptionRefAny, RefAny},
45 resources::{ImageCache, ImageMask, ImageRef},
46 styled_dom::NodeHierarchyItemId,
47 window::RawWindowHandle,
48 FastBTreeSet, OrderedMap,
49};
50
51#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[repr(C)]
54pub enum TerminateTimer {
55 Terminate,
57 Continue,
59}
60
61pub const CURSOR_BLINK_TIMER_ID: TimerId = TimerId { id: 0x0001 };
69pub const SCROLL_MOMENTUM_TIMER_ID: TimerId = TimerId { id: 0x0002 };
71pub const DRAG_AUTOSCROLL_TIMER_ID: TimerId = TimerId { id: 0x0003 };
73pub const TOOLTIP_DELAY_TIMER_ID: TimerId = TimerId { id: 0x0004 };
85pub const CAPABILITY_PUMP_TIMER_ID: TimerId = TimerId { id: 0x0005 };
95pub const LONG_PRESS_TIMER_ID: TimerId = TimerId { id: 0x0006 };
103
104pub const CARET_TWEEN_TIMER_ID: TimerId = TimerId { id: 0x0007 };
109
110pub const USER_TIMER_ID_START: usize = 0x0100;
112
113static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
115
116#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
118#[repr(C)]
119pub struct TimerId {
120 pub id: usize,
121}
122
123impl TimerId {
124 #[must_use]
126 pub fn unique() -> Self {
127 Self {
128 id: MAX_TIMER_ID.fetch_add(1, Ordering::SeqCst),
129 }
130 }
131}
132
133impl_option!(
134 TimerId,
135 OptionTimerId,
136 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
137);
138
139impl_vec!(TimerId, TimerIdVec, TimerIdVecDestructor, TimerIdVecDestructorType, TimerIdVecSlice, OptionTimerId);
140impl_vec_debug!(TimerId, TimerIdVec);
141impl_vec_clone!(TimerId, TimerIdVec, TimerIdVecDestructor);
142impl_vec_partialeq!(TimerId, TimerIdVec);
143impl_vec_partialord!(TimerId, TimerIdVec);
144
145const RESERVED_THREAD_ID_COUNT: usize = 5;
148static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
149
150#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
152#[repr(C)]
153pub struct ThreadId {
154 id: usize,
155}
156
157impl_option!(
158 ThreadId,
159 OptionThreadId,
160 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
161);
162
163impl_vec!(ThreadId, ThreadIdVec, ThreadIdVecDestructor, ThreadIdVecDestructorType, ThreadIdVecSlice, OptionThreadId);
164impl_vec_debug!(ThreadId, ThreadIdVec);
165impl_vec_clone!(ThreadId, ThreadIdVec, ThreadIdVecDestructor);
166impl_vec_partialeq!(ThreadId, ThreadIdVec);
167impl_vec_partialord!(ThreadId, ThreadIdVec);
168
169impl ThreadId {
170 #[must_use]
172 pub fn unique() -> Self {
173 Self {
174 id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
175 }
176 }
177}
178
179#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
183#[repr(C, u8)]
184pub enum Instant {
185 System(InstantPtr),
187 Tick(SystemTick),
189}
190
191#[cfg(feature = "std")]
192impl From<StdInstant> for Instant {
193 fn from(s: StdInstant) -> Self {
194 Self::System(s.into())
195 }
196}
197
198#[cfg(feature = "std")]
199std::thread_local! {
200 static TEST_CLOCK_OFFSET_MS: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
231}
232
233#[cfg(feature = "std")]
236#[must_use]
237pub fn advance_test_clock_ms(ms: u64) -> u64 {
238 TEST_CLOCK_OFFSET_MS.with(|c| {
239 let next = c.get().saturating_add(ms);
240 c.set(next);
241 next
242 })
243}
244
245#[cfg(feature = "std")]
248#[must_use]
249pub fn test_clock_offset_ms() -> u64 {
250 TEST_CLOCK_OFFSET_MS.with(core::cell::Cell::get)
251}
252
253#[cfg(feature = "std")]
254#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
255std::thread_local! {
256 static TEST_CLOCK_BASE: core::cell::Cell<Option<StdInstant>> =
260 const { core::cell::Cell::new(None) };
261}
262
263#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
289pub fn freeze_test_clock() {
290 TEST_CLOCK_BASE.with(|c| {
291 if c.get().is_none() {
292 c.set(Some(StdInstant::now()));
293 }
294 });
295}
296
297#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
299#[must_use]
300pub fn test_clock_is_frozen() -> bool {
301 TEST_CLOCK_BASE.with(core::cell::Cell::get).is_some()
302}
303
304#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
313pub fn reset_test_clock() {
314 TEST_CLOCK_OFFSET_MS.with(|c| c.set(0));
315 TEST_CLOCK_BASE.with(|c| c.set(None));
316}
317
318#[cfg(feature = "std")]
330static SYSTEM_TICK: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
331
332#[cfg(feature = "std")]
335pub fn advance_system_tick() {
336 SYSTEM_TICK.fetch_add(1, Ordering::Relaxed);
337}
338
339#[cfg(feature = "std")]
341#[must_use]
342pub fn system_tick_now() -> u64 {
343 SYSTEM_TICK.load(Ordering::Relaxed)
344}
345
346#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
357fn std_now_with_test_offset() -> StdInstant {
358 let offset = test_clock_offset_ms();
359 if let Some(base) = TEST_CLOCK_BASE.with(core::cell::Cell::get) {
360 return base + core::time::Duration::from_millis(offset);
361 }
362 if offset == 0 {
363 StdInstant::now()
364 } else {
365 StdInstant::now() + core::time::Duration::from_millis(offset)
366 }
367}
368
369impl Instant {
370 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
375 #[must_use] pub fn now() -> Self {
376 std_now_with_test_offset().into()
377 }
378
379 #[cfg(all(feature = "std", target_arch = "wasm32"))]
394 #[must_use] pub fn now() -> Self {
395 Instant::Tick(SystemTick::new(system_tick_now()))
396 }
397
398 #[cfg(not(feature = "std"))]
400 pub fn now() -> Self {
401 Instant::Tick(SystemTick::new(0))
402 }
403
404 #[must_use] pub fn linear_interpolate(&self, mut start: Self, mut end: Self) -> f32 {
407 use core::mem;
408
409 if end < start {
410 mem::swap(&mut start, &mut end);
411 }
412
413 if *self < start {
414 return 0.0;
415 }
416 if *self > end {
417 return 1.0;
418 }
419
420 if start == end {
424 return 1.0;
425 }
426
427 let duration_total = end.duration_since(&start);
428 let duration_current = self.duration_since(&start);
429
430 let ratio = duration_current.div(&duration_total);
431 if ratio.is_nan() {
432 return 1.0;
433 }
434 ratio.clamp(0.0, 1.0)
435 }
436
437 #[must_use] pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
456 duration.map_or_else(|| self.clone(), |d| match (self, d) {
457 (Self::System(i), Duration::System(d)) => {
458 #[cfg(feature = "std")]
459 {
460 let s: StdInstant = i.clone().into();
461 let d: StdDuration = (*d).into();
462 let new: InstantPtr = (s + d).into();
463 Self::System(new)
464 }
465 #[cfg(not(feature = "std"))]
466 {
467 let _ = (i, d);
471 self.clone()
472 }
473 }
474 (Self::Tick(s), Duration::Tick(d)) => Self::Tick(SystemTick {
475 tick_counter: s.tick_counter.saturating_add(d.tick_diff),
477 }),
478 (Self::System(_), Duration::Tick(_)) => {
482 self.add_optional_duration(Some(&Duration::System(
483 SystemTimeDiff::from_nanos_u128(d.as_nanos()),
484 )))
485 }
486 (Self::Tick(s), Duration::System(_)) => Self::Tick(SystemTick {
490 tick_counter: s.tick_counter.saturating_add(d.as_ticks()),
491 }),
492 })
493 }
494
495 #[cfg(feature = "std")]
497 #[must_use] pub fn into_std_instant(self) -> StdInstant {
498 match self {
499 Self::System(s) => s.into(),
500 Self::Tick(_) => unreachable!(),
501 }
502 }
503
504 #[must_use] pub fn duration_since(&self, earlier: &Self) -> Duration {
510 match (earlier, self) {
511 (Self::System(prev), Self::System(now)) => {
512 #[cfg(feature = "std")]
513 {
514 let prev_instant: StdInstant = prev.clone().into();
515 let now_instant: StdInstant = now.clone().into();
516 Duration::System(now_instant.saturating_duration_since(prev_instant).into())
519 }
520 #[cfg(not(feature = "std"))]
521 {
522 let _ = (prev, now);
524 Duration::Tick(SystemTickDiff { tick_diff: 0 })
525 }
526 }
527 (
528 Self::Tick(SystemTick { tick_counter: prev }),
529 Self::Tick(SystemTick { tick_counter: now }),
530 ) => Duration::Tick(SystemTickDiff {
531 tick_diff: now.saturating_sub(*prev),
533 }),
534 _ => Duration::Tick(SystemTickDiff { tick_diff: 0 }),
536 }
537 }
538}
539
540#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
544#[repr(C)]
545pub struct SystemTick {
546 pub tick_counter: u64,
547}
548
549impl SystemTick {
550 #[must_use] pub const fn new(tick_counter: u64) -> Self {
552 Self { tick_counter }
553 }
554}
555
556#[repr(C)]
560pub struct InstantPtr {
561 #[cfg(feature = "std")]
569 pub ptr: ManuallyDrop<Box<StdInstant>>,
570 #[cfg(not(feature = "std"))]
571 pub ptr: *const c_void,
572 pub clone_fn: InstantPtrCloneCallback,
573 pub destructor: InstantPtrDestructorCallback,
574 pub run_destructor: bool,
575}
576
577pub type InstantPtrCloneCallbackType = extern "C" fn(*const InstantPtr) -> InstantPtr;
578#[repr(C)]
579pub struct InstantPtrCloneCallback {
580 pub cb: InstantPtrCloneCallbackType,
581}
582impl_callback_simple!(InstantPtrCloneCallback);
583
584pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
585#[repr(C)]
586pub struct InstantPtrDestructorCallback {
587 pub cb: InstantPtrDestructorCallbackType,
588}
589impl_callback_simple!(InstantPtrDestructorCallback);
590
591#[cfg(feature = "std")]
593impl fmt::Debug for InstantPtr {
594 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
595 write!(f, "{:?}", self.get())
596 }
597}
598
599#[cfg(not(feature = "std"))]
600impl core::fmt::Debug for InstantPtr {
601 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
602 write!(f, "{:?}", self.ptr as usize)
603 }
604}
605
606#[cfg(feature = "std")]
607impl core::hash::Hash for InstantPtr {
608 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
609 self.get().hash(state);
610 }
611}
612
613#[cfg(not(feature = "std"))]
614impl core::hash::Hash for InstantPtr {
615 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
616 (self.ptr as usize).hash(state);
617 }
618}
619
620#[cfg(feature = "std")]
621impl PartialEq for InstantPtr {
622 fn eq(&self, other: &Self) -> bool {
623 self.get() == other.get()
624 }
625}
626
627#[cfg(not(feature = "std"))]
628impl PartialEq for InstantPtr {
629 fn eq(&self, other: &InstantPtr) -> bool {
630 (self.ptr as usize).eq(&(other.ptr as usize))
631 }
632}
633
634impl Eq for InstantPtr {}
635
636#[cfg(feature = "std")]
637impl PartialOrd for InstantPtr {
638 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
639 Some((self.get()).cmp(&(other.get())))
640 }
641}
642
643#[cfg(not(feature = "std"))]
644impl PartialOrd for InstantPtr {
645 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
646 Some((self.ptr as usize).cmp(&(other.ptr as usize)))
647 }
648}
649
650#[cfg(feature = "std")]
651impl Ord for InstantPtr {
652 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
653 (self.get()).cmp(&(other.get()))
654 }
655}
656
657#[cfg(not(feature = "std"))]
658impl Ord for InstantPtr {
659 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
660 (self.ptr as usize).cmp(&(other.ptr as usize))
661 }
662}
663
664#[cfg(feature = "std")]
665impl InstantPtr {
666 fn get(&self) -> StdInstant {
667 (**self.ptr)
668 }
669}
670
671impl Clone for InstantPtr {
672 fn clone(&self) -> Self {
673 (self.clone_fn.cb)(self)
674 }
675}
676
677#[cfg(feature = "std")]
678extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
679 let az_instant_ptr = unsafe { &*ptr };
680 InstantPtr {
681 ptr: ManuallyDrop::new((*az_instant_ptr.ptr).clone()),
682 clone_fn: az_instant_ptr.clone_fn,
683 destructor: az_instant_ptr.destructor,
684 run_destructor: true,
685 }
686}
687
688#[cfg(feature = "std")]
689impl From<StdInstant> for InstantPtr {
690 fn from(s: StdInstant) -> Self {
691 Self {
692 ptr: ManuallyDrop::new(Box::new(s)),
693 clone_fn: InstantPtrCloneCallback {
694 cb: std_instant_clone,
695 },
696 destructor: InstantPtrDestructorCallback {
697 cb: std_instant_drop,
698 },
699 run_destructor: true,
700 }
701 }
702}
703
704#[cfg(feature = "std")]
705impl From<InstantPtr> for StdInstant {
706 fn from(s: InstantPtr) -> Self {
707 s.get()
708 }
709}
710
711impl Drop for InstantPtr {
712 fn drop(&mut self) {
713 if self.run_destructor {
714 self.run_destructor = false;
715 (self.destructor.cb)(self);
716 #[cfg(feature = "std")]
725 unsafe {
726 ManuallyDrop::drop(&mut self.ptr);
727 }
728 }
729 }
730}
731
732#[cfg(feature = "std")]
733const extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
734
735#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
742#[repr(C, u8)]
743pub enum Duration {
744 System(SystemTimeDiff),
746 Tick(SystemTickDiff),
748}
749
750impl fmt::Display for Duration {
751 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
752 match self {
753 #[cfg(feature = "std")]
754 Self::System(s) => {
755 let s: StdDuration = (*s).into();
756 write!(f, "{s:?}")
757 }
758 #[cfg(not(feature = "std"))]
759 Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
760 Self::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
761 }
762 }
763}
764
765#[cfg(feature = "std")]
766impl From<StdDuration> for Duration {
767 fn from(s: StdDuration) -> Self {
768 Self::System(s.into())
769 }
770}
771
772pub use azul_css::props::basic::time::TICKS_PER_SECOND;
778
779impl Duration {
780 #[allow(clippy::cast_lossless)]
794 #[must_use]
795 pub const fn as_nanos(&self) -> u128 {
796 match self {
797 Self::System(s) => (s.secs as u128) * (NANOS_PER_SEC as u128) + (s.nanos as u128),
798 Self::Tick(t) => (t.tick_diff as u128) * (NANOS_PER_SEC as u128) / (TICKS_PER_SECOND as u128),
799 }
800 }
801
802 #[must_use]
804 pub const fn from_millis(ms: u64) -> Self {
805 Self::System(SystemTimeDiff::from_millis(ms))
806 }
807
808 #[must_use]
811 pub const fn from_ticks(ticks: u64) -> Self {
812 Self::Tick(SystemTickDiff { tick_diff: ticks })
813 }
814
815 #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
823 #[must_use]
824 pub const fn as_ticks(&self) -> u64 {
825 match self {
826 Self::Tick(t) => t.tick_diff,
827 Self::System(_) => {
828 let ticks = self.as_nanos() * (TICKS_PER_SECOND as u128) / (NANOS_PER_SEC as u128);
829 if ticks > u64::MAX as u128 {
830 u64::MAX
831 } else {
832 ticks as u64
833 }
834 }
835 }
836 }
837
838 #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
843 #[must_use]
844 pub const fn as_millis_u64(&self) -> u64 {
845 let ms = self.as_nanos() / (NANOS_PER_MILLI as u128);
846 if ms > u64::MAX as u128 {
847 u64::MAX
848 } else {
849 ms as u64
850 }
851 }
852
853 #[must_use] pub fn max() -> Self {
855 #[cfg(feature = "std")]
856 {
857 Self::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
858 }
859 #[cfg(not(feature = "std"))]
860 {
861 Duration::Tick(SystemTickDiff {
862 tick_diff: u64::MAX,
863 })
864 }
865 }
866
867 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
877 #[must_use] pub fn div(&self, other: &Self) -> f32 {
878 use self::Duration::{System, Tick};
879 match (self, other) {
880 (System(s), System(s2)) => s.div(s2) as f32,
881 (Tick(t), Tick(t2)) => t.div(t2) as f32,
882 _ => (self.as_nanos() as f64 / other.as_nanos() as f64) as f32,
885 }
886 }
887
888 #[must_use] pub const fn min(self, other: Self) -> Self {
890 if self.smaller_than(&other) {
891 self
892 } else {
893 other
894 }
895 }
896
897 #[must_use] pub const fn greater_than(&self, other: &Self) -> bool {
921 self.as_nanos() > other.as_nanos()
922 }
923
924 #[must_use] pub const fn smaller_than(&self, other: &Self) -> bool {
929 self.as_nanos() < other.as_nanos()
930 }
931}
932
933#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
936#[repr(C)]
937pub struct SystemTickDiff {
938 pub tick_diff: u64,
939}
940
941impl SystemTickDiff {
942 #[allow(clippy::cast_precision_loss)]
946 #[must_use] pub fn div(&self, other: &Self) -> f64 {
947 self.tick_diff as f64 / other.tick_diff as f64
948 }
949}
950
951#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
953#[repr(C)]
954pub struct SystemTimeDiff {
955 pub secs: u64,
956 pub nanos: u32,
957}
958
959impl SystemTimeDiff {
960 #[must_use] pub fn div(&self, other: &Self) -> f64 {
963 self.as_secs_f64() / other.as_secs_f64()
964 }
965 #[allow(clippy::cast_precision_loss)]
967 fn as_secs_f64(&self) -> f64 {
968 (self.secs as f64) + (f64::from(self.nanos) / f64::from(NANOS_PER_SEC))
969 }
970}
971
972#[cfg(feature = "std")]
973impl From<StdDuration> for SystemTimeDiff {
974 fn from(d: StdDuration) -> Self {
975 Self {
976 secs: d.as_secs(),
977 nanos: d.subsec_nanos(),
978 }
979 }
980}
981
982#[cfg(feature = "std")]
983impl From<SystemTimeDiff> for StdDuration {
984 fn from(d: SystemTimeDiff) -> Self {
985 Self::new(d.secs, d.nanos)
986 }
987}
988
989const MILLIS_PER_SEC: u64 = 1_000;
990const NANOS_PER_MILLI: u32 = 1_000_000;
991const NANOS_PER_SEC: u32 = 1_000_000_000;
992
993impl SystemTimeDiff {
994 #[must_use] pub const fn from_secs(secs: u64) -> Self {
996 Self { secs, nanos: 0 }
997 }
998 #[must_use] pub const fn from_millis(millis: u64) -> Self {
1000 Self {
1001 secs: millis / MILLIS_PER_SEC,
1002 nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
1003 }
1004 }
1005 #[allow(clippy::cast_possible_truncation)]
1009 #[must_use] pub const fn from_nanos(nanos: u64) -> Self {
1010 Self {
1011 secs: nanos / (NANOS_PER_SEC as u64),
1012 nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
1013 }
1014 }
1015
1016 #[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
1025 #[must_use] pub const fn from_nanos_u128(nanos: u128) -> Self {
1026 let secs = nanos / (NANOS_PER_SEC as u128);
1027 if secs > u64::MAX as u128 {
1028 Self {
1029 secs: u64::MAX,
1030 nanos: NANOS_PER_SEC - 1,
1031 }
1032 } else {
1033 Self {
1034 secs: secs as u64,
1035 nanos: (nanos % (NANOS_PER_SEC as u128)) as u32,
1036 }
1037 }
1038 }
1039 #[must_use] pub const fn checked_add(self, rhs: Self) -> Option<Self> {
1041 if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
1042 let mut nanos = self.nanos + rhs.nanos;
1043 if nanos >= NANOS_PER_SEC {
1044 nanos -= NANOS_PER_SEC;
1045 if let Some(new_secs) = secs.checked_add(1) {
1046 secs = new_secs;
1047 } else {
1048 return None;
1049 }
1050 }
1051 Some(Self { secs, nanos })
1052 } else {
1053 None
1054 }
1055 }
1056
1057 #[must_use] pub const fn millis(&self) -> u64 {
1062 self.secs
1063 .saturating_mul(MILLIS_PER_SEC)
1064 .saturating_add((self.nanos / NANOS_PER_MILLI) as u64)
1065 }
1066
1067 #[cfg(feature = "std")]
1069 #[must_use] pub fn get(&self) -> StdDuration {
1070 (*self).into()
1071 }
1072}
1073
1074impl From<azul_css::props::basic::time::CssDuration> for Duration {
1083 fn from(d: azul_css::props::basic::time::CssDuration) -> Self {
1084 use azul_css::props::basic::time::CssDurationUnit;
1085 match d.unit {
1086 CssDurationUnit::Milliseconds => Self::from_millis(u64::from(d.inner)),
1087 CssDurationUnit::Ticks => Self::from_ticks(u64::from(d.inner)),
1088 }
1089 }
1090}
1091
1092impl_option!(
1093 Instant,
1094 OptionInstant,
1095 copy = false,
1096 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1097);
1098impl_option!(
1099 Duration,
1100 OptionDuration,
1101 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1102);
1103#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1108#[repr(C, u8)]
1109pub enum ThreadSendMsg {
1110 TerminateThread,
1112 Tick,
1114 Custom(RefAny),
1116}
1117
1118impl_option!(
1119 ThreadSendMsg,
1120 OptionThreadSendMsg,
1121 copy = false,
1122 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1123);
1124
1125#[derive(Debug)]
1129#[repr(C)]
1130pub struct ThreadReceiver {
1131 #[cfg(feature = "std")]
1132 pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
1133 #[cfg(not(feature = "std"))]
1134 pub ptr: *const c_void,
1135 pub run_destructor: bool,
1136 pub ctx: OptionRefAny,
1138}
1139
1140impl Clone for ThreadReceiver {
1141 fn clone(&self) -> Self {
1142 Self {
1143 ptr: self.ptr.clone(),
1144 run_destructor: true,
1145 ctx: self.ctx.clone(),
1146 }
1147 }
1148}
1149
1150impl Drop for ThreadReceiver {
1151 fn drop(&mut self) {
1152 self.run_destructor = false;
1153 }
1154}
1155
1156impl ThreadReceiver {
1157 #[cfg(not(feature = "std"))]
1159 pub fn new(_t: ThreadReceiverInner) -> Self {
1160 Self {
1161 ptr: core::ptr::null(),
1162 run_destructor: false,
1163 ctx: OptionRefAny::None,
1164 }
1165 }
1166
1167 #[cfg(feature = "std")]
1169 #[must_use] pub fn new(t: ThreadReceiverInner) -> Self {
1170 Self {
1171 ptr: Box::new(Arc::new(Mutex::new(t))),
1172 run_destructor: true,
1173 ctx: OptionRefAny::None,
1174 }
1175 }
1176
1177 #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
1179 self.ctx.clone()
1180 }
1181
1182 #[cfg(not(feature = "std"))]
1184 pub fn recv(&mut self) -> OptionThreadSendMsg {
1185 None.into()
1186 }
1187
1188 #[cfg(feature = "std")]
1190 pub fn recv(&mut self) -> OptionThreadSendMsg {
1191 let Some(ts) = self.ptr.lock().ok() else {
1192 return None.into();
1193 };
1194 (ts.recv_fn.cb)(std::ptr::from_ref(ts.ptr.as_ref()) as *const c_void)
1195 }
1196}
1197
1198#[derive(Debug)]
1200#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
1201#[repr(C)]
1202pub struct ThreadReceiverInner {
1203 #[cfg(feature = "std")]
1204 pub ptr: Box<Receiver<ThreadSendMsg>>,
1205 #[cfg(not(feature = "std"))]
1206 pub ptr: *const c_void,
1207 pub recv_fn: ThreadRecvCallback,
1208 pub destructor: ThreadReceiverDestructorCallback,
1209}
1210
1211#[cfg(not(feature = "std"))]
1212unsafe impl Send for ThreadReceiverInner {}
1213
1214#[cfg(feature = "std")]
1215impl core::hash::Hash for ThreadReceiverInner {
1216 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
1217 (std::ptr::from_ref(self.ptr.as_ref()) as usize).hash(state);
1218 }
1219}
1220
1221#[cfg(feature = "std")]
1222impl PartialEq for ThreadReceiverInner {
1223 fn eq(&self, other: &Self) -> bool {
1224 std::ptr::eq(self.ptr.as_ref(), other.ptr.as_ref())
1225 }
1226}
1227
1228#[cfg(feature = "std")]
1229impl Eq for ThreadReceiverInner {}
1230
1231#[cfg(feature = "std")]
1232impl PartialOrd for ThreadReceiverInner {
1233 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1234 Some(
1235 (std::ptr::from_ref(self.ptr.as_ref()) as usize)
1236 .cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize)),
1237 )
1238 }
1239}
1240
1241#[cfg(feature = "std")]
1242impl Ord for ThreadReceiverInner {
1243 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1244 (std::ptr::from_ref(self.ptr.as_ref()) as usize).cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize))
1245 }
1246}
1247
1248impl Drop for ThreadReceiverInner {
1249 fn drop(&mut self) {
1250 (self.destructor.cb)(self);
1251 }
1252}
1253
1254pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
1257#[repr(C)]
1258pub struct GetSystemTimeCallback {
1259 pub cb: GetSystemTimeCallbackType,
1260}
1261impl_callback_simple!(GetSystemTimeCallback);
1262
1263#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1268#[must_use] pub extern "C" fn get_system_time_libstd() -> Instant {
1269 std_now_with_test_offset().into()
1271}
1272
1273#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
1275pub extern "C" fn get_system_time_libstd() -> Instant {
1276 Instant::Tick(SystemTick::new(0))
1277}
1278
1279pub type CheckThreadFinishedCallbackType =
1281 extern "C" fn(*const c_void) -> bool;
1282#[repr(C)]
1284pub struct CheckThreadFinishedCallback {
1285 pub cb: CheckThreadFinishedCallbackType,
1286}
1287impl_callback_simple!(CheckThreadFinishedCallback);
1288
1289pub type LibrarySendThreadMsgCallbackType =
1291 extern "C" fn(*const c_void, ThreadSendMsg) -> bool;
1292#[repr(C)]
1294pub struct LibrarySendThreadMsgCallback {
1295 pub cb: LibrarySendThreadMsgCallbackType,
1296}
1297impl_callback_simple!(LibrarySendThreadMsgCallback);
1298
1299pub type ThreadRecvCallbackType =
1301 extern "C" fn(*const c_void) -> OptionThreadSendMsg;
1302#[repr(C)]
1304pub struct ThreadRecvCallback {
1305 pub cb: ThreadRecvCallbackType,
1306}
1307impl_callback_simple!(ThreadRecvCallback);
1308
1309pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
1311#[repr(C)]
1313pub struct ThreadReceiverDestructorCallback {
1314 pub cb: ThreadReceiverDestructorCallbackType,
1315}
1316impl_callback_simple!(ThreadReceiverDestructorCallback);
1317
1318#[cfg(test)]
1319#[allow(clippy::float_cmp)] mod tests {
1321 use super::*;
1322
1323 fn tick(n: u64) -> Instant {
1324 Instant::Tick(SystemTick::new(n))
1325 }
1326 fn tick_dur(n: u64) -> Duration {
1327 Duration::Tick(SystemTickDiff { tick_diff: n })
1328 }
1329 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1330 Duration::System(SystemTimeDiff { secs, nanos })
1331 }
1332
1333 #[test]
1338 #[cfg(feature = "std")]
1339 fn test_clock_offset_is_per_thread_not_process_global() {
1340 reset_test_clock();
1341 assert_eq!(test_clock_offset_ms(), 0);
1342
1343 let (tx, rx) = std::sync::mpsc::channel();
1344 let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();
1345 let other = std::thread::spawn(move || {
1346 go_rx.recv().expect("handshake");
1348 let seen_after_main_ticked = test_clock_offset_ms();
1349 let _ = advance_test_clock_ms(7);
1350 tx.send((seen_after_main_ticked, test_clock_offset_ms()))
1351 .expect("send");
1352 });
1353
1354 assert_eq!(advance_test_clock_ms(5_000), 5_000);
1355 go_tx.send(()).expect("handshake");
1356 let (other_before, other_after) = rx.recv().expect("recv");
1357 other.join().expect("join");
1358
1359 assert_eq!(
1360 other_before, 0,
1361 "a tick on the main thread leaked into another thread's clock"
1362 );
1363 assert_eq!(other_after, 7, "the other thread must own its own offset");
1364 assert_eq!(
1365 test_clock_offset_ms(),
1366 5_000,
1367 "another thread's tick leaked into the main thread's clock"
1368 );
1369
1370 reset_test_clock();
1372 assert_eq!(test_clock_offset_ms(), 0);
1373 }
1374
1375 #[test]
1383 #[cfg(feature = "std")]
1384 fn a_frozen_clock_advances_only_by_what_the_scenario_asks_for() {
1385 reset_test_clock();
1386 assert!(!test_clock_is_frozen());
1387
1388 freeze_test_clock();
1389 assert!(test_clock_is_frozen());
1390
1391 let t0 = Instant::now();
1392 std::thread::sleep(core::time::Duration::from_millis(25));
1394 let t1 = Instant::now();
1395 assert_eq!(
1396 t1.duration_since(&t0),
1397 Duration::System(SystemTimeDiff { secs: 0, nanos: 0 }),
1398 "real time leaked into a frozen clock",
1399 );
1400
1401 let _ = advance_test_clock_ms(500);
1403 let t2 = Instant::now();
1404 assert_eq!(
1405 t2.duration_since(&t0),
1406 Duration::System(SystemTimeDiff { secs: 0, nanos: 500_000_000 }),
1407 "a 500 ms tick must read back as exactly 500 ms",
1408 );
1409
1410 freeze_test_clock();
1412 assert_eq!(
1413 Instant::now().duration_since(&t0),
1414 Duration::System(SystemTimeDiff { secs: 0, nanos: 500_000_000 }),
1415 "re-freezing re-based the clock and discarded elapsed virtual time",
1416 );
1417
1418 reset_test_clock();
1421 assert!(!test_clock_is_frozen());
1422 let r0 = Instant::now();
1423 std::thread::sleep(core::time::Duration::from_millis(15));
1424 assert!(
1425 Instant::now().duration_since(&r0)
1426 > Duration::System(SystemTimeDiff { secs: 0, nanos: 0 }),
1427 "reset_test_clock left the clock frozen",
1428 );
1429 }
1430
1431 #[test]
1432 fn linear_interpolate_zero_interval_is_one_not_nan() {
1433 let t = tick(5);
1434 let v = t.linear_interpolate(tick(5), tick(5));
1435 assert!(v.is_finite());
1436 assert_eq!(v, 1.0);
1437 }
1438
1439 #[test]
1440 fn linear_interpolate_midpoint() {
1441 let v = tick(5).linear_interpolate(tick(0), tick(10));
1442 assert!((v - 0.5).abs() < 1e-6);
1443 }
1444
1445 #[test]
1446 fn duration_since_saturates_on_negative() {
1447 let d = tick(1).duration_since(&tick(10));
1449 assert_eq!(d, tick_dur(0));
1450 }
1451
1452 #[test]
1459 fn duration_compare_is_unit_aware_across_ticks_and_wall_clock() {
1460 let five_ticks = tick_dur(5);
1462 let one_second = sys_dur(1, 0);
1463 assert!(five_ticks.smaller_than(&one_second));
1464 assert!(!five_ticks.greater_than(&one_second));
1465 assert!(one_second.greater_than(&five_ticks));
1466 assert!(!one_second.smaller_than(&five_ticks));
1467
1468 assert!(tick_dur(120).greater_than(&one_second));
1470 assert!(one_second.smaller_than(&tick_dur(120)));
1471
1472 assert!(!tick_dur(60).greater_than(&one_second));
1474 assert!(!tick_dur(60).smaller_than(&one_second));
1475 assert!(!one_second.greater_than(&tick_dur(60)));
1476 assert!(!one_second.smaller_than(&tick_dur(60)));
1477 }
1478
1479 #[test]
1483 fn duration_compare_across_units_at_the_extremes() {
1484 assert!(Duration::max().greater_than(&tick_dur(u64::MAX)));
1485 assert!(tick_dur(u64::MAX).smaller_than(&Duration::max()));
1486 assert!(!tick_dur(0).greater_than(&sys_dur(0, 0)));
1487 assert!(!sys_dur(0, 0).greater_than(&tick_dur(0)));
1488 assert!(sys_dur(0, 1).greater_than(&tick_dur(0)));
1490 }
1491
1492 #[test]
1493 fn add_optional_duration_converts_across_units() {
1494 let inst = tick(100);
1495 assert_eq!(inst.add_optional_duration(Some(&sys_dur(1, 0))), tick(160));
1497 assert_eq!(inst.add_optional_duration(Some(&sys_dur(0, 1))), tick(100));
1499 assert_eq!(inst.add_optional_duration(Some(&tick_dur(5))), tick(105));
1501 let big = tick(u64::MAX);
1503 assert_eq!(big.add_optional_duration(Some(&tick_dur(10))), tick(u64::MAX));
1504 assert_eq!(big.add_optional_duration(Some(&Duration::max())), tick(u64::MAX));
1506 }
1507
1508 #[test]
1509 fn millis_saturates_on_overflow() {
1510 let huge = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
1511 assert_eq!(huge.millis(), u64::MAX);
1512 let normal = SystemTimeDiff { secs: 2, nanos: 500_000_000 };
1513 assert_eq!(normal.millis(), 2500);
1514 }
1515
1516 #[test]
1520 fn duration_div_is_unit_aware() {
1521 assert!((tick_dur(30).div(&sys_dur(1, 0)) - 0.5).abs() < 1e-6);
1523 assert!((sys_dur(1, 0).div(&tick_dur(30)) - 2.0).abs() < 1e-6);
1525 assert!((tick_dur(5).div(&tick_dur(10)) - 0.5).abs() < 1e-6);
1527 assert!((sys_dur(1, 0).div(&sys_dur(2, 0)) - 0.5).abs() < 1e-6);
1529 }
1530
1531 #[cfg(feature = "std")]
1537 #[test]
1538 fn instant_ptr_clone_and_drop_no_ub() {
1539 let base = StdInstant::now();
1540 let a: InstantPtr = base.into();
1541 let b = a.clone();
1542 assert_eq!(a, b);
1544 drop(a);
1547 drop(b);
1548 }
1549}
1550
1551#[cfg(test)]
1552#[allow(clippy::float_cmp)] mod autotest_generated {
1554 use super::*;
1555
1556 fn tick(n: u64) -> Instant {
1559 Instant::Tick(SystemTick::new(n))
1560 }
1561 fn tick_dur(n: u64) -> Duration {
1562 Duration::Tick(SystemTickDiff { tick_diff: n })
1563 }
1564 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1565 Duration::System(SystemTimeDiff { secs, nanos })
1566 }
1567
1568 #[test]
1573 fn timer_id_unique_is_strictly_increasing_and_above_reserved_range() {
1574 let a = TimerId::unique();
1575 let b = TimerId::unique();
1576 assert_ne!(a, b);
1577 assert!(b.id > a.id, "unique() must strictly increase: {a:?} -> {b:?}");
1578 for id in [a, b] {
1580 assert!(
1581 id.id >= USER_TIMER_ID_START,
1582 "unique() handed out a reserved system ID: {id:?}"
1583 );
1584 assert_ne!(id, CURSOR_BLINK_TIMER_ID);
1585 assert_ne!(id, SCROLL_MOMENTUM_TIMER_ID);
1586 assert_ne!(id, DRAG_AUTOSCROLL_TIMER_ID);
1587 assert_ne!(id, TOOLTIP_DELAY_TIMER_ID);
1588 assert_ne!(id, CAPABILITY_PUMP_TIMER_ID);
1589 assert_ne!(id, LONG_PRESS_TIMER_ID);
1590 }
1591 }
1592
1593 #[test]
1594 fn thread_id_unique_is_strictly_increasing_and_above_reserved_range() {
1595 let a = ThreadId::unique();
1596 let b = ThreadId::unique();
1597 assert_ne!(a, b);
1598 assert!(b.id > a.id);
1599 assert!(a.id >= RESERVED_THREAD_ID_COUNT);
1600 }
1601
1602 #[cfg(feature = "std")]
1605 #[test]
1606 fn unique_ids_do_not_collide_across_threads() {
1607 use alloc::collections::BTreeSet;
1608
1609 let handles: Vec<_> = (0..8)
1610 .map(|_| {
1611 std::thread::spawn(|| {
1612 let mut out = Vec::new();
1613 for _ in 0..64 {
1614 out.push((TimerId::unique().id, ThreadId::unique().id));
1615 }
1616 out
1617 })
1618 })
1619 .collect();
1620
1621 let mut timer_ids = BTreeSet::new();
1622 let mut thread_ids = BTreeSet::new();
1623 for h in handles {
1624 for (t, th) in h.join().expect("worker thread panicked") {
1625 assert!(timer_ids.insert(t), "duplicate TimerId handed out: {t}");
1626 assert!(thread_ids.insert(th), "duplicate ThreadId handed out: {th}");
1627 }
1628 }
1629 assert_eq!(timer_ids.len(), 8 * 64);
1630 assert_eq!(thread_ids.len(), 8 * 64);
1631 }
1632
1633 #[cfg(feature = "std")]
1638 #[test]
1639 fn instant_now_is_system_and_monotonic() {
1640 let a = Instant::now();
1641 let b = Instant::now();
1642 assert!(matches!(a, Instant::System(_)));
1643 assert!(a <= b, "Instant::now() went backwards");
1644 assert_eq!(a.duration_since(&b), sys_dur(0, 0));
1646 }
1647
1648 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1649 #[test]
1650 fn get_system_time_libstd_is_monotonic_system_instant() {
1651 let a = get_system_time_libstd();
1652 let b = get_system_time_libstd();
1653 assert!(matches!(a, Instant::System(_)));
1654 assert!(matches!(b, Instant::System(_)));
1655 assert!(a <= b);
1656 }
1657
1658 #[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
1659 #[test]
1660 fn get_system_time_libstd_wasm_fallback_is_zero_tick() {
1661 assert_eq!(get_system_time_libstd(), tick(0));
1664 }
1665
1666 #[test]
1671 fn linear_interpolate_clamps_outside_the_interval() {
1672 assert_eq!(tick(0).linear_interpolate(tick(10), tick(20)), 0.0);
1674 assert_eq!(tick(999).linear_interpolate(tick(10), tick(20)), 1.0);
1675 assert_eq!(tick(10).linear_interpolate(tick(10), tick(20)), 0.0);
1677 assert_eq!(tick(20).linear_interpolate(tick(10), tick(20)), 1.0);
1678 }
1679
1680 #[test]
1681 fn linear_interpolate_reversed_interval_is_normalized() {
1682 let forwards = tick(5).linear_interpolate(tick(0), tick(10));
1685 let backwards = tick(5).linear_interpolate(tick(10), tick(0));
1686 assert_eq!(forwards, backwards);
1687 assert!((backwards - 0.5).abs() < 1e-6);
1688 }
1689
1690 #[test]
1691 fn linear_interpolate_saturating_extremes_stay_in_range() {
1692 let v = tick(u64::MAX / 2).linear_interpolate(tick(0), tick(u64::MAX));
1695 assert!(v.is_finite(), "interpolation over the full u64 span went non-finite");
1696 assert!((0.0..=1.0).contains(&v));
1697 assert!((v - 0.5).abs() < 1e-3, "expected ~0.5, got {v}");
1698
1699 let z = tick(u64::MAX).linear_interpolate(tick(u64::MAX), tick(u64::MAX));
1701 assert_eq!(z, 1.0);
1702 let z0 = tick(0).linear_interpolate(tick(0), tick(0));
1703 assert_eq!(z0, 1.0);
1704 }
1705
1706 #[cfg(feature = "std")]
1707 #[test]
1708 fn linear_interpolate_mismatched_kinds_never_nan() {
1709 let sys = Instant::now();
1712 let cases = [
1713 (tick(5), sys.clone(), tick(10)),
1714 (sys.clone(), tick(0), tick(10)),
1715 (tick(5), tick(0), sys.clone()),
1716 (sys.clone(), sys.clone(), tick(10)),
1717 (tick(5), sys.clone(), sys.clone()),
1718 ];
1719 for (this, start, end) in cases {
1720 let v = this.linear_interpolate(start, end);
1721 assert!(v.is_finite(), "mismatched-kind interpolation returned {v}");
1722 assert!(
1723 (0.0..=1.0).contains(&v),
1724 "mismatched-kind interpolation escaped [0,1]: {v}"
1725 );
1726 }
1727 }
1728
1729 #[test]
1734 fn add_optional_duration_none_is_identity() {
1735 let t = tick(42);
1736 assert_eq!(t.add_optional_duration(None), t);
1737 assert_eq!(tick(u64::MAX).add_optional_duration(None), tick(u64::MAX));
1738 }
1739
1740 #[test]
1741 fn add_optional_duration_tick_saturates_at_u64_max() {
1742 let near_max = tick(u64::MAX - 1);
1744 assert_eq!(
1745 near_max.add_optional_duration(Some(&tick_dur(u64::MAX))),
1746 tick(u64::MAX)
1747 );
1748 assert_eq!(tick(0).add_optional_duration(Some(&tick_dur(0))), tick(0));
1749 }
1750
1751 #[cfg(feature = "std")]
1752 #[test]
1753 fn add_optional_duration_system_advances_by_the_duration() {
1754 let base = Instant::now();
1755 let later = base.add_optional_duration(Some(&Duration::System(SystemTimeDiff::from_secs(1))));
1756 assert!(later > base);
1757 let delta = later.duration_since(&base);
1758 assert_eq!(delta, sys_dur(1, 0));
1759 assert_eq!(base.duration_since(&later), sys_dur(0, 0));
1761 }
1762
1763 #[cfg(feature = "std")]
1769 #[test]
1770 fn add_optional_duration_converts_between_units_in_both_directions() {
1771 let sys = Instant::now();
1772 let later = sys.add_optional_duration(Some(&tick_dur(60)));
1774 assert!(later > sys, "a tick interval must advance a wall-clock instant");
1775 assert_eq!(later.duration_since(&sys), sys_dur(1, 0));
1776
1777 assert_eq!(sys.add_optional_duration(Some(&tick_dur(0))), sys);
1779
1780 assert_eq!(tick(7).add_optional_duration(Some(&sys_dur(3, 0))), tick(187));
1782 }
1783
1784 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1790 #[test]
1791 #[should_panic(expected = "overflow")]
1792 fn add_optional_duration_system_overflow_panics_today() {
1793 let base = Instant::now();
1794 let _ = base.add_optional_duration(Some(&Duration::max()));
1795 }
1796
1797 #[test]
1802 fn duration_since_tick_saturates_and_is_exact() {
1803 assert_eq!(tick(10).duration_since(&tick(4)), tick_dur(6));
1804 assert_eq!(tick(10).duration_since(&tick(10)), tick_dur(0));
1806 assert_eq!(tick(0).duration_since(&tick(u64::MAX)), tick_dur(0));
1808 assert_eq!(tick(u64::MAX).duration_since(&tick(0)), tick_dur(u64::MAX));
1810 }
1811
1812 #[cfg(feature = "std")]
1813 #[test]
1814 fn duration_since_mismatched_kinds_is_zero_tick_both_directions() {
1815 let sys = Instant::now();
1816 assert_eq!(sys.duration_since(&tick(5)), tick_dur(0));
1817 assert_eq!(tick(5).duration_since(&sys), tick_dur(0));
1818 }
1819
1820 #[cfg(feature = "std")]
1821 #[test]
1822 fn into_std_instant_round_trips_a_system_instant() {
1823 let base = StdInstant::now();
1824 let wrapped: Instant = base.into();
1825 assert_eq!(wrapped.into_std_instant(), base);
1826 }
1827
1828 #[cfg(feature = "std")]
1829 #[test]
1830 #[should_panic(expected = "internal error: entered unreachable code")]
1831 fn into_std_instant_on_tick_variant_panics() {
1832 let _ = tick(1).into_std_instant();
1834 }
1835
1836 #[test]
1841 fn system_tick_new_stores_the_counter_verbatim() {
1842 for n in [0_u64, 1, 0x0100, u64::MAX / 2, u64::MAX] {
1843 assert_eq!(SystemTick::new(n).tick_counter, n);
1844 }
1845 assert!(SystemTick::new(0) < SystemTick::new(u64::MAX));
1847 assert_eq!(SystemTick::new(7), SystemTick::new(7));
1848 }
1849
1850 #[cfg(feature = "std")]
1855 #[test]
1856 fn instant_ptr_get_returns_the_wrapped_instant() {
1857 let base = StdInstant::now();
1858 let p: InstantPtr = base.into();
1859 assert_eq!(p.get(), base);
1860 assert_eq!(p.get(), p.get());
1862 assert!(p.run_destructor);
1863 assert!(!alloc::format!("{p:?}").is_empty());
1865 }
1866
1867 #[cfg(feature = "std")]
1868 #[test]
1869 fn std_instant_clone_deep_copies_and_arms_the_destructor() {
1870 let base = StdInstant::now();
1871 let a: InstantPtr = base.into();
1872 let cloned = std_instant_clone(core::ptr::from_ref(&a));
1873 assert_eq!(cloned.get(), base);
1874 assert!(!core::ptr::eq(&**a.ptr, &**cloned.ptr));
1876 assert!(cloned.run_destructor, "clone handed back a disarmed destructor");
1877 drop(cloned);
1878 assert_eq!(a.get(), base);
1880 }
1881
1882 #[cfg(feature = "std")]
1883 #[test]
1884 fn std_instant_drop_is_a_noop_even_for_null() {
1885 std_instant_drop(core::ptr::null_mut());
1889
1890 let mut p: InstantPtr = StdInstant::now().into();
1891 let before = p.get();
1892 std_instant_drop(core::ptr::from_mut(&mut p));
1893 assert_eq!(p.get(), before);
1895 assert!(p.run_destructor);
1896 }
1897
1898 #[test]
1903 fn duration_display_tick_edge_values() {
1904 assert_eq!(alloc::format!("{}", tick_dur(0)), "0 ticks");
1905 assert_eq!(alloc::format!("{}", tick_dur(1)), "1 ticks");
1906 assert_eq!(
1907 alloc::format!("{}", tick_dur(u64::MAX)),
1908 "18446744073709551615 ticks"
1909 );
1910 }
1911
1912 #[cfg(feature = "std")]
1913 #[test]
1914 fn duration_display_system_edge_values_do_not_panic() {
1915 for d in [
1918 sys_dur(0, 0),
1919 sys_dur(1, 500_000_000),
1920 sys_dur(0, u32::MAX),
1921 sys_dur(u64::MAX, NANOS_PER_SEC - 1),
1922 Duration::max(),
1923 ] {
1924 let s = alloc::format!("{d}");
1925 assert!(!s.is_empty());
1926 assert!(!s.ends_with("ticks"), "System duration formatted as ticks: {s}");
1927 }
1928 }
1929
1930 #[cfg(feature = "std")]
1935 #[test]
1936 fn duration_max_is_the_upper_bound() {
1937 let m = Duration::max();
1938 assert_eq!(m, sys_dur(u64::MAX, NANOS_PER_SEC - 1));
1939 assert!(m.greater_than(&sys_dur(u64::MAX, NANOS_PER_SEC - 2)));
1941 assert!(m.greater_than(&sys_dur(0, 0)));
1942 assert!(!m.greater_than(&m));
1944 assert!(!m.smaller_than(&m));
1945 let Duration::System(inner) = m else {
1947 panic!("Duration::max() is not a System duration under std")
1948 };
1949 assert_eq!(inner.get(), StdDuration::new(u64::MAX, NANOS_PER_SEC - 1));
1950 }
1951
1952 #[test]
1953 fn duration_div_by_zero_yields_inf_or_nan_not_a_panic() {
1954 assert!(tick_dur(0).div(&tick_dur(0)).is_nan());
1956 let inf = tick_dur(5).div(&tick_dur(0));
1957 assert!(inf.is_infinite() && inf.is_sign_positive());
1958
1959 assert!(sys_dur(0, 0).div(&sys_dur(0, 0)).is_nan());
1960 let sinf = sys_dur(1, 0).div(&sys_dur(0, 0));
1961 assert!(sinf.is_infinite() && sinf.is_sign_positive());
1962 }
1963
1964 #[test]
1965 fn duration_div_extremes_stay_finite_in_f32() {
1966 let r = tick_dur(u64::MAX).div(&tick_dur(1));
1969 assert!(r.is_finite(), "u64::MAX tick ratio overflowed f32: {r}");
1970 assert!(r > 1e19);
1971 assert_eq!(tick_dur(u64::MAX).div(&tick_dur(u64::MAX)), 1.0);
1973 assert_eq!(sys_dur(3, 0).div(&sys_dur(2, 0)), 1.5);
1974 }
1975
1976 #[test]
1980 fn duration_div_across_kinds_converts_both_ways() {
1981 assert!((sys_dur(1, 0).div(&tick_dur(10)) - 6.0).abs() < 1e-5);
1982 assert!((tick_dur(10).div(&sys_dur(1, 0)) - (1.0 / 6.0)).abs() < 1e-5);
1983 }
1984
1985 #[test]
1986 fn duration_min_picks_the_smaller_of_the_same_kind() {
1987 assert_eq!(tick_dur(5).min(tick_dur(10)), tick_dur(5));
1988 assert_eq!(tick_dur(10).min(tick_dur(5)), tick_dur(5));
1989 assert_eq!(tick_dur(7).min(tick_dur(7)), tick_dur(7));
1990 assert_eq!(tick_dur(0).min(tick_dur(u64::MAX)), tick_dur(0));
1991 assert_eq!(sys_dur(1, 0).min(sys_dur(1, 1)), sys_dur(1, 0));
1993 }
1994
1995 #[test]
2000 fn duration_min_across_kinds_picks_the_genuinely_shorter_span() {
2001 assert_eq!(tick_dur(5).min(sys_dur(1, 0)), tick_dur(5));
2003 assert_eq!(sys_dur(1, 0).min(tick_dur(5)), tick_dur(5));
2004 assert_eq!(tick_dur(120).min(sys_dur(1, 0)), sys_dur(1, 0));
2006 assert_eq!(sys_dur(1, 0).min(tick_dur(120)), sys_dur(1, 0));
2007 }
2008
2009 #[test]
2010 fn duration_comparison_is_a_strict_total_order_within_a_kind() {
2011 let mut pairs = alloc::vec![(tick_dur(0), tick_dur(u64::MAX)), (tick_dur(1), tick_dur(2))];
2012 pairs.extend_from_slice(&[
2016 (sys_dur(0, 0), sys_dur(u64::MAX, 0)),
2017 (sys_dur(1, 999_999_999), sys_dur(2, 0)),
2018 ]);
2019
2020 for (a, b) in pairs {
2021 assert!(a.smaller_than(&b));
2022 assert!(b.greater_than(&a));
2023 assert!(!a.greater_than(&b));
2024 assert!(!b.smaller_than(&a));
2025 }
2026 let eq = tick_dur(4);
2028 assert!(!eq.greater_than(&eq));
2029 assert!(!eq.smaller_than(&eq));
2030 let eq_sys = sys_dur(4, 2);
2031 assert!(!eq_sys.greater_than(&eq_sys));
2032 assert!(!eq_sys.smaller_than(&eq_sys));
2033 }
2034
2035 #[cfg(feature = "std")]
2036 #[test]
2037 fn duration_comparison_normalizes_denormalized_nanos() {
2038 let denorm = sys_dur(0, u32::MAX);
2041 assert!(denorm.greater_than(&sys_dur(4, 0)));
2042 assert!(denorm.smaller_than(&sys_dur(5, 0)));
2043 }
2044
2045 #[test]
2050 fn system_tick_diff_div_edge_cases() {
2051 let zero = SystemTickDiff { tick_diff: 0 };
2052 let one = SystemTickDiff { tick_diff: 1 };
2053 let max = SystemTickDiff { tick_diff: u64::MAX };
2054
2055 assert!(zero.div(&zero).is_nan());
2056 assert!(one.div(&zero).is_infinite());
2057 assert_eq!(zero.div(&one), 0.0);
2058 assert_eq!(max.div(&max), 1.0);
2059 assert!(max.div(&one).is_finite());
2060 assert_eq!(SystemTickDiff { tick_diff: 5 }.div(&SystemTickDiff { tick_diff: 10 }), 0.5);
2061 }
2062
2063 #[test]
2064 fn system_time_diff_as_secs_f64_is_exact_for_representable_values() {
2065 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.as_secs_f64(), 0.0);
2066 assert_eq!(SystemTimeDiff { secs: 1, nanos: 500_000_000 }.as_secs_f64(), 1.5);
2067 assert_eq!(SystemTimeDiff { secs: 0, nanos: 500_000_000 }.as_secs_f64(), 0.5);
2068 let huge = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2070 assert!(huge.as_secs_f64().is_finite());
2071 assert!(huge.as_secs_f64() > 1e19);
2072 assert!(
2074 SystemTimeDiff::from_secs(2).as_secs_f64() > SystemTimeDiff::from_secs(1).as_secs_f64()
2075 );
2076 }
2077
2078 #[test]
2079 fn system_time_diff_div_edge_cases() {
2080 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
2081 let one = SystemTimeDiff::from_secs(1);
2082 let half = SystemTimeDiff { secs: 0, nanos: 500_000_000 };
2083
2084 assert!(zero.div(&zero).is_nan());
2085 assert!(one.div(&zero).is_infinite());
2086 assert_eq!(zero.div(&one), 0.0);
2087 assert_eq!(one.div(&one), 1.0);
2088 assert_eq!(one.div(&half), 2.0);
2089 let max = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2090 assert_eq!(max.div(&max), 1.0);
2091 assert!(max.div(&one).is_finite());
2092 }
2093
2094 #[test]
2099 fn from_secs_invariants() {
2100 for s in [0_u64, 1, 1_000, u64::MAX] {
2101 let d = SystemTimeDiff::from_secs(s);
2102 assert_eq!(d.secs, s);
2103 assert_eq!(d.nanos, 0, "from_secs must leave nanos at zero");
2104 }
2105 }
2106
2107 #[test]
2108 fn from_millis_normalizes_and_keeps_nanos_in_range() {
2109 assert_eq!(SystemTimeDiff::from_millis(0), SystemTimeDiff { secs: 0, nanos: 0 });
2110 assert_eq!(
2111 SystemTimeDiff::from_millis(999),
2112 SystemTimeDiff { secs: 0, nanos: 999_000_000 }
2113 );
2114 assert_eq!(SystemTimeDiff::from_millis(1_000), SystemTimeDiff { secs: 1, nanos: 0 });
2115 assert_eq!(
2116 SystemTimeDiff::from_millis(1_500),
2117 SystemTimeDiff { secs: 1, nanos: 500_000_000 }
2118 );
2119 let max = SystemTimeDiff::from_millis(u64::MAX);
2121 assert!(max.nanos < NANOS_PER_SEC, "from_millis produced denormalized nanos");
2122 assert_eq!(max.secs, u64::MAX / MILLIS_PER_SEC);
2123 }
2124
2125 #[test]
2126 fn from_nanos_normalizes_and_keeps_nanos_in_range() {
2127 assert_eq!(SystemTimeDiff::from_nanos(0), SystemTimeDiff { secs: 0, nanos: 0 });
2128 assert_eq!(
2129 SystemTimeDiff::from_nanos(999_999_999),
2130 SystemTimeDiff { secs: 0, nanos: 999_999_999 }
2131 );
2132 assert_eq!(
2133 SystemTimeDiff::from_nanos(1_000_000_000),
2134 SystemTimeDiff { secs: 1, nanos: 0 }
2135 );
2136 for n in [0_u64, 1, 999_999_999, 1_000_000_001, u64::MAX] {
2137 let d = SystemTimeDiff::from_nanos(n);
2138 assert!(d.nanos < NANOS_PER_SEC, "from_nanos({n}) produced denormalized nanos");
2139 let back =
2141 u128::from(d.secs) * u128::from(NANOS_PER_SEC) + u128::from(d.nanos);
2142 assert_eq!(back, u128::from(n), "from_nanos({n}) lost information");
2143 }
2144 }
2145
2146 #[test]
2151 fn millis_round_trips_through_from_millis() {
2152 for m in [0_u64, 1, 999, 1_000, 1_500, 86_400_000, u64::MAX] {
2155 assert_eq!(
2156 SystemTimeDiff::from_millis(m).millis(),
2157 m,
2158 "from_millis({m}).millis() is not lossless"
2159 );
2160 }
2161 }
2162
2163 #[test]
2164 fn millis_truncates_and_saturates_instead_of_panicking() {
2165 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999 }.millis(), 0);
2167 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999_999 }.millis(), 999);
2168 assert_eq!(SystemTimeDiff { secs: u64::MAX, nanos: 0 }.millis(), u64::MAX);
2170 assert_eq!(
2171 SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 }.millis(),
2172 u64::MAX
2173 );
2174 assert_eq!(SystemTimeDiff::from_secs(u64::MAX / 1_000).millis(), (u64::MAX / 1_000) * 1_000);
2175 }
2176
2177 #[test]
2182 fn checked_add_carries_nanos_into_secs() {
2183 let a = SystemTimeDiff { secs: 0, nanos: 999_999_999 };
2184 let sum = a.checked_add(a).expect("0.999s + 0.999s must not overflow");
2185 assert_eq!(sum, SystemTimeDiff { secs: 1, nanos: 999_999_998 });
2186 let b = SystemTimeDiff { secs: 1, nanos: 500_000_000 };
2188 assert_eq!(
2189 b.checked_add(b),
2190 Some(SystemTimeDiff { secs: 3, nanos: 0 })
2191 );
2192 }
2193
2194 #[test]
2195 fn checked_add_returns_none_on_overflow_instead_of_panicking() {
2196 let max_secs = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
2197 assert_eq!(max_secs.checked_add(SystemTimeDiff::from_secs(1)), None);
2199 assert_eq!(
2201 max_secs.checked_add(SystemTimeDiff { secs: 0, nanos: NANOS_PER_SEC - 1 }),
2202 Some(SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 })
2203 );
2204 let brim = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2206 assert_eq!(brim.checked_add(SystemTimeDiff { secs: 0, nanos: 1 }), None);
2207 }
2208
2209 #[test]
2210 fn checked_add_identity_and_commutativity() {
2211 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
2212 for d in [
2213 SystemTimeDiff::from_secs(0),
2214 SystemTimeDiff::from_millis(1_500),
2215 SystemTimeDiff::from_nanos(u64::MAX),
2216 SystemTimeDiff { secs: u64::MAX, nanos: 0 },
2217 ] {
2218 assert_eq!(d.checked_add(zero), Some(d));
2219 assert_eq!(zero.checked_add(d), Some(d));
2220 let other = SystemTimeDiff::from_millis(750);
2222 assert_eq!(d.checked_add(other), other.checked_add(d));
2223 }
2224 }
2225
2226 #[cfg(feature = "std")]
2231 #[test]
2232 fn system_time_diff_get_round_trips_std_duration() {
2233 for std_d in [
2234 StdDuration::ZERO,
2235 StdDuration::from_millis(1_500),
2236 StdDuration::from_nanos(1),
2237 StdDuration::new(u64::MAX, NANOS_PER_SEC - 1),
2238 ] {
2239 let mid: SystemTimeDiff = std_d.into();
2240 assert_eq!(mid.get(), std_d, "StdDuration -> SystemTimeDiff -> StdDuration lost data");
2241 }
2242 }
2243
2244 #[cfg(feature = "std")]
2245 #[test]
2246 fn system_time_diff_get_on_edge_values_does_not_panic() {
2247 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.get(), StdDuration::ZERO);
2248 assert_eq!(
2250 SystemTimeDiff::from_secs(u64::MAX).get(),
2251 StdDuration::new(u64::MAX, 0)
2252 );
2253 assert_eq!(
2255 SystemTimeDiff { secs: 0, nanos: u32::MAX }.get(),
2256 StdDuration::new(0, u32::MAX)
2257 );
2258 }
2259
2260 #[cfg(feature = "std")]
2265 extern "C" fn test_thread_recv(ptr: *const c_void) -> OptionThreadSendMsg {
2266 let receiver = unsafe { &*(ptr.cast::<Receiver<ThreadSendMsg>>()) };
2269 receiver.try_recv().ok().into()
2270 }
2271
2272 #[cfg(feature = "std")]
2273 const extern "C" fn test_thread_recv_destructor(_: *mut ThreadReceiverInner) {}
2274
2275 #[cfg(feature = "std")]
2276 fn test_receiver() -> (Sender<ThreadSendMsg>, ThreadReceiver) {
2277 let (tx, rx) = std::sync::mpsc::channel::<ThreadSendMsg>();
2278 let inner = ThreadReceiverInner {
2279 ptr: Box::new(rx),
2280 recv_fn: ThreadRecvCallback { cb: test_thread_recv },
2281 destructor: ThreadReceiverDestructorCallback {
2282 cb: test_thread_recv_destructor,
2283 },
2284 };
2285 (tx, ThreadReceiver::new(inner))
2286 }
2287
2288 #[cfg(feature = "std")]
2289 #[test]
2290 fn thread_receiver_new_arms_destructor_and_has_no_ctx() {
2291 let (_tx, r) = test_receiver();
2292 assert!(r.run_destructor, "ThreadReceiver::new left the destructor disarmed");
2293 assert!(r.get_ctx().is_none(), "a fresh receiver must have no FFI context");
2294 }
2295
2296 #[cfg(feature = "std")]
2297 #[test]
2298 fn thread_receiver_recv_on_empty_and_disconnected_channel_is_none() {
2299 let (tx, mut r) = test_receiver();
2300 assert!(r.recv().is_none());
2302 drop(tx);
2304 assert!(r.recv().is_none());
2305 assert!(r.recv().is_none());
2306 }
2307
2308 #[cfg(feature = "std")]
2309 #[test]
2310 fn thread_receiver_recv_delivers_messages_in_order() {
2311 let (tx, mut r) = test_receiver();
2312 tx.send(ThreadSendMsg::Tick).unwrap();
2313 tx.send(ThreadSendMsg::Custom(RefAny::new(42_u32))).unwrap();
2314 tx.send(ThreadSendMsg::TerminateThread).unwrap();
2315
2316 assert_eq!(r.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
2317 assert!(matches!(
2318 r.recv(),
2319 OptionThreadSendMsg::Some(ThreadSendMsg::Custom(_))
2320 ));
2321 assert_eq!(
2322 r.recv(),
2323 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
2324 );
2325 assert!(r.recv().is_none());
2327 }
2328
2329 #[cfg(feature = "std")]
2330 #[test]
2331 fn thread_receiver_clone_shares_the_same_channel() {
2332 let (tx, mut a) = test_receiver();
2333 let mut b = a.clone();
2334 assert!(b.run_destructor);
2335
2336 tx.send(ThreadSendMsg::Tick).unwrap();
2337 assert_eq!(a.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
2341 assert!(b.recv().is_none());
2342
2343 tx.send(ThreadSendMsg::TerminateThread).unwrap();
2344 assert_eq!(
2345 b.recv(),
2346 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
2347 );
2348 assert!(a.recv().is_none());
2349 }
2350
2351 #[cfg(feature = "std")]
2352 #[test]
2353 fn thread_receiver_get_ctx_clones_rather_than_takes() {
2354 let (_tx, mut r) = test_receiver();
2355 r.ctx = OptionRefAny::Some(RefAny::new(7_u64));
2356 assert!(r.get_ctx().is_some());
2359 assert!(r.get_ctx().is_some());
2360 let held = r.get_ctx();
2361 drop(r);
2362 assert!(held.is_some());
2364 }
2365}