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 USER_TIMER_ID_START: usize = 0x0100;
106
107static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
109
110#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
112#[repr(C)]
113pub struct TimerId {
114 pub id: usize,
115}
116
117impl TimerId {
118 #[must_use]
120 pub fn unique() -> Self {
121 Self {
122 id: MAX_TIMER_ID.fetch_add(1, Ordering::SeqCst),
123 }
124 }
125}
126
127impl_option!(
128 TimerId,
129 OptionTimerId,
130 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
131);
132
133impl_vec!(TimerId, TimerIdVec, TimerIdVecDestructor, TimerIdVecDestructorType, TimerIdVecSlice, OptionTimerId);
134impl_vec_debug!(TimerId, TimerIdVec);
135impl_vec_clone!(TimerId, TimerIdVec, TimerIdVecDestructor);
136impl_vec_partialeq!(TimerId, TimerIdVec);
137impl_vec_partialord!(TimerId, TimerIdVec);
138
139const RESERVED_THREAD_ID_COUNT: usize = 5;
142static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
143
144#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
146#[repr(C)]
147pub struct ThreadId {
148 id: usize,
149}
150
151impl_option!(
152 ThreadId,
153 OptionThreadId,
154 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
155);
156
157impl_vec!(ThreadId, ThreadIdVec, ThreadIdVecDestructor, ThreadIdVecDestructorType, ThreadIdVecSlice, OptionThreadId);
158impl_vec_debug!(ThreadId, ThreadIdVec);
159impl_vec_clone!(ThreadId, ThreadIdVec, ThreadIdVecDestructor);
160impl_vec_partialeq!(ThreadId, ThreadIdVec);
161impl_vec_partialord!(ThreadId, ThreadIdVec);
162
163impl ThreadId {
164 #[must_use]
166 pub fn unique() -> Self {
167 Self {
168 id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
169 }
170 }
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
177#[repr(C, u8)]
178pub enum Instant {
179 System(InstantPtr),
181 Tick(SystemTick),
183}
184
185#[cfg(feature = "std")]
186impl From<StdInstant> for Instant {
187 fn from(s: StdInstant) -> Self {
188 Self::System(s.into())
189 }
190}
191
192#[cfg(feature = "std")]
193std::thread_local! {
194 static TEST_CLOCK_OFFSET_MS: core::cell::Cell<u64> = const { core::cell::Cell::new(0) };
225}
226
227#[cfg(feature = "std")]
230#[must_use]
231pub fn advance_test_clock_ms(ms: u64) -> u64 {
232 TEST_CLOCK_OFFSET_MS.with(|c| {
233 let next = c.get().saturating_add(ms);
234 c.set(next);
235 next
236 })
237}
238
239#[cfg(feature = "std")]
242#[must_use]
243pub fn test_clock_offset_ms() -> u64 {
244 TEST_CLOCK_OFFSET_MS.with(core::cell::Cell::get)
245}
246
247#[cfg(feature = "std")]
248#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
249std::thread_local! {
250 static TEST_CLOCK_BASE: core::cell::Cell<Option<StdInstant>> =
254 const { core::cell::Cell::new(None) };
255}
256
257#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
283pub fn freeze_test_clock() {
284 TEST_CLOCK_BASE.with(|c| {
285 if c.get().is_none() {
286 c.set(Some(StdInstant::now()));
287 }
288 });
289}
290
291#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
293#[must_use]
294pub fn test_clock_is_frozen() -> bool {
295 TEST_CLOCK_BASE.with(core::cell::Cell::get).is_some()
296}
297
298#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
307pub fn reset_test_clock() {
308 TEST_CLOCK_OFFSET_MS.with(|c| c.set(0));
309 TEST_CLOCK_BASE.with(|c| c.set(None));
310}
311
312#[cfg(feature = "std")]
324static SYSTEM_TICK: core::sync::atomic::AtomicU64 = core::sync::atomic::AtomicU64::new(0);
325
326#[cfg(feature = "std")]
329pub fn advance_system_tick() {
330 SYSTEM_TICK.fetch_add(1, Ordering::Relaxed);
331}
332
333#[cfg(feature = "std")]
335#[must_use]
336pub fn system_tick_now() -> u64 {
337 SYSTEM_TICK.load(Ordering::Relaxed)
338}
339
340#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
351fn std_now_with_test_offset() -> StdInstant {
352 let offset = test_clock_offset_ms();
353 if let Some(base) = TEST_CLOCK_BASE.with(core::cell::Cell::get) {
354 return base + core::time::Duration::from_millis(offset);
355 }
356 if offset == 0 {
357 StdInstant::now()
358 } else {
359 StdInstant::now() + core::time::Duration::from_millis(offset)
360 }
361}
362
363impl Instant {
364 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
369 #[must_use] pub fn now() -> Self {
370 std_now_with_test_offset().into()
371 }
372
373 #[cfg(all(feature = "std", target_arch = "wasm32"))]
388 #[must_use] pub fn now() -> Self {
389 Instant::Tick(SystemTick::new(system_tick_now()))
390 }
391
392 #[cfg(not(feature = "std"))]
394 pub fn now() -> Self {
395 Instant::Tick(SystemTick::new(0))
396 }
397
398 #[must_use] pub fn linear_interpolate(&self, mut start: Self, mut end: Self) -> f32 {
401 use core::mem;
402
403 if end < start {
404 mem::swap(&mut start, &mut end);
405 }
406
407 if *self < start {
408 return 0.0;
409 }
410 if *self > end {
411 return 1.0;
412 }
413
414 if start == end {
418 return 1.0;
419 }
420
421 let duration_total = end.duration_since(&start);
422 let duration_current = self.duration_since(&start);
423
424 let ratio = duration_current.div(&duration_total);
425 if ratio.is_nan() {
426 return 1.0;
427 }
428 ratio.clamp(0.0, 1.0)
429 }
430
431 #[must_use] pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
450 duration.map_or_else(|| self.clone(), |d| match (self, d) {
451 (Self::System(i), Duration::System(d)) => {
452 #[cfg(feature = "std")]
453 {
454 let s: StdInstant = i.clone().into();
455 let d: StdDuration = (*d).into();
456 let new: InstantPtr = (s + d).into();
457 Self::System(new)
458 }
459 #[cfg(not(feature = "std"))]
460 {
461 let _ = (i, d);
465 self.clone()
466 }
467 }
468 (Self::Tick(s), Duration::Tick(d)) => Self::Tick(SystemTick {
469 tick_counter: s.tick_counter.saturating_add(d.tick_diff),
471 }),
472 (Self::System(_), Duration::Tick(_)) => {
476 self.add_optional_duration(Some(&Duration::System(
477 SystemTimeDiff::from_nanos_u128(d.as_nanos()),
478 )))
479 }
480 (Self::Tick(s), Duration::System(_)) => Self::Tick(SystemTick {
484 tick_counter: s.tick_counter.saturating_add(d.as_ticks()),
485 }),
486 })
487 }
488
489 #[cfg(feature = "std")]
491 #[must_use] pub fn into_std_instant(self) -> StdInstant {
492 match self {
493 Self::System(s) => s.into(),
494 Self::Tick(_) => unreachable!(),
495 }
496 }
497
498 #[must_use] pub fn duration_since(&self, earlier: &Self) -> Duration {
504 match (earlier, self) {
505 (Self::System(prev), Self::System(now)) => {
506 #[cfg(feature = "std")]
507 {
508 let prev_instant: StdInstant = prev.clone().into();
509 let now_instant: StdInstant = now.clone().into();
510 Duration::System(now_instant.saturating_duration_since(prev_instant).into())
513 }
514 #[cfg(not(feature = "std"))]
515 {
516 let _ = (prev, now);
518 Duration::Tick(SystemTickDiff { tick_diff: 0 })
519 }
520 }
521 (
522 Self::Tick(SystemTick { tick_counter: prev }),
523 Self::Tick(SystemTick { tick_counter: now }),
524 ) => Duration::Tick(SystemTickDiff {
525 tick_diff: now.saturating_sub(*prev),
527 }),
528 _ => Duration::Tick(SystemTickDiff { tick_diff: 0 }),
530 }
531 }
532}
533
534#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
538#[repr(C)]
539pub struct SystemTick {
540 pub tick_counter: u64,
541}
542
543impl SystemTick {
544 #[must_use] pub const fn new(tick_counter: u64) -> Self {
546 Self { tick_counter }
547 }
548}
549
550#[repr(C)]
554pub struct InstantPtr {
555 #[cfg(feature = "std")]
563 pub ptr: ManuallyDrop<Box<StdInstant>>,
564 #[cfg(not(feature = "std"))]
565 pub ptr: *const c_void,
566 pub clone_fn: InstantPtrCloneCallback,
567 pub destructor: InstantPtrDestructorCallback,
568 pub run_destructor: bool,
569}
570
571pub type InstantPtrCloneCallbackType = extern "C" fn(*const InstantPtr) -> InstantPtr;
572#[repr(C)]
573pub struct InstantPtrCloneCallback {
574 pub cb: InstantPtrCloneCallbackType,
575}
576impl_callback_simple!(InstantPtrCloneCallback);
577
578pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
579#[repr(C)]
580pub struct InstantPtrDestructorCallback {
581 pub cb: InstantPtrDestructorCallbackType,
582}
583impl_callback_simple!(InstantPtrDestructorCallback);
584
585#[cfg(feature = "std")]
587impl fmt::Debug for InstantPtr {
588 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
589 write!(f, "{:?}", self.get())
590 }
591}
592
593#[cfg(not(feature = "std"))]
594impl core::fmt::Debug for InstantPtr {
595 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
596 write!(f, "{:?}", self.ptr as usize)
597 }
598}
599
600#[cfg(feature = "std")]
601impl core::hash::Hash for InstantPtr {
602 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
603 self.get().hash(state);
604 }
605}
606
607#[cfg(not(feature = "std"))]
608impl core::hash::Hash for InstantPtr {
609 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
610 (self.ptr as usize).hash(state);
611 }
612}
613
614#[cfg(feature = "std")]
615impl PartialEq for InstantPtr {
616 fn eq(&self, other: &Self) -> bool {
617 self.get() == other.get()
618 }
619}
620
621#[cfg(not(feature = "std"))]
622impl PartialEq for InstantPtr {
623 fn eq(&self, other: &InstantPtr) -> bool {
624 (self.ptr as usize).eq(&(other.ptr as usize))
625 }
626}
627
628impl Eq for InstantPtr {}
629
630#[cfg(feature = "std")]
631impl PartialOrd for InstantPtr {
632 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
633 Some((self.get()).cmp(&(other.get())))
634 }
635}
636
637#[cfg(not(feature = "std"))]
638impl PartialOrd for InstantPtr {
639 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
640 Some((self.ptr as usize).cmp(&(other.ptr as usize)))
641 }
642}
643
644#[cfg(feature = "std")]
645impl Ord for InstantPtr {
646 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
647 (self.get()).cmp(&(other.get()))
648 }
649}
650
651#[cfg(not(feature = "std"))]
652impl Ord for InstantPtr {
653 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
654 (self.ptr as usize).cmp(&(other.ptr as usize))
655 }
656}
657
658#[cfg(feature = "std")]
659impl InstantPtr {
660 fn get(&self) -> StdInstant {
661 (**self.ptr)
662 }
663}
664
665impl Clone for InstantPtr {
666 fn clone(&self) -> Self {
667 (self.clone_fn.cb)(self)
668 }
669}
670
671#[cfg(feature = "std")]
672extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
673 let az_instant_ptr = unsafe { &*ptr };
674 InstantPtr {
675 ptr: ManuallyDrop::new((*az_instant_ptr.ptr).clone()),
676 clone_fn: az_instant_ptr.clone_fn,
677 destructor: az_instant_ptr.destructor,
678 run_destructor: true,
679 }
680}
681
682#[cfg(feature = "std")]
683impl From<StdInstant> for InstantPtr {
684 fn from(s: StdInstant) -> Self {
685 Self {
686 ptr: ManuallyDrop::new(Box::new(s)),
687 clone_fn: InstantPtrCloneCallback {
688 cb: std_instant_clone,
689 },
690 destructor: InstantPtrDestructorCallback {
691 cb: std_instant_drop,
692 },
693 run_destructor: true,
694 }
695 }
696}
697
698#[cfg(feature = "std")]
699impl From<InstantPtr> for StdInstant {
700 fn from(s: InstantPtr) -> Self {
701 s.get()
702 }
703}
704
705impl Drop for InstantPtr {
706 fn drop(&mut self) {
707 if self.run_destructor {
708 self.run_destructor = false;
709 (self.destructor.cb)(self);
710 #[cfg(feature = "std")]
719 unsafe {
720 ManuallyDrop::drop(&mut self.ptr);
721 }
722 }
723 }
724}
725
726#[cfg(feature = "std")]
727const extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
728
729#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
736#[repr(C, u8)]
737pub enum Duration {
738 System(SystemTimeDiff),
740 Tick(SystemTickDiff),
742}
743
744impl fmt::Display for Duration {
745 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
746 match self {
747 #[cfg(feature = "std")]
748 Self::System(s) => {
749 let s: StdDuration = (*s).into();
750 write!(f, "{s:?}")
751 }
752 #[cfg(not(feature = "std"))]
753 Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
754 Self::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
755 }
756 }
757}
758
759#[cfg(feature = "std")]
760impl From<StdDuration> for Duration {
761 fn from(s: StdDuration) -> Self {
762 Self::System(s.into())
763 }
764}
765
766pub use azul_css::props::basic::time::TICKS_PER_SECOND;
772
773impl Duration {
774 #[allow(clippy::cast_lossless)]
788 #[must_use]
789 pub const fn as_nanos(&self) -> u128 {
790 match self {
791 Self::System(s) => (s.secs as u128) * (NANOS_PER_SEC as u128) + (s.nanos as u128),
792 Self::Tick(t) => (t.tick_diff as u128) * (NANOS_PER_SEC as u128) / (TICKS_PER_SECOND as u128),
793 }
794 }
795
796 #[must_use]
798 pub const fn from_millis(ms: u64) -> Self {
799 Self::System(SystemTimeDiff::from_millis(ms))
800 }
801
802 #[must_use]
805 pub const fn from_ticks(ticks: u64) -> Self {
806 Self::Tick(SystemTickDiff { tick_diff: ticks })
807 }
808
809 #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
817 #[must_use]
818 pub const fn as_ticks(&self) -> u64 {
819 match self {
820 Self::Tick(t) => t.tick_diff,
821 Self::System(_) => {
822 let ticks = self.as_nanos() * (TICKS_PER_SECOND as u128) / (NANOS_PER_SEC as u128);
823 if ticks > u64::MAX as u128 {
824 u64::MAX
825 } else {
826 ticks as u64
827 }
828 }
829 }
830 }
831
832 #[allow(clippy::cast_lossless, clippy::cast_possible_truncation)]
837 #[must_use]
838 pub const fn as_millis_u64(&self) -> u64 {
839 let ms = self.as_nanos() / (NANOS_PER_MILLI as u128);
840 if ms > u64::MAX as u128 {
841 u64::MAX
842 } else {
843 ms as u64
844 }
845 }
846
847 #[must_use] pub fn max() -> Self {
849 #[cfg(feature = "std")]
850 {
851 Self::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
852 }
853 #[cfg(not(feature = "std"))]
854 {
855 Duration::Tick(SystemTickDiff {
856 tick_diff: u64::MAX,
857 })
858 }
859 }
860
861 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
871 #[must_use] pub fn div(&self, other: &Self) -> f32 {
872 use self::Duration::{System, Tick};
873 match (self, other) {
874 (System(s), System(s2)) => s.div(s2) as f32,
875 (Tick(t), Tick(t2)) => t.div(t2) as f32,
876 _ => (self.as_nanos() as f64 / other.as_nanos() as f64) as f32,
879 }
880 }
881
882 #[must_use] pub const fn min(self, other: Self) -> Self {
884 if self.smaller_than(&other) {
885 self
886 } else {
887 other
888 }
889 }
890
891 #[must_use] pub const fn greater_than(&self, other: &Self) -> bool {
915 self.as_nanos() > other.as_nanos()
916 }
917
918 #[must_use] pub const fn smaller_than(&self, other: &Self) -> bool {
923 self.as_nanos() < other.as_nanos()
924 }
925}
926
927#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
930#[repr(C)]
931pub struct SystemTickDiff {
932 pub tick_diff: u64,
933}
934
935impl SystemTickDiff {
936 #[allow(clippy::cast_precision_loss)]
940 #[must_use] pub fn div(&self, other: &Self) -> f64 {
941 self.tick_diff as f64 / other.tick_diff as f64
942 }
943}
944
945#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
947#[repr(C)]
948pub struct SystemTimeDiff {
949 pub secs: u64,
950 pub nanos: u32,
951}
952
953impl SystemTimeDiff {
954 #[must_use] pub fn div(&self, other: &Self) -> f64 {
957 self.as_secs_f64() / other.as_secs_f64()
958 }
959 #[allow(clippy::cast_precision_loss)]
961 fn as_secs_f64(&self) -> f64 {
962 (self.secs as f64) + (f64::from(self.nanos) / f64::from(NANOS_PER_SEC))
963 }
964}
965
966#[cfg(feature = "std")]
967impl From<StdDuration> for SystemTimeDiff {
968 fn from(d: StdDuration) -> Self {
969 Self {
970 secs: d.as_secs(),
971 nanos: d.subsec_nanos(),
972 }
973 }
974}
975
976#[cfg(feature = "std")]
977impl From<SystemTimeDiff> for StdDuration {
978 fn from(d: SystemTimeDiff) -> Self {
979 Self::new(d.secs, d.nanos)
980 }
981}
982
983const MILLIS_PER_SEC: u64 = 1_000;
984const NANOS_PER_MILLI: u32 = 1_000_000;
985const NANOS_PER_SEC: u32 = 1_000_000_000;
986
987impl SystemTimeDiff {
988 #[must_use] pub const fn from_secs(secs: u64) -> Self {
990 Self { secs, nanos: 0 }
991 }
992 #[must_use] pub const fn from_millis(millis: u64) -> Self {
994 Self {
995 secs: millis / MILLIS_PER_SEC,
996 nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
997 }
998 }
999 #[allow(clippy::cast_possible_truncation)]
1003 #[must_use] pub const fn from_nanos(nanos: u64) -> Self {
1004 Self {
1005 secs: nanos / (NANOS_PER_SEC as u64),
1006 nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
1007 }
1008 }
1009
1010 #[allow(clippy::cast_possible_truncation, clippy::cast_lossless)]
1019 #[must_use] pub const fn from_nanos_u128(nanos: u128) -> Self {
1020 let secs = nanos / (NANOS_PER_SEC as u128);
1021 if secs > u64::MAX as u128 {
1022 Self {
1023 secs: u64::MAX,
1024 nanos: NANOS_PER_SEC - 1,
1025 }
1026 } else {
1027 Self {
1028 secs: secs as u64,
1029 nanos: (nanos % (NANOS_PER_SEC as u128)) as u32,
1030 }
1031 }
1032 }
1033 #[must_use] pub const fn checked_add(self, rhs: Self) -> Option<Self> {
1035 if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
1036 let mut nanos = self.nanos + rhs.nanos;
1037 if nanos >= NANOS_PER_SEC {
1038 nanos -= NANOS_PER_SEC;
1039 if let Some(new_secs) = secs.checked_add(1) {
1040 secs = new_secs;
1041 } else {
1042 return None;
1043 }
1044 }
1045 Some(Self { secs, nanos })
1046 } else {
1047 None
1048 }
1049 }
1050
1051 #[must_use] pub const fn millis(&self) -> u64 {
1056 self.secs
1057 .saturating_mul(MILLIS_PER_SEC)
1058 .saturating_add((self.nanos / NANOS_PER_MILLI) as u64)
1059 }
1060
1061 #[cfg(feature = "std")]
1063 #[must_use] pub fn get(&self) -> StdDuration {
1064 (*self).into()
1065 }
1066}
1067
1068impl From<azul_css::props::basic::time::CssDuration> for Duration {
1077 fn from(d: azul_css::props::basic::time::CssDuration) -> Self {
1078 use azul_css::props::basic::time::CssDurationUnit;
1079 match d.unit {
1080 CssDurationUnit::Milliseconds => Self::from_millis(u64::from(d.inner)),
1081 CssDurationUnit::Ticks => Self::from_ticks(u64::from(d.inner)),
1082 }
1083 }
1084}
1085
1086impl_option!(
1087 Instant,
1088 OptionInstant,
1089 copy = false,
1090 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1091);
1092impl_option!(
1093 Duration,
1094 OptionDuration,
1095 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
1096);
1097#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
1102#[repr(C, u8)]
1103pub enum ThreadSendMsg {
1104 TerminateThread,
1106 Tick,
1108 Custom(RefAny),
1110}
1111
1112impl_option!(
1113 ThreadSendMsg,
1114 OptionThreadSendMsg,
1115 copy = false,
1116 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
1117);
1118
1119#[derive(Debug)]
1123#[repr(C)]
1124pub struct ThreadReceiver {
1125 #[cfg(feature = "std")]
1126 pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
1127 #[cfg(not(feature = "std"))]
1128 pub ptr: *const c_void,
1129 pub run_destructor: bool,
1130 pub ctx: OptionRefAny,
1132}
1133
1134impl Clone for ThreadReceiver {
1135 fn clone(&self) -> Self {
1136 Self {
1137 ptr: self.ptr.clone(),
1138 run_destructor: true,
1139 ctx: self.ctx.clone(),
1140 }
1141 }
1142}
1143
1144impl Drop for ThreadReceiver {
1145 fn drop(&mut self) {
1146 self.run_destructor = false;
1147 }
1148}
1149
1150impl ThreadReceiver {
1151 #[cfg(not(feature = "std"))]
1153 pub fn new(_t: ThreadReceiverInner) -> Self {
1154 Self {
1155 ptr: core::ptr::null(),
1156 run_destructor: false,
1157 ctx: OptionRefAny::None,
1158 }
1159 }
1160
1161 #[cfg(feature = "std")]
1163 #[must_use] pub fn new(t: ThreadReceiverInner) -> Self {
1164 Self {
1165 ptr: Box::new(Arc::new(Mutex::new(t))),
1166 run_destructor: true,
1167 ctx: OptionRefAny::None,
1168 }
1169 }
1170
1171 #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
1173 self.ctx.clone()
1174 }
1175
1176 #[cfg(not(feature = "std"))]
1178 pub fn recv(&mut self) -> OptionThreadSendMsg {
1179 None.into()
1180 }
1181
1182 #[cfg(feature = "std")]
1184 pub fn recv(&mut self) -> OptionThreadSendMsg {
1185 let Some(ts) = self.ptr.lock().ok() else {
1186 return None.into();
1187 };
1188 (ts.recv_fn.cb)(std::ptr::from_ref(ts.ptr.as_ref()) as *const c_void)
1189 }
1190}
1191
1192#[derive(Debug)]
1194#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
1195#[repr(C)]
1196pub struct ThreadReceiverInner {
1197 #[cfg(feature = "std")]
1198 pub ptr: Box<Receiver<ThreadSendMsg>>,
1199 #[cfg(not(feature = "std"))]
1200 pub ptr: *const c_void,
1201 pub recv_fn: ThreadRecvCallback,
1202 pub destructor: ThreadReceiverDestructorCallback,
1203}
1204
1205#[cfg(not(feature = "std"))]
1206unsafe impl Send for ThreadReceiverInner {}
1207
1208#[cfg(feature = "std")]
1209impl core::hash::Hash for ThreadReceiverInner {
1210 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
1211 (std::ptr::from_ref(self.ptr.as_ref()) as usize).hash(state);
1212 }
1213}
1214
1215#[cfg(feature = "std")]
1216impl PartialEq for ThreadReceiverInner {
1217 fn eq(&self, other: &Self) -> bool {
1218 std::ptr::eq(self.ptr.as_ref(), other.ptr.as_ref())
1219 }
1220}
1221
1222#[cfg(feature = "std")]
1223impl Eq for ThreadReceiverInner {}
1224
1225#[cfg(feature = "std")]
1226impl PartialOrd for ThreadReceiverInner {
1227 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
1228 Some(
1229 (std::ptr::from_ref(self.ptr.as_ref()) as usize)
1230 .cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize)),
1231 )
1232 }
1233}
1234
1235#[cfg(feature = "std")]
1236impl Ord for ThreadReceiverInner {
1237 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
1238 (std::ptr::from_ref(self.ptr.as_ref()) as usize).cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize))
1239 }
1240}
1241
1242impl Drop for ThreadReceiverInner {
1243 fn drop(&mut self) {
1244 (self.destructor.cb)(self);
1245 }
1246}
1247
1248pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
1251#[repr(C)]
1252pub struct GetSystemTimeCallback {
1253 pub cb: GetSystemTimeCallbackType,
1254}
1255impl_callback_simple!(GetSystemTimeCallback);
1256
1257#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1262#[must_use] pub extern "C" fn get_system_time_libstd() -> Instant {
1263 std_now_with_test_offset().into()
1265}
1266
1267#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
1269pub extern "C" fn get_system_time_libstd() -> Instant {
1270 Instant::Tick(SystemTick::new(0))
1271}
1272
1273pub type CheckThreadFinishedCallbackType =
1275 extern "C" fn(*const c_void) -> bool;
1276#[repr(C)]
1278pub struct CheckThreadFinishedCallback {
1279 pub cb: CheckThreadFinishedCallbackType,
1280}
1281impl_callback_simple!(CheckThreadFinishedCallback);
1282
1283pub type LibrarySendThreadMsgCallbackType =
1285 extern "C" fn(*const c_void, ThreadSendMsg) -> bool;
1286#[repr(C)]
1288pub struct LibrarySendThreadMsgCallback {
1289 pub cb: LibrarySendThreadMsgCallbackType,
1290}
1291impl_callback_simple!(LibrarySendThreadMsgCallback);
1292
1293pub type ThreadRecvCallbackType =
1295 extern "C" fn(*const c_void) -> OptionThreadSendMsg;
1296#[repr(C)]
1298pub struct ThreadRecvCallback {
1299 pub cb: ThreadRecvCallbackType,
1300}
1301impl_callback_simple!(ThreadRecvCallback);
1302
1303pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
1305#[repr(C)]
1307pub struct ThreadReceiverDestructorCallback {
1308 pub cb: ThreadReceiverDestructorCallbackType,
1309}
1310impl_callback_simple!(ThreadReceiverDestructorCallback);
1311
1312#[cfg(test)]
1313#[allow(clippy::float_cmp)] mod tests {
1315 use super::*;
1316
1317 fn tick(n: u64) -> Instant {
1318 Instant::Tick(SystemTick::new(n))
1319 }
1320 fn tick_dur(n: u64) -> Duration {
1321 Duration::Tick(SystemTickDiff { tick_diff: n })
1322 }
1323 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1324 Duration::System(SystemTimeDiff { secs, nanos })
1325 }
1326
1327 #[test]
1332 #[cfg(feature = "std")]
1333 fn test_clock_offset_is_per_thread_not_process_global() {
1334 reset_test_clock();
1335 assert_eq!(test_clock_offset_ms(), 0);
1336
1337 let (tx, rx) = std::sync::mpsc::channel();
1338 let (go_tx, go_rx) = std::sync::mpsc::channel::<()>();
1339 let other = std::thread::spawn(move || {
1340 go_rx.recv().expect("handshake");
1342 let seen_after_main_ticked = test_clock_offset_ms();
1343 let _ = advance_test_clock_ms(7);
1344 tx.send((seen_after_main_ticked, test_clock_offset_ms()))
1345 .expect("send");
1346 });
1347
1348 assert_eq!(advance_test_clock_ms(5_000), 5_000);
1349 go_tx.send(()).expect("handshake");
1350 let (other_before, other_after) = rx.recv().expect("recv");
1351 other.join().expect("join");
1352
1353 assert_eq!(
1354 other_before, 0,
1355 "a tick on the main thread leaked into another thread's clock"
1356 );
1357 assert_eq!(other_after, 7, "the other thread must own its own offset");
1358 assert_eq!(
1359 test_clock_offset_ms(),
1360 5_000,
1361 "another thread's tick leaked into the main thread's clock"
1362 );
1363
1364 reset_test_clock();
1366 assert_eq!(test_clock_offset_ms(), 0);
1367 }
1368
1369 #[test]
1377 #[cfg(feature = "std")]
1378 fn a_frozen_clock_advances_only_by_what_the_scenario_asks_for() {
1379 reset_test_clock();
1380 assert!(!test_clock_is_frozen());
1381
1382 freeze_test_clock();
1383 assert!(test_clock_is_frozen());
1384
1385 let t0 = Instant::now();
1386 std::thread::sleep(core::time::Duration::from_millis(25));
1388 let t1 = Instant::now();
1389 assert_eq!(
1390 t1.duration_since(&t0),
1391 Duration::System(SystemTimeDiff { secs: 0, nanos: 0 }),
1392 "real time leaked into a frozen clock",
1393 );
1394
1395 let _ = advance_test_clock_ms(500);
1397 let t2 = Instant::now();
1398 assert_eq!(
1399 t2.duration_since(&t0),
1400 Duration::System(SystemTimeDiff { secs: 0, nanos: 500_000_000 }),
1401 "a 500 ms tick must read back as exactly 500 ms",
1402 );
1403
1404 freeze_test_clock();
1406 assert_eq!(
1407 Instant::now().duration_since(&t0),
1408 Duration::System(SystemTimeDiff { secs: 0, nanos: 500_000_000 }),
1409 "re-freezing re-based the clock and discarded elapsed virtual time",
1410 );
1411
1412 reset_test_clock();
1415 assert!(!test_clock_is_frozen());
1416 let r0 = Instant::now();
1417 std::thread::sleep(core::time::Duration::from_millis(15));
1418 assert!(
1419 Instant::now().duration_since(&r0)
1420 > Duration::System(SystemTimeDiff { secs: 0, nanos: 0 }),
1421 "reset_test_clock left the clock frozen",
1422 );
1423 }
1424
1425 #[test]
1426 fn linear_interpolate_zero_interval_is_one_not_nan() {
1427 let t = tick(5);
1428 let v = t.linear_interpolate(tick(5), tick(5));
1429 assert!(v.is_finite());
1430 assert_eq!(v, 1.0);
1431 }
1432
1433 #[test]
1434 fn linear_interpolate_midpoint() {
1435 let v = tick(5).linear_interpolate(tick(0), tick(10));
1436 assert!((v - 0.5).abs() < 1e-6);
1437 }
1438
1439 #[test]
1440 fn duration_since_saturates_on_negative() {
1441 let d = tick(1).duration_since(&tick(10));
1443 assert_eq!(d, tick_dur(0));
1444 }
1445
1446 #[test]
1453 fn duration_compare_is_unit_aware_across_ticks_and_wall_clock() {
1454 let five_ticks = tick_dur(5);
1456 let one_second = sys_dur(1, 0);
1457 assert!(five_ticks.smaller_than(&one_second));
1458 assert!(!five_ticks.greater_than(&one_second));
1459 assert!(one_second.greater_than(&five_ticks));
1460 assert!(!one_second.smaller_than(&five_ticks));
1461
1462 assert!(tick_dur(120).greater_than(&one_second));
1464 assert!(one_second.smaller_than(&tick_dur(120)));
1465
1466 assert!(!tick_dur(60).greater_than(&one_second));
1468 assert!(!tick_dur(60).smaller_than(&one_second));
1469 assert!(!one_second.greater_than(&tick_dur(60)));
1470 assert!(!one_second.smaller_than(&tick_dur(60)));
1471 }
1472
1473 #[test]
1477 fn duration_compare_across_units_at_the_extremes() {
1478 assert!(Duration::max().greater_than(&tick_dur(u64::MAX)));
1479 assert!(tick_dur(u64::MAX).smaller_than(&Duration::max()));
1480 assert!(!tick_dur(0).greater_than(&sys_dur(0, 0)));
1481 assert!(!sys_dur(0, 0).greater_than(&tick_dur(0)));
1482 assert!(sys_dur(0, 1).greater_than(&tick_dur(0)));
1484 }
1485
1486 #[test]
1487 fn add_optional_duration_converts_across_units() {
1488 let inst = tick(100);
1489 assert_eq!(inst.add_optional_duration(Some(&sys_dur(1, 0))), tick(160));
1491 assert_eq!(inst.add_optional_duration(Some(&sys_dur(0, 1))), tick(100));
1493 assert_eq!(inst.add_optional_duration(Some(&tick_dur(5))), tick(105));
1495 let big = tick(u64::MAX);
1497 assert_eq!(big.add_optional_duration(Some(&tick_dur(10))), tick(u64::MAX));
1498 assert_eq!(big.add_optional_duration(Some(&Duration::max())), tick(u64::MAX));
1500 }
1501
1502 #[test]
1503 fn millis_saturates_on_overflow() {
1504 let huge = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
1505 assert_eq!(huge.millis(), u64::MAX);
1506 let normal = SystemTimeDiff { secs: 2, nanos: 500_000_000 };
1507 assert_eq!(normal.millis(), 2500);
1508 }
1509
1510 #[test]
1514 fn duration_div_is_unit_aware() {
1515 assert!((tick_dur(30).div(&sys_dur(1, 0)) - 0.5).abs() < 1e-6);
1517 assert!((sys_dur(1, 0).div(&tick_dur(30)) - 2.0).abs() < 1e-6);
1519 assert!((tick_dur(5).div(&tick_dur(10)) - 0.5).abs() < 1e-6);
1521 assert!((sys_dur(1, 0).div(&sys_dur(2, 0)) - 0.5).abs() < 1e-6);
1523 }
1524
1525 #[cfg(feature = "std")]
1531 #[test]
1532 fn instant_ptr_clone_and_drop_no_ub() {
1533 let base = StdInstant::now();
1534 let a: InstantPtr = base.into();
1535 let b = a.clone();
1536 assert_eq!(a, b);
1538 drop(a);
1541 drop(b);
1542 }
1543}
1544
1545#[cfg(test)]
1546#[allow(clippy::float_cmp)] mod autotest_generated {
1548 use super::*;
1549
1550 fn tick(n: u64) -> Instant {
1553 Instant::Tick(SystemTick::new(n))
1554 }
1555 fn tick_dur(n: u64) -> Duration {
1556 Duration::Tick(SystemTickDiff { tick_diff: n })
1557 }
1558 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1559 Duration::System(SystemTimeDiff { secs, nanos })
1560 }
1561
1562 #[test]
1567 fn timer_id_unique_is_strictly_increasing_and_above_reserved_range() {
1568 let a = TimerId::unique();
1569 let b = TimerId::unique();
1570 assert_ne!(a, b);
1571 assert!(b.id > a.id, "unique() must strictly increase: {a:?} -> {b:?}");
1572 for id in [a, b] {
1574 assert!(
1575 id.id >= USER_TIMER_ID_START,
1576 "unique() handed out a reserved system ID: {id:?}"
1577 );
1578 assert_ne!(id, CURSOR_BLINK_TIMER_ID);
1579 assert_ne!(id, SCROLL_MOMENTUM_TIMER_ID);
1580 assert_ne!(id, DRAG_AUTOSCROLL_TIMER_ID);
1581 assert_ne!(id, TOOLTIP_DELAY_TIMER_ID);
1582 assert_ne!(id, CAPABILITY_PUMP_TIMER_ID);
1583 assert_ne!(id, LONG_PRESS_TIMER_ID);
1584 }
1585 }
1586
1587 #[test]
1588 fn thread_id_unique_is_strictly_increasing_and_above_reserved_range() {
1589 let a = ThreadId::unique();
1590 let b = ThreadId::unique();
1591 assert_ne!(a, b);
1592 assert!(b.id > a.id);
1593 assert!(a.id >= RESERVED_THREAD_ID_COUNT);
1594 }
1595
1596 #[cfg(feature = "std")]
1599 #[test]
1600 fn unique_ids_do_not_collide_across_threads() {
1601 use alloc::collections::BTreeSet;
1602
1603 let handles: Vec<_> = (0..8)
1604 .map(|_| {
1605 std::thread::spawn(|| {
1606 let mut out = Vec::new();
1607 for _ in 0..64 {
1608 out.push((TimerId::unique().id, ThreadId::unique().id));
1609 }
1610 out
1611 })
1612 })
1613 .collect();
1614
1615 let mut timer_ids = BTreeSet::new();
1616 let mut thread_ids = BTreeSet::new();
1617 for h in handles {
1618 for (t, th) in h.join().expect("worker thread panicked") {
1619 assert!(timer_ids.insert(t), "duplicate TimerId handed out: {t}");
1620 assert!(thread_ids.insert(th), "duplicate ThreadId handed out: {th}");
1621 }
1622 }
1623 assert_eq!(timer_ids.len(), 8 * 64);
1624 assert_eq!(thread_ids.len(), 8 * 64);
1625 }
1626
1627 #[cfg(feature = "std")]
1632 #[test]
1633 fn instant_now_is_system_and_monotonic() {
1634 let a = Instant::now();
1635 let b = Instant::now();
1636 assert!(matches!(a, Instant::System(_)));
1637 assert!(a <= b, "Instant::now() went backwards");
1638 assert_eq!(a.duration_since(&b), sys_dur(0, 0));
1640 }
1641
1642 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1643 #[test]
1644 fn get_system_time_libstd_is_monotonic_system_instant() {
1645 let a = get_system_time_libstd();
1646 let b = get_system_time_libstd();
1647 assert!(matches!(a, Instant::System(_)));
1648 assert!(matches!(b, Instant::System(_)));
1649 assert!(a <= b);
1650 }
1651
1652 #[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
1653 #[test]
1654 fn get_system_time_libstd_wasm_fallback_is_zero_tick() {
1655 assert_eq!(get_system_time_libstd(), tick(0));
1658 }
1659
1660 #[test]
1665 fn linear_interpolate_clamps_outside_the_interval() {
1666 assert_eq!(tick(0).linear_interpolate(tick(10), tick(20)), 0.0);
1668 assert_eq!(tick(999).linear_interpolate(tick(10), tick(20)), 1.0);
1669 assert_eq!(tick(10).linear_interpolate(tick(10), tick(20)), 0.0);
1671 assert_eq!(tick(20).linear_interpolate(tick(10), tick(20)), 1.0);
1672 }
1673
1674 #[test]
1675 fn linear_interpolate_reversed_interval_is_normalized() {
1676 let forwards = tick(5).linear_interpolate(tick(0), tick(10));
1679 let backwards = tick(5).linear_interpolate(tick(10), tick(0));
1680 assert_eq!(forwards, backwards);
1681 assert!((backwards - 0.5).abs() < 1e-6);
1682 }
1683
1684 #[test]
1685 fn linear_interpolate_saturating_extremes_stay_in_range() {
1686 let v = tick(u64::MAX / 2).linear_interpolate(tick(0), tick(u64::MAX));
1689 assert!(v.is_finite(), "interpolation over the full u64 span went non-finite");
1690 assert!((0.0..=1.0).contains(&v));
1691 assert!((v - 0.5).abs() < 1e-3, "expected ~0.5, got {v}");
1692
1693 let z = tick(u64::MAX).linear_interpolate(tick(u64::MAX), tick(u64::MAX));
1695 assert_eq!(z, 1.0);
1696 let z0 = tick(0).linear_interpolate(tick(0), tick(0));
1697 assert_eq!(z0, 1.0);
1698 }
1699
1700 #[cfg(feature = "std")]
1701 #[test]
1702 fn linear_interpolate_mismatched_kinds_never_nan() {
1703 let sys = Instant::now();
1706 let cases = [
1707 (tick(5), sys.clone(), tick(10)),
1708 (sys.clone(), tick(0), tick(10)),
1709 (tick(5), tick(0), sys.clone()),
1710 (sys.clone(), sys.clone(), tick(10)),
1711 (tick(5), sys.clone(), sys.clone()),
1712 ];
1713 for (this, start, end) in cases {
1714 let v = this.linear_interpolate(start, end);
1715 assert!(v.is_finite(), "mismatched-kind interpolation returned {v}");
1716 assert!(
1717 (0.0..=1.0).contains(&v),
1718 "mismatched-kind interpolation escaped [0,1]: {v}"
1719 );
1720 }
1721 }
1722
1723 #[test]
1728 fn add_optional_duration_none_is_identity() {
1729 let t = tick(42);
1730 assert_eq!(t.add_optional_duration(None), t);
1731 assert_eq!(tick(u64::MAX).add_optional_duration(None), tick(u64::MAX));
1732 }
1733
1734 #[test]
1735 fn add_optional_duration_tick_saturates_at_u64_max() {
1736 let near_max = tick(u64::MAX - 1);
1738 assert_eq!(
1739 near_max.add_optional_duration(Some(&tick_dur(u64::MAX))),
1740 tick(u64::MAX)
1741 );
1742 assert_eq!(tick(0).add_optional_duration(Some(&tick_dur(0))), tick(0));
1743 }
1744
1745 #[cfg(feature = "std")]
1746 #[test]
1747 fn add_optional_duration_system_advances_by_the_duration() {
1748 let base = Instant::now();
1749 let later = base.add_optional_duration(Some(&Duration::System(SystemTimeDiff::from_secs(1))));
1750 assert!(later > base);
1751 let delta = later.duration_since(&base);
1752 assert_eq!(delta, sys_dur(1, 0));
1753 assert_eq!(base.duration_since(&later), sys_dur(0, 0));
1755 }
1756
1757 #[cfg(feature = "std")]
1763 #[test]
1764 fn add_optional_duration_converts_between_units_in_both_directions() {
1765 let sys = Instant::now();
1766 let later = sys.add_optional_duration(Some(&tick_dur(60)));
1768 assert!(later > sys, "a tick interval must advance a wall-clock instant");
1769 assert_eq!(later.duration_since(&sys), sys_dur(1, 0));
1770
1771 assert_eq!(sys.add_optional_duration(Some(&tick_dur(0))), sys);
1773
1774 assert_eq!(tick(7).add_optional_duration(Some(&sys_dur(3, 0))), tick(187));
1776 }
1777
1778 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1784 #[test]
1785 #[should_panic(expected = "overflow")]
1786 fn add_optional_duration_system_overflow_panics_today() {
1787 let base = Instant::now();
1788 let _ = base.add_optional_duration(Some(&Duration::max()));
1789 }
1790
1791 #[test]
1796 fn duration_since_tick_saturates_and_is_exact() {
1797 assert_eq!(tick(10).duration_since(&tick(4)), tick_dur(6));
1798 assert_eq!(tick(10).duration_since(&tick(10)), tick_dur(0));
1800 assert_eq!(tick(0).duration_since(&tick(u64::MAX)), tick_dur(0));
1802 assert_eq!(tick(u64::MAX).duration_since(&tick(0)), tick_dur(u64::MAX));
1804 }
1805
1806 #[cfg(feature = "std")]
1807 #[test]
1808 fn duration_since_mismatched_kinds_is_zero_tick_both_directions() {
1809 let sys = Instant::now();
1810 assert_eq!(sys.duration_since(&tick(5)), tick_dur(0));
1811 assert_eq!(tick(5).duration_since(&sys), tick_dur(0));
1812 }
1813
1814 #[cfg(feature = "std")]
1815 #[test]
1816 fn into_std_instant_round_trips_a_system_instant() {
1817 let base = StdInstant::now();
1818 let wrapped: Instant = base.into();
1819 assert_eq!(wrapped.into_std_instant(), base);
1820 }
1821
1822 #[cfg(feature = "std")]
1823 #[test]
1824 #[should_panic(expected = "internal error: entered unreachable code")]
1825 fn into_std_instant_on_tick_variant_panics() {
1826 let _ = tick(1).into_std_instant();
1828 }
1829
1830 #[test]
1835 fn system_tick_new_stores_the_counter_verbatim() {
1836 for n in [0_u64, 1, 0x0100, u64::MAX / 2, u64::MAX] {
1837 assert_eq!(SystemTick::new(n).tick_counter, n);
1838 }
1839 assert!(SystemTick::new(0) < SystemTick::new(u64::MAX));
1841 assert_eq!(SystemTick::new(7), SystemTick::new(7));
1842 }
1843
1844 #[cfg(feature = "std")]
1849 #[test]
1850 fn instant_ptr_get_returns_the_wrapped_instant() {
1851 let base = StdInstant::now();
1852 let p: InstantPtr = base.into();
1853 assert_eq!(p.get(), base);
1854 assert_eq!(p.get(), p.get());
1856 assert!(p.run_destructor);
1857 assert!(!alloc::format!("{p:?}").is_empty());
1859 }
1860
1861 #[cfg(feature = "std")]
1862 #[test]
1863 fn std_instant_clone_deep_copies_and_arms_the_destructor() {
1864 let base = StdInstant::now();
1865 let a: InstantPtr = base.into();
1866 let cloned = std_instant_clone(core::ptr::from_ref(&a));
1867 assert_eq!(cloned.get(), base);
1868 assert!(!core::ptr::eq(&**a.ptr, &**cloned.ptr));
1870 assert!(cloned.run_destructor, "clone handed back a disarmed destructor");
1871 drop(cloned);
1872 assert_eq!(a.get(), base);
1874 }
1875
1876 #[cfg(feature = "std")]
1877 #[test]
1878 fn std_instant_drop_is_a_noop_even_for_null() {
1879 std_instant_drop(core::ptr::null_mut());
1883
1884 let mut p: InstantPtr = StdInstant::now().into();
1885 let before = p.get();
1886 std_instant_drop(core::ptr::from_mut(&mut p));
1887 assert_eq!(p.get(), before);
1889 assert!(p.run_destructor);
1890 }
1891
1892 #[test]
1897 fn duration_display_tick_edge_values() {
1898 assert_eq!(alloc::format!("{}", tick_dur(0)), "0 ticks");
1899 assert_eq!(alloc::format!("{}", tick_dur(1)), "1 ticks");
1900 assert_eq!(
1901 alloc::format!("{}", tick_dur(u64::MAX)),
1902 "18446744073709551615 ticks"
1903 );
1904 }
1905
1906 #[cfg(feature = "std")]
1907 #[test]
1908 fn duration_display_system_edge_values_do_not_panic() {
1909 for d in [
1912 sys_dur(0, 0),
1913 sys_dur(1, 500_000_000),
1914 sys_dur(0, u32::MAX),
1915 sys_dur(u64::MAX, NANOS_PER_SEC - 1),
1916 Duration::max(),
1917 ] {
1918 let s = alloc::format!("{d}");
1919 assert!(!s.is_empty());
1920 assert!(!s.ends_with("ticks"), "System duration formatted as ticks: {s}");
1921 }
1922 }
1923
1924 #[cfg(feature = "std")]
1929 #[test]
1930 fn duration_max_is_the_upper_bound() {
1931 let m = Duration::max();
1932 assert_eq!(m, sys_dur(u64::MAX, NANOS_PER_SEC - 1));
1933 assert!(m.greater_than(&sys_dur(u64::MAX, NANOS_PER_SEC - 2)));
1935 assert!(m.greater_than(&sys_dur(0, 0)));
1936 assert!(!m.greater_than(&m));
1938 assert!(!m.smaller_than(&m));
1939 let Duration::System(inner) = m else {
1941 panic!("Duration::max() is not a System duration under std")
1942 };
1943 assert_eq!(inner.get(), StdDuration::new(u64::MAX, NANOS_PER_SEC - 1));
1944 }
1945
1946 #[test]
1947 fn duration_div_by_zero_yields_inf_or_nan_not_a_panic() {
1948 assert!(tick_dur(0).div(&tick_dur(0)).is_nan());
1950 let inf = tick_dur(5).div(&tick_dur(0));
1951 assert!(inf.is_infinite() && inf.is_sign_positive());
1952
1953 assert!(sys_dur(0, 0).div(&sys_dur(0, 0)).is_nan());
1954 let sinf = sys_dur(1, 0).div(&sys_dur(0, 0));
1955 assert!(sinf.is_infinite() && sinf.is_sign_positive());
1956 }
1957
1958 #[test]
1959 fn duration_div_extremes_stay_finite_in_f32() {
1960 let r = tick_dur(u64::MAX).div(&tick_dur(1));
1963 assert!(r.is_finite(), "u64::MAX tick ratio overflowed f32: {r}");
1964 assert!(r > 1e19);
1965 assert_eq!(tick_dur(u64::MAX).div(&tick_dur(u64::MAX)), 1.0);
1967 assert_eq!(sys_dur(3, 0).div(&sys_dur(2, 0)), 1.5);
1968 }
1969
1970 #[test]
1974 fn duration_div_across_kinds_converts_both_ways() {
1975 assert!((sys_dur(1, 0).div(&tick_dur(10)) - 6.0).abs() < 1e-5);
1976 assert!((tick_dur(10).div(&sys_dur(1, 0)) - (1.0 / 6.0)).abs() < 1e-5);
1977 }
1978
1979 #[test]
1980 fn duration_min_picks_the_smaller_of_the_same_kind() {
1981 assert_eq!(tick_dur(5).min(tick_dur(10)), tick_dur(5));
1982 assert_eq!(tick_dur(10).min(tick_dur(5)), tick_dur(5));
1983 assert_eq!(tick_dur(7).min(tick_dur(7)), tick_dur(7));
1984 assert_eq!(tick_dur(0).min(tick_dur(u64::MAX)), tick_dur(0));
1985 assert_eq!(sys_dur(1, 0).min(sys_dur(1, 1)), sys_dur(1, 0));
1987 }
1988
1989 #[test]
1994 fn duration_min_across_kinds_picks_the_genuinely_shorter_span() {
1995 assert_eq!(tick_dur(5).min(sys_dur(1, 0)), tick_dur(5));
1997 assert_eq!(sys_dur(1, 0).min(tick_dur(5)), tick_dur(5));
1998 assert_eq!(tick_dur(120).min(sys_dur(1, 0)), sys_dur(1, 0));
2000 assert_eq!(sys_dur(1, 0).min(tick_dur(120)), sys_dur(1, 0));
2001 }
2002
2003 #[test]
2004 fn duration_comparison_is_a_strict_total_order_within_a_kind() {
2005 let mut pairs = alloc::vec![(tick_dur(0), tick_dur(u64::MAX)), (tick_dur(1), tick_dur(2))];
2006 pairs.extend_from_slice(&[
2010 (sys_dur(0, 0), sys_dur(u64::MAX, 0)),
2011 (sys_dur(1, 999_999_999), sys_dur(2, 0)),
2012 ]);
2013
2014 for (a, b) in pairs {
2015 assert!(a.smaller_than(&b));
2016 assert!(b.greater_than(&a));
2017 assert!(!a.greater_than(&b));
2018 assert!(!b.smaller_than(&a));
2019 }
2020 let eq = tick_dur(4);
2022 assert!(!eq.greater_than(&eq));
2023 assert!(!eq.smaller_than(&eq));
2024 let eq_sys = sys_dur(4, 2);
2025 assert!(!eq_sys.greater_than(&eq_sys));
2026 assert!(!eq_sys.smaller_than(&eq_sys));
2027 }
2028
2029 #[cfg(feature = "std")]
2030 #[test]
2031 fn duration_comparison_normalizes_denormalized_nanos() {
2032 let denorm = sys_dur(0, u32::MAX);
2035 assert!(denorm.greater_than(&sys_dur(4, 0)));
2036 assert!(denorm.smaller_than(&sys_dur(5, 0)));
2037 }
2038
2039 #[test]
2044 fn system_tick_diff_div_edge_cases() {
2045 let zero = SystemTickDiff { tick_diff: 0 };
2046 let one = SystemTickDiff { tick_diff: 1 };
2047 let max = SystemTickDiff { tick_diff: u64::MAX };
2048
2049 assert!(zero.div(&zero).is_nan());
2050 assert!(one.div(&zero).is_infinite());
2051 assert_eq!(zero.div(&one), 0.0);
2052 assert_eq!(max.div(&max), 1.0);
2053 assert!(max.div(&one).is_finite());
2054 assert_eq!(SystemTickDiff { tick_diff: 5 }.div(&SystemTickDiff { tick_diff: 10 }), 0.5);
2055 }
2056
2057 #[test]
2058 fn system_time_diff_as_secs_f64_is_exact_for_representable_values() {
2059 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.as_secs_f64(), 0.0);
2060 assert_eq!(SystemTimeDiff { secs: 1, nanos: 500_000_000 }.as_secs_f64(), 1.5);
2061 assert_eq!(SystemTimeDiff { secs: 0, nanos: 500_000_000 }.as_secs_f64(), 0.5);
2062 let huge = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2064 assert!(huge.as_secs_f64().is_finite());
2065 assert!(huge.as_secs_f64() > 1e19);
2066 assert!(
2068 SystemTimeDiff::from_secs(2).as_secs_f64() > SystemTimeDiff::from_secs(1).as_secs_f64()
2069 );
2070 }
2071
2072 #[test]
2073 fn system_time_diff_div_edge_cases() {
2074 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
2075 let one = SystemTimeDiff::from_secs(1);
2076 let half = SystemTimeDiff { secs: 0, nanos: 500_000_000 };
2077
2078 assert!(zero.div(&zero).is_nan());
2079 assert!(one.div(&zero).is_infinite());
2080 assert_eq!(zero.div(&one), 0.0);
2081 assert_eq!(one.div(&one), 1.0);
2082 assert_eq!(one.div(&half), 2.0);
2083 let max = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2084 assert_eq!(max.div(&max), 1.0);
2085 assert!(max.div(&one).is_finite());
2086 }
2087
2088 #[test]
2093 fn from_secs_invariants() {
2094 for s in [0_u64, 1, 1_000, u64::MAX] {
2095 let d = SystemTimeDiff::from_secs(s);
2096 assert_eq!(d.secs, s);
2097 assert_eq!(d.nanos, 0, "from_secs must leave nanos at zero");
2098 }
2099 }
2100
2101 #[test]
2102 fn from_millis_normalizes_and_keeps_nanos_in_range() {
2103 assert_eq!(SystemTimeDiff::from_millis(0), SystemTimeDiff { secs: 0, nanos: 0 });
2104 assert_eq!(
2105 SystemTimeDiff::from_millis(999),
2106 SystemTimeDiff { secs: 0, nanos: 999_000_000 }
2107 );
2108 assert_eq!(SystemTimeDiff::from_millis(1_000), SystemTimeDiff { secs: 1, nanos: 0 });
2109 assert_eq!(
2110 SystemTimeDiff::from_millis(1_500),
2111 SystemTimeDiff { secs: 1, nanos: 500_000_000 }
2112 );
2113 let max = SystemTimeDiff::from_millis(u64::MAX);
2115 assert!(max.nanos < NANOS_PER_SEC, "from_millis produced denormalized nanos");
2116 assert_eq!(max.secs, u64::MAX / MILLIS_PER_SEC);
2117 }
2118
2119 #[test]
2120 fn from_nanos_normalizes_and_keeps_nanos_in_range() {
2121 assert_eq!(SystemTimeDiff::from_nanos(0), SystemTimeDiff { secs: 0, nanos: 0 });
2122 assert_eq!(
2123 SystemTimeDiff::from_nanos(999_999_999),
2124 SystemTimeDiff { secs: 0, nanos: 999_999_999 }
2125 );
2126 assert_eq!(
2127 SystemTimeDiff::from_nanos(1_000_000_000),
2128 SystemTimeDiff { secs: 1, nanos: 0 }
2129 );
2130 for n in [0_u64, 1, 999_999_999, 1_000_000_001, u64::MAX] {
2131 let d = SystemTimeDiff::from_nanos(n);
2132 assert!(d.nanos < NANOS_PER_SEC, "from_nanos({n}) produced denormalized nanos");
2133 let back =
2135 u128::from(d.secs) * u128::from(NANOS_PER_SEC) + u128::from(d.nanos);
2136 assert_eq!(back, u128::from(n), "from_nanos({n}) lost information");
2137 }
2138 }
2139
2140 #[test]
2145 fn millis_round_trips_through_from_millis() {
2146 for m in [0_u64, 1, 999, 1_000, 1_500, 86_400_000, u64::MAX] {
2149 assert_eq!(
2150 SystemTimeDiff::from_millis(m).millis(),
2151 m,
2152 "from_millis({m}).millis() is not lossless"
2153 );
2154 }
2155 }
2156
2157 #[test]
2158 fn millis_truncates_and_saturates_instead_of_panicking() {
2159 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999 }.millis(), 0);
2161 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999_999 }.millis(), 999);
2162 assert_eq!(SystemTimeDiff { secs: u64::MAX, nanos: 0 }.millis(), u64::MAX);
2164 assert_eq!(
2165 SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 }.millis(),
2166 u64::MAX
2167 );
2168 assert_eq!(SystemTimeDiff::from_secs(u64::MAX / 1_000).millis(), (u64::MAX / 1_000) * 1_000);
2169 }
2170
2171 #[test]
2176 fn checked_add_carries_nanos_into_secs() {
2177 let a = SystemTimeDiff { secs: 0, nanos: 999_999_999 };
2178 let sum = a.checked_add(a).expect("0.999s + 0.999s must not overflow");
2179 assert_eq!(sum, SystemTimeDiff { secs: 1, nanos: 999_999_998 });
2180 let b = SystemTimeDiff { secs: 1, nanos: 500_000_000 };
2182 assert_eq!(
2183 b.checked_add(b),
2184 Some(SystemTimeDiff { secs: 3, nanos: 0 })
2185 );
2186 }
2187
2188 #[test]
2189 fn checked_add_returns_none_on_overflow_instead_of_panicking() {
2190 let max_secs = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
2191 assert_eq!(max_secs.checked_add(SystemTimeDiff::from_secs(1)), None);
2193 assert_eq!(
2195 max_secs.checked_add(SystemTimeDiff { secs: 0, nanos: NANOS_PER_SEC - 1 }),
2196 Some(SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 })
2197 );
2198 let brim = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
2200 assert_eq!(brim.checked_add(SystemTimeDiff { secs: 0, nanos: 1 }), None);
2201 }
2202
2203 #[test]
2204 fn checked_add_identity_and_commutativity() {
2205 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
2206 for d in [
2207 SystemTimeDiff::from_secs(0),
2208 SystemTimeDiff::from_millis(1_500),
2209 SystemTimeDiff::from_nanos(u64::MAX),
2210 SystemTimeDiff { secs: u64::MAX, nanos: 0 },
2211 ] {
2212 assert_eq!(d.checked_add(zero), Some(d));
2213 assert_eq!(zero.checked_add(d), Some(d));
2214 let other = SystemTimeDiff::from_millis(750);
2216 assert_eq!(d.checked_add(other), other.checked_add(d));
2217 }
2218 }
2219
2220 #[cfg(feature = "std")]
2225 #[test]
2226 fn system_time_diff_get_round_trips_std_duration() {
2227 for std_d in [
2228 StdDuration::ZERO,
2229 StdDuration::from_millis(1_500),
2230 StdDuration::from_nanos(1),
2231 StdDuration::new(u64::MAX, NANOS_PER_SEC - 1),
2232 ] {
2233 let mid: SystemTimeDiff = std_d.into();
2234 assert_eq!(mid.get(), std_d, "StdDuration -> SystemTimeDiff -> StdDuration lost data");
2235 }
2236 }
2237
2238 #[cfg(feature = "std")]
2239 #[test]
2240 fn system_time_diff_get_on_edge_values_does_not_panic() {
2241 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.get(), StdDuration::ZERO);
2242 assert_eq!(
2244 SystemTimeDiff::from_secs(u64::MAX).get(),
2245 StdDuration::new(u64::MAX, 0)
2246 );
2247 assert_eq!(
2249 SystemTimeDiff { secs: 0, nanos: u32::MAX }.get(),
2250 StdDuration::new(0, u32::MAX)
2251 );
2252 }
2253
2254 #[cfg(feature = "std")]
2259 extern "C" fn test_thread_recv(ptr: *const c_void) -> OptionThreadSendMsg {
2260 let receiver = unsafe { &*(ptr.cast::<Receiver<ThreadSendMsg>>()) };
2263 receiver.try_recv().ok().into()
2264 }
2265
2266 #[cfg(feature = "std")]
2267 const extern "C" fn test_thread_recv_destructor(_: *mut ThreadReceiverInner) {}
2268
2269 #[cfg(feature = "std")]
2270 fn test_receiver() -> (Sender<ThreadSendMsg>, ThreadReceiver) {
2271 let (tx, rx) = std::sync::mpsc::channel::<ThreadSendMsg>();
2272 let inner = ThreadReceiverInner {
2273 ptr: Box::new(rx),
2274 recv_fn: ThreadRecvCallback { cb: test_thread_recv },
2275 destructor: ThreadReceiverDestructorCallback {
2276 cb: test_thread_recv_destructor,
2277 },
2278 };
2279 (tx, ThreadReceiver::new(inner))
2280 }
2281
2282 #[cfg(feature = "std")]
2283 #[test]
2284 fn thread_receiver_new_arms_destructor_and_has_no_ctx() {
2285 let (_tx, r) = test_receiver();
2286 assert!(r.run_destructor, "ThreadReceiver::new left the destructor disarmed");
2287 assert!(r.get_ctx().is_none(), "a fresh receiver must have no FFI context");
2288 }
2289
2290 #[cfg(feature = "std")]
2291 #[test]
2292 fn thread_receiver_recv_on_empty_and_disconnected_channel_is_none() {
2293 let (tx, mut r) = test_receiver();
2294 assert!(r.recv().is_none());
2296 drop(tx);
2298 assert!(r.recv().is_none());
2299 assert!(r.recv().is_none());
2300 }
2301
2302 #[cfg(feature = "std")]
2303 #[test]
2304 fn thread_receiver_recv_delivers_messages_in_order() {
2305 let (tx, mut r) = test_receiver();
2306 tx.send(ThreadSendMsg::Tick).unwrap();
2307 tx.send(ThreadSendMsg::Custom(RefAny::new(42_u32))).unwrap();
2308 tx.send(ThreadSendMsg::TerminateThread).unwrap();
2309
2310 assert_eq!(r.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
2311 assert!(matches!(
2312 r.recv(),
2313 OptionThreadSendMsg::Some(ThreadSendMsg::Custom(_))
2314 ));
2315 assert_eq!(
2316 r.recv(),
2317 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
2318 );
2319 assert!(r.recv().is_none());
2321 }
2322
2323 #[cfg(feature = "std")]
2324 #[test]
2325 fn thread_receiver_clone_shares_the_same_channel() {
2326 let (tx, mut a) = test_receiver();
2327 let mut b = a.clone();
2328 assert!(b.run_destructor);
2329
2330 tx.send(ThreadSendMsg::Tick).unwrap();
2331 assert_eq!(a.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
2335 assert!(b.recv().is_none());
2336
2337 tx.send(ThreadSendMsg::TerminateThread).unwrap();
2338 assert_eq!(
2339 b.recv(),
2340 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
2341 );
2342 assert!(a.recv().is_none());
2343 }
2344
2345 #[cfg(feature = "std")]
2346 #[test]
2347 fn thread_receiver_get_ctx_clones_rather_than_takes() {
2348 let (_tx, mut r) = test_receiver();
2349 r.ctx = OptionRefAny::Some(RefAny::new(7_u64));
2350 assert!(r.get_ctx().is_some());
2353 assert!(r.get_ctx().is_some());
2354 let held = r.get_ctx();
2355 drop(r);
2356 assert!(held.is_some());
2358 }
2359}