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")]
203pub static TEST_CLOCK_OFFSET_MS: core::sync::atomic::AtomicU64 =
204 core::sync::atomic::AtomicU64::new(0);
205
206#[cfg(feature = "std")]
208pub fn advance_test_clock_ms(ms: u64) -> u64 {
209 TEST_CLOCK_OFFSET_MS.fetch_add(ms, Ordering::SeqCst) + ms
210}
211
212#[cfg(feature = "std")]
214#[must_use]
215pub fn test_clock_offset_ms() -> u64 {
216 TEST_CLOCK_OFFSET_MS.load(Ordering::SeqCst)
217}
218
219#[cfg(feature = "std")]
221fn std_now_with_test_offset() -> StdInstant {
222 let offset = test_clock_offset_ms();
223 if offset == 0 {
224 StdInstant::now()
225 } else {
226 StdInstant::now() + core::time::Duration::from_millis(offset)
227 }
228}
229
230impl Instant {
231 #[cfg(feature = "std")]
236 #[must_use] pub fn now() -> Self {
237 std_now_with_test_offset().into()
238 }
239
240 #[cfg(not(feature = "std"))]
242 pub fn now() -> Self {
243 Instant::Tick(SystemTick::new(0))
244 }
245
246 #[must_use] pub fn linear_interpolate(&self, mut start: Self, mut end: Self) -> f32 {
249 use core::mem;
250
251 if end < start {
252 mem::swap(&mut start, &mut end);
253 }
254
255 if *self < start {
256 return 0.0;
257 }
258 if *self > end {
259 return 1.0;
260 }
261
262 if start == end {
266 return 1.0;
267 }
268
269 let duration_total = end.duration_since(&start);
270 let duration_current = self.duration_since(&start);
271
272 let ratio = duration_current.div(&duration_total);
273 if ratio.is_nan() {
274 return 1.0;
275 }
276 ratio.clamp(0.0, 1.0)
277 }
278
279 #[must_use] pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
286 duration.map_or_else(|| self.clone(), |d| match (self, d) {
287 (Self::System(i), Duration::System(d)) => {
288 #[cfg(feature = "std")]
289 {
290 let s: StdInstant = i.clone().into();
291 let d: StdDuration = (*d).into();
292 let new: InstantPtr = (s + d).into();
293 Self::System(new)
294 }
295 #[cfg(not(feature = "std"))]
296 {
297 let _ = (i, d);
301 self.clone()
302 }
303 }
304 (Self::Tick(s), Duration::Tick(d)) => Self::Tick(SystemTick {
305 tick_counter: s.tick_counter.saturating_add(d.tick_diff),
307 }),
308 _ => self.clone(),
310 })
311 }
312
313 #[cfg(feature = "std")]
315 #[must_use] pub fn into_std_instant(self) -> StdInstant {
316 match self {
317 Self::System(s) => s.into(),
318 Self::Tick(_) => unreachable!(),
319 }
320 }
321
322 #[must_use] pub fn duration_since(&self, earlier: &Self) -> Duration {
328 match (earlier, self) {
329 (Self::System(prev), Self::System(now)) => {
330 #[cfg(feature = "std")]
331 {
332 let prev_instant: StdInstant = prev.clone().into();
333 let now_instant: StdInstant = now.clone().into();
334 Duration::System(now_instant.saturating_duration_since(prev_instant).into())
337 }
338 #[cfg(not(feature = "std"))]
339 {
340 let _ = (prev, now);
342 Duration::Tick(SystemTickDiff { tick_diff: 0 })
343 }
344 }
345 (
346 Self::Tick(SystemTick { tick_counter: prev }),
347 Self::Tick(SystemTick { tick_counter: now }),
348 ) => Duration::Tick(SystemTickDiff {
349 tick_diff: now.saturating_sub(*prev),
351 }),
352 _ => Duration::Tick(SystemTickDiff { tick_diff: 0 }),
354 }
355 }
356}
357
358#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
362#[repr(C)]
363pub struct SystemTick {
364 pub tick_counter: u64,
365}
366
367impl SystemTick {
368 #[must_use] pub const fn new(tick_counter: u64) -> Self {
370 Self { tick_counter }
371 }
372}
373
374#[repr(C)]
378pub struct InstantPtr {
379 #[cfg(feature = "std")]
387 pub ptr: ManuallyDrop<Box<StdInstant>>,
388 #[cfg(not(feature = "std"))]
389 pub ptr: *const c_void,
390 pub clone_fn: InstantPtrCloneCallback,
391 pub destructor: InstantPtrDestructorCallback,
392 pub run_destructor: bool,
393}
394
395pub type InstantPtrCloneCallbackType = extern "C" fn(*const InstantPtr) -> InstantPtr;
396#[repr(C)]
397pub struct InstantPtrCloneCallback {
398 pub cb: InstantPtrCloneCallbackType,
399}
400impl_callback_simple!(InstantPtrCloneCallback);
401
402pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
403#[repr(C)]
404pub struct InstantPtrDestructorCallback {
405 pub cb: InstantPtrDestructorCallbackType,
406}
407impl_callback_simple!(InstantPtrDestructorCallback);
408
409#[cfg(feature = "std")]
411impl fmt::Debug for InstantPtr {
412 fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
413 write!(f, "{:?}", self.get())
414 }
415}
416
417#[cfg(not(feature = "std"))]
418impl core::fmt::Debug for InstantPtr {
419 fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
420 write!(f, "{:?}", self.ptr as usize)
421 }
422}
423
424#[cfg(feature = "std")]
425impl core::hash::Hash for InstantPtr {
426 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
427 self.get().hash(state);
428 }
429}
430
431#[cfg(not(feature = "std"))]
432impl core::hash::Hash for InstantPtr {
433 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
434 (self.ptr as usize).hash(state);
435 }
436}
437
438#[cfg(feature = "std")]
439impl PartialEq for InstantPtr {
440 fn eq(&self, other: &Self) -> bool {
441 self.get() == other.get()
442 }
443}
444
445#[cfg(not(feature = "std"))]
446impl PartialEq for InstantPtr {
447 fn eq(&self, other: &InstantPtr) -> bool {
448 (self.ptr as usize).eq(&(other.ptr as usize))
449 }
450}
451
452impl Eq for InstantPtr {}
453
454#[cfg(feature = "std")]
455impl PartialOrd for InstantPtr {
456 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
457 Some((self.get()).cmp(&(other.get())))
458 }
459}
460
461#[cfg(not(feature = "std"))]
462impl PartialOrd for InstantPtr {
463 fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
464 Some((self.ptr as usize).cmp(&(other.ptr as usize)))
465 }
466}
467
468#[cfg(feature = "std")]
469impl Ord for InstantPtr {
470 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
471 (self.get()).cmp(&(other.get()))
472 }
473}
474
475#[cfg(not(feature = "std"))]
476impl Ord for InstantPtr {
477 fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
478 (self.ptr as usize).cmp(&(other.ptr as usize))
479 }
480}
481
482#[cfg(feature = "std")]
483impl InstantPtr {
484 fn get(&self) -> StdInstant {
485 (**self.ptr)
486 }
487}
488
489impl Clone for InstantPtr {
490 fn clone(&self) -> Self {
491 (self.clone_fn.cb)(self)
492 }
493}
494
495#[cfg(feature = "std")]
496extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
497 let az_instant_ptr = unsafe { &*ptr };
498 InstantPtr {
499 ptr: ManuallyDrop::new((*az_instant_ptr.ptr).clone()),
500 clone_fn: az_instant_ptr.clone_fn,
501 destructor: az_instant_ptr.destructor,
502 run_destructor: true,
503 }
504}
505
506#[cfg(feature = "std")]
507impl From<StdInstant> for InstantPtr {
508 fn from(s: StdInstant) -> Self {
509 Self {
510 ptr: ManuallyDrop::new(Box::new(s)),
511 clone_fn: InstantPtrCloneCallback {
512 cb: std_instant_clone,
513 },
514 destructor: InstantPtrDestructorCallback {
515 cb: std_instant_drop,
516 },
517 run_destructor: true,
518 }
519 }
520}
521
522#[cfg(feature = "std")]
523impl From<InstantPtr> for StdInstant {
524 fn from(s: InstantPtr) -> Self {
525 s.get()
526 }
527}
528
529impl Drop for InstantPtr {
530 fn drop(&mut self) {
531 if self.run_destructor {
532 self.run_destructor = false;
533 (self.destructor.cb)(self);
534 #[cfg(feature = "std")]
543 unsafe {
544 ManuallyDrop::drop(&mut self.ptr);
545 }
546 }
547 }
548}
549
550#[cfg(feature = "std")]
551const extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
552
553#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
560#[repr(C, u8)]
561pub enum Duration {
562 System(SystemTimeDiff),
564 Tick(SystemTickDiff),
566}
567
568impl fmt::Display for Duration {
569 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
570 match self {
571 #[cfg(feature = "std")]
572 Self::System(s) => {
573 let s: StdDuration = (*s).into();
574 write!(f, "{s:?}")
575 }
576 #[cfg(not(feature = "std"))]
577 Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
578 Self::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
579 }
580 }
581}
582
583#[cfg(feature = "std")]
584impl From<StdDuration> for Duration {
585 fn from(s: StdDuration) -> Self {
586 Self::System(s.into())
587 }
588}
589
590impl Duration {
591 #[must_use] pub fn max() -> Self {
593 #[cfg(feature = "std")]
594 {
595 Self::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
596 }
597 #[cfg(not(feature = "std"))]
598 {
599 Duration::Tick(SystemTickDiff {
600 tick_diff: u64::MAX,
601 })
602 }
603 }
604
605 #[allow(clippy::cast_possible_truncation)]
609 #[must_use] pub fn div(&self, other: &Self) -> f32 {
610 use self::Duration::{System, Tick};
611 match (self, other) {
612 (System(s), System(s2)) => s.div(s2) as f32,
613 (Tick(t), Tick(t2)) => t.div(t2) as f32,
614 _ => 0.0,
615 }
616 }
617
618 #[must_use] pub fn min(self, other: Self) -> Self {
620 if self.smaller_than(&other) {
621 self
622 } else {
623 other
624 }
625 }
626
627 #[allow(unused_variables)]
632 #[must_use] pub fn greater_than(&self, other: &Self) -> bool {
633 match (self, other) {
634 (Self::System(s), Self::System(o)) => {
636 #[cfg(feature = "std")]
637 {
638 let s: StdDuration = (*s).into();
639 let o: StdDuration = (*o).into();
640 s > o
641 }
642 #[cfg(not(feature = "std"))]
643 {
644 false
645 }
646 }
647 (Self::Tick(s), Self::Tick(o)) => s.tick_diff > o.tick_diff,
648 _ => false,
649 }
650 }
651
652 #[allow(unused_variables)]
657 #[must_use] pub fn smaller_than(&self, other: &Self) -> bool {
658 match (self, other) {
660 (Self::System(s), Self::System(o)) => {
662 #[cfg(feature = "std")]
663 {
664 let s: StdDuration = (*s).into();
665 let o: StdDuration = (*o).into();
666 s < o
667 }
668 #[cfg(not(feature = "std"))]
669 {
670 false
671 }
672 }
673 (Self::Tick(s), Self::Tick(o)) => s.tick_diff < o.tick_diff,
674 _ => false,
675 }
676 }
677}
678
679#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
682#[repr(C)]
683pub struct SystemTickDiff {
684 pub tick_diff: u64,
685}
686
687impl SystemTickDiff {
688 #[allow(clippy::cast_precision_loss)]
692 #[must_use] pub fn div(&self, other: &Self) -> f64 {
693 self.tick_diff as f64 / other.tick_diff as f64
694 }
695}
696
697#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
699#[repr(C)]
700pub struct SystemTimeDiff {
701 pub secs: u64,
702 pub nanos: u32,
703}
704
705impl SystemTimeDiff {
706 #[must_use] pub fn div(&self, other: &Self) -> f64 {
709 self.as_secs_f64() / other.as_secs_f64()
710 }
711 #[allow(clippy::cast_precision_loss)]
713 fn as_secs_f64(&self) -> f64 {
714 (self.secs as f64) + (f64::from(self.nanos) / f64::from(NANOS_PER_SEC))
715 }
716}
717
718#[cfg(feature = "std")]
719impl From<StdDuration> for SystemTimeDiff {
720 fn from(d: StdDuration) -> Self {
721 Self {
722 secs: d.as_secs(),
723 nanos: d.subsec_nanos(),
724 }
725 }
726}
727
728#[cfg(feature = "std")]
729impl From<SystemTimeDiff> for StdDuration {
730 fn from(d: SystemTimeDiff) -> Self {
731 Self::new(d.secs, d.nanos)
732 }
733}
734
735const MILLIS_PER_SEC: u64 = 1_000;
736const NANOS_PER_MILLI: u32 = 1_000_000;
737const NANOS_PER_SEC: u32 = 1_000_000_000;
738
739impl SystemTimeDiff {
740 #[must_use] pub const fn from_secs(secs: u64) -> Self {
742 Self { secs, nanos: 0 }
743 }
744 #[must_use] pub const fn from_millis(millis: u64) -> Self {
746 Self {
747 secs: millis / MILLIS_PER_SEC,
748 nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
749 }
750 }
751 #[allow(clippy::cast_possible_truncation)]
755 #[must_use] pub const fn from_nanos(nanos: u64) -> Self {
756 Self {
757 secs: nanos / (NANOS_PER_SEC as u64),
758 nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
759 }
760 }
761 #[must_use] pub const fn checked_add(self, rhs: Self) -> Option<Self> {
763 if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
764 let mut nanos = self.nanos + rhs.nanos;
765 if nanos >= NANOS_PER_SEC {
766 nanos -= NANOS_PER_SEC;
767 if let Some(new_secs) = secs.checked_add(1) {
768 secs = new_secs;
769 } else {
770 return None;
771 }
772 }
773 Some(Self { secs, nanos })
774 } else {
775 None
776 }
777 }
778
779 #[must_use] pub const fn millis(&self) -> u64 {
784 self.secs
785 .saturating_mul(MILLIS_PER_SEC)
786 .saturating_add((self.nanos / NANOS_PER_MILLI) as u64)
787 }
788
789 #[cfg(feature = "std")]
791 #[must_use] pub fn get(&self) -> StdDuration {
792 (*self).into()
793 }
794}
795
796impl_option!(
797 Instant,
798 OptionInstant,
799 copy = false,
800 [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
801);
802impl_option!(
803 Duration,
804 OptionDuration,
805 [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
806);
807#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
812#[repr(C, u8)]
813pub enum ThreadSendMsg {
814 TerminateThread,
816 Tick,
818 Custom(RefAny),
820}
821
822impl_option!(
823 ThreadSendMsg,
824 OptionThreadSendMsg,
825 copy = false,
826 [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
827);
828
829#[derive(Debug)]
833#[repr(C)]
834pub struct ThreadReceiver {
835 #[cfg(feature = "std")]
836 pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
837 #[cfg(not(feature = "std"))]
838 pub ptr: *const c_void,
839 pub run_destructor: bool,
840 pub ctx: OptionRefAny,
842}
843
844impl Clone for ThreadReceiver {
845 fn clone(&self) -> Self {
846 Self {
847 ptr: self.ptr.clone(),
848 run_destructor: true,
849 ctx: self.ctx.clone(),
850 }
851 }
852}
853
854impl Drop for ThreadReceiver {
855 fn drop(&mut self) {
856 self.run_destructor = false;
857 }
858}
859
860impl ThreadReceiver {
861 #[cfg(not(feature = "std"))]
863 pub fn new(_t: ThreadReceiverInner) -> Self {
864 Self {
865 ptr: core::ptr::null(),
866 run_destructor: false,
867 ctx: OptionRefAny::None,
868 }
869 }
870
871 #[cfg(feature = "std")]
873 #[must_use] pub fn new(t: ThreadReceiverInner) -> Self {
874 Self {
875 ptr: Box::new(Arc::new(Mutex::new(t))),
876 run_destructor: true,
877 ctx: OptionRefAny::None,
878 }
879 }
880
881 #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
883 self.ctx.clone()
884 }
885
886 #[cfg(not(feature = "std"))]
888 pub fn recv(&mut self) -> OptionThreadSendMsg {
889 None.into()
890 }
891
892 #[cfg(feature = "std")]
894 pub fn recv(&mut self) -> OptionThreadSendMsg {
895 let Some(ts) = self.ptr.lock().ok() else {
896 return None.into();
897 };
898 (ts.recv_fn.cb)(std::ptr::from_ref(ts.ptr.as_ref()) as *const c_void)
899 }
900}
901
902#[derive(Debug)]
904#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
905#[repr(C)]
906pub struct ThreadReceiverInner {
907 #[cfg(feature = "std")]
908 pub ptr: Box<Receiver<ThreadSendMsg>>,
909 #[cfg(not(feature = "std"))]
910 pub ptr: *const c_void,
911 pub recv_fn: ThreadRecvCallback,
912 pub destructor: ThreadReceiverDestructorCallback,
913}
914
915#[cfg(not(feature = "std"))]
916unsafe impl Send for ThreadReceiverInner {}
917
918#[cfg(feature = "std")]
919impl core::hash::Hash for ThreadReceiverInner {
920 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
921 (std::ptr::from_ref(self.ptr.as_ref()) as usize).hash(state);
922 }
923}
924
925#[cfg(feature = "std")]
926impl PartialEq for ThreadReceiverInner {
927 fn eq(&self, other: &Self) -> bool {
928 std::ptr::eq(self.ptr.as_ref(), other.ptr.as_ref())
929 }
930}
931
932#[cfg(feature = "std")]
933impl Eq for ThreadReceiverInner {}
934
935#[cfg(feature = "std")]
936impl PartialOrd for ThreadReceiverInner {
937 fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
938 Some(
939 (std::ptr::from_ref(self.ptr.as_ref()) as usize)
940 .cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize)),
941 )
942 }
943}
944
945#[cfg(feature = "std")]
946impl Ord for ThreadReceiverInner {
947 fn cmp(&self, other: &Self) -> core::cmp::Ordering {
948 (std::ptr::from_ref(self.ptr.as_ref()) as usize).cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize))
949 }
950}
951
952impl Drop for ThreadReceiverInner {
953 fn drop(&mut self) {
954 (self.destructor.cb)(self);
955 }
956}
957
958pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
961#[repr(C)]
962pub struct GetSystemTimeCallback {
963 pub cb: GetSystemTimeCallbackType,
964}
965impl_callback_simple!(GetSystemTimeCallback);
966
967#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
972#[must_use] pub extern "C" fn get_system_time_libstd() -> Instant {
973 std_now_with_test_offset().into()
975}
976
977#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
979pub extern "C" fn get_system_time_libstd() -> Instant {
980 Instant::Tick(SystemTick::new(0))
981}
982
983pub type CheckThreadFinishedCallbackType =
985 extern "C" fn(*const c_void) -> bool;
986#[repr(C)]
988pub struct CheckThreadFinishedCallback {
989 pub cb: CheckThreadFinishedCallbackType,
990}
991impl_callback_simple!(CheckThreadFinishedCallback);
992
993pub type LibrarySendThreadMsgCallbackType =
995 extern "C" fn(*const c_void, ThreadSendMsg) -> bool;
996#[repr(C)]
998pub struct LibrarySendThreadMsgCallback {
999 pub cb: LibrarySendThreadMsgCallbackType,
1000}
1001impl_callback_simple!(LibrarySendThreadMsgCallback);
1002
1003pub type ThreadRecvCallbackType =
1005 extern "C" fn(*const c_void) -> OptionThreadSendMsg;
1006#[repr(C)]
1008pub struct ThreadRecvCallback {
1009 pub cb: ThreadRecvCallbackType,
1010}
1011impl_callback_simple!(ThreadRecvCallback);
1012
1013pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
1015#[repr(C)]
1017pub struct ThreadReceiverDestructorCallback {
1018 pub cb: ThreadReceiverDestructorCallbackType,
1019}
1020impl_callback_simple!(ThreadReceiverDestructorCallback);
1021
1022#[cfg(test)]
1023#[allow(clippy::float_cmp)] mod tests {
1025 use super::*;
1026
1027 fn tick(n: u64) -> Instant {
1028 Instant::Tick(SystemTick::new(n))
1029 }
1030 fn tick_dur(n: u64) -> Duration {
1031 Duration::Tick(SystemTickDiff { tick_diff: n })
1032 }
1033 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1034 Duration::System(SystemTimeDiff { secs, nanos })
1035 }
1036
1037 #[test]
1038 fn linear_interpolate_zero_interval_is_one_not_nan() {
1039 let t = tick(5);
1040 let v = t.linear_interpolate(tick(5), tick(5));
1041 assert!(v.is_finite());
1042 assert_eq!(v, 1.0);
1043 }
1044
1045 #[test]
1046 fn linear_interpolate_midpoint() {
1047 let v = tick(5).linear_interpolate(tick(0), tick(10));
1048 assert!((v - 0.5).abs() < 1e-6);
1049 }
1050
1051 #[test]
1052 fn duration_since_saturates_on_negative() {
1053 let d = tick(1).duration_since(&tick(10));
1055 assert_eq!(d, tick_dur(0));
1056 }
1057
1058 #[test]
1059 fn duration_compare_mismatched_kinds_saturates() {
1060 let a = tick_dur(5);
1062 let b = sys_dur(1, 0);
1063 assert!(!a.greater_than(&b));
1064 assert!(!a.smaller_than(&b));
1065 assert!(!b.greater_than(&a));
1066 assert!(!b.smaller_than(&a));
1067 }
1068
1069 #[test]
1070 fn add_optional_duration_mismatched_is_noop() {
1071 let inst = tick(100);
1072 let out = inst.add_optional_duration(Some(&sys_dur(1, 0)));
1074 assert_eq!(out, tick(100));
1075 let out2 = inst.add_optional_duration(Some(&tick_dur(5)));
1077 assert_eq!(out2, tick(105));
1078 let big = tick(u64::MAX);
1080 let out3 = big.add_optional_duration(Some(&tick_dur(10)));
1081 assert_eq!(out3, tick(u64::MAX));
1082 }
1083
1084 #[test]
1085 fn millis_saturates_on_overflow() {
1086 let huge = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
1087 assert_eq!(huge.millis(), u64::MAX);
1088 let normal = SystemTimeDiff { secs: 2, nanos: 500_000_000 };
1089 assert_eq!(normal.millis(), 2500);
1090 }
1091
1092 #[test]
1093 fn duration_div_mismatched_kinds_is_zero() {
1094 assert_eq!(tick_dur(10).div(&sys_dur(1, 0)), 0.0);
1096 assert!((tick_dur(5).div(&tick_dur(10)) - 0.5).abs() < 1e-6);
1098 }
1099
1100 #[cfg(feature = "std")]
1106 #[test]
1107 fn instant_ptr_clone_and_drop_no_ub() {
1108 let base = StdInstant::now();
1109 let a: InstantPtr = base.into();
1110 let b = a.clone();
1111 assert_eq!(a, b);
1113 drop(a);
1116 drop(b);
1117 }
1118}
1119
1120#[cfg(test)]
1121#[allow(clippy::float_cmp)] mod autotest_generated {
1123 use super::*;
1124
1125 fn tick(n: u64) -> Instant {
1128 Instant::Tick(SystemTick::new(n))
1129 }
1130 fn tick_dur(n: u64) -> Duration {
1131 Duration::Tick(SystemTickDiff { tick_diff: n })
1132 }
1133 fn sys_dur(secs: u64, nanos: u32) -> Duration {
1134 Duration::System(SystemTimeDiff { secs, nanos })
1135 }
1136
1137 #[test]
1142 fn timer_id_unique_is_strictly_increasing_and_above_reserved_range() {
1143 let a = TimerId::unique();
1144 let b = TimerId::unique();
1145 assert_ne!(a, b);
1146 assert!(b.id > a.id, "unique() must strictly increase: {a:?} -> {b:?}");
1147 for id in [a, b] {
1149 assert!(
1150 id.id >= USER_TIMER_ID_START,
1151 "unique() handed out a reserved system ID: {id:?}"
1152 );
1153 assert_ne!(id, CURSOR_BLINK_TIMER_ID);
1154 assert_ne!(id, SCROLL_MOMENTUM_TIMER_ID);
1155 assert_ne!(id, DRAG_AUTOSCROLL_TIMER_ID);
1156 assert_ne!(id, TOOLTIP_DELAY_TIMER_ID);
1157 assert_ne!(id, CAPABILITY_PUMP_TIMER_ID);
1158 assert_ne!(id, LONG_PRESS_TIMER_ID);
1159 }
1160 }
1161
1162 #[test]
1163 fn thread_id_unique_is_strictly_increasing_and_above_reserved_range() {
1164 let a = ThreadId::unique();
1165 let b = ThreadId::unique();
1166 assert_ne!(a, b);
1167 assert!(b.id > a.id);
1168 assert!(a.id >= RESERVED_THREAD_ID_COUNT);
1169 }
1170
1171 #[cfg(feature = "std")]
1174 #[test]
1175 fn unique_ids_do_not_collide_across_threads() {
1176 use alloc::collections::BTreeSet;
1177
1178 let handles: Vec<_> = (0..8)
1179 .map(|_| {
1180 std::thread::spawn(|| {
1181 let mut out = Vec::new();
1182 for _ in 0..64 {
1183 out.push((TimerId::unique().id, ThreadId::unique().id));
1184 }
1185 out
1186 })
1187 })
1188 .collect();
1189
1190 let mut timer_ids = BTreeSet::new();
1191 let mut thread_ids = BTreeSet::new();
1192 for h in handles {
1193 for (t, th) in h.join().expect("worker thread panicked") {
1194 assert!(timer_ids.insert(t), "duplicate TimerId handed out: {t}");
1195 assert!(thread_ids.insert(th), "duplicate ThreadId handed out: {th}");
1196 }
1197 }
1198 assert_eq!(timer_ids.len(), 8 * 64);
1199 assert_eq!(thread_ids.len(), 8 * 64);
1200 }
1201
1202 #[cfg(feature = "std")]
1207 #[test]
1208 fn instant_now_is_system_and_monotonic() {
1209 let a = Instant::now();
1210 let b = Instant::now();
1211 assert!(matches!(a, Instant::System(_)));
1212 assert!(a <= b, "Instant::now() went backwards");
1213 assert_eq!(a.duration_since(&b), sys_dur(0, 0));
1215 }
1216
1217 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1218 #[test]
1219 fn get_system_time_libstd_is_monotonic_system_instant() {
1220 let a = get_system_time_libstd();
1221 let b = get_system_time_libstd();
1222 assert!(matches!(a, Instant::System(_)));
1223 assert!(matches!(b, Instant::System(_)));
1224 assert!(a <= b);
1225 }
1226
1227 #[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
1228 #[test]
1229 fn get_system_time_libstd_wasm_fallback_is_zero_tick() {
1230 assert_eq!(get_system_time_libstd(), tick(0));
1233 }
1234
1235 #[test]
1240 fn linear_interpolate_clamps_outside_the_interval() {
1241 assert_eq!(tick(0).linear_interpolate(tick(10), tick(20)), 0.0);
1243 assert_eq!(tick(999).linear_interpolate(tick(10), tick(20)), 1.0);
1244 assert_eq!(tick(10).linear_interpolate(tick(10), tick(20)), 0.0);
1246 assert_eq!(tick(20).linear_interpolate(tick(10), tick(20)), 1.0);
1247 }
1248
1249 #[test]
1250 fn linear_interpolate_reversed_interval_is_normalized() {
1251 let forwards = tick(5).linear_interpolate(tick(0), tick(10));
1254 let backwards = tick(5).linear_interpolate(tick(10), tick(0));
1255 assert_eq!(forwards, backwards);
1256 assert!((backwards - 0.5).abs() < 1e-6);
1257 }
1258
1259 #[test]
1260 fn linear_interpolate_saturating_extremes_stay_in_range() {
1261 let v = tick(u64::MAX / 2).linear_interpolate(tick(0), tick(u64::MAX));
1264 assert!(v.is_finite(), "interpolation over the full u64 span went non-finite");
1265 assert!((0.0..=1.0).contains(&v));
1266 assert!((v - 0.5).abs() < 1e-3, "expected ~0.5, got {v}");
1267
1268 let z = tick(u64::MAX).linear_interpolate(tick(u64::MAX), tick(u64::MAX));
1270 assert_eq!(z, 1.0);
1271 let z0 = tick(0).linear_interpolate(tick(0), tick(0));
1272 assert_eq!(z0, 1.0);
1273 }
1274
1275 #[cfg(feature = "std")]
1276 #[test]
1277 fn linear_interpolate_mismatched_kinds_never_nan() {
1278 let sys = Instant::now();
1281 let cases = [
1282 (tick(5), sys.clone(), tick(10)),
1283 (sys.clone(), tick(0), tick(10)),
1284 (tick(5), tick(0), sys.clone()),
1285 (sys.clone(), sys.clone(), tick(10)),
1286 (tick(5), sys.clone(), sys.clone()),
1287 ];
1288 for (this, start, end) in cases {
1289 let v = this.linear_interpolate(start, end);
1290 assert!(v.is_finite(), "mismatched-kind interpolation returned {v}");
1291 assert!(
1292 (0.0..=1.0).contains(&v),
1293 "mismatched-kind interpolation escaped [0,1]: {v}"
1294 );
1295 }
1296 }
1297
1298 #[test]
1303 fn add_optional_duration_none_is_identity() {
1304 let t = tick(42);
1305 assert_eq!(t.add_optional_duration(None), t);
1306 assert_eq!(tick(u64::MAX).add_optional_duration(None), tick(u64::MAX));
1307 }
1308
1309 #[test]
1310 fn add_optional_duration_tick_saturates_at_u64_max() {
1311 let near_max = tick(u64::MAX - 1);
1313 assert_eq!(
1314 near_max.add_optional_duration(Some(&tick_dur(u64::MAX))),
1315 tick(u64::MAX)
1316 );
1317 assert_eq!(tick(0).add_optional_duration(Some(&tick_dur(0))), tick(0));
1318 }
1319
1320 #[cfg(feature = "std")]
1321 #[test]
1322 fn add_optional_duration_system_advances_by_the_duration() {
1323 let base = Instant::now();
1324 let later = base.add_optional_duration(Some(&Duration::System(SystemTimeDiff::from_secs(1))));
1325 assert!(later > base);
1326 let delta = later.duration_since(&base);
1327 assert_eq!(delta, sys_dur(1, 0));
1328 assert_eq!(base.duration_since(&later), sys_dur(0, 0));
1330 }
1331
1332 #[cfg(feature = "std")]
1333 #[test]
1334 fn add_optional_duration_mismatched_kinds_saturate_both_ways() {
1335 let sys = Instant::now();
1336 assert_eq!(sys.add_optional_duration(Some(&tick_dur(500))), sys);
1338 let t = tick(7);
1340 assert_eq!(t.add_optional_duration(Some(&sys_dur(3, 0))), t);
1341 }
1342
1343 #[cfg(all(feature = "std", not(target_arch = "wasm32")))]
1349 #[test]
1350 #[should_panic(expected = "overflow")]
1351 fn add_optional_duration_system_overflow_panics_today() {
1352 let base = Instant::now();
1353 let _ = base.add_optional_duration(Some(&Duration::max()));
1354 }
1355
1356 #[test]
1361 fn duration_since_tick_saturates_and_is_exact() {
1362 assert_eq!(tick(10).duration_since(&tick(4)), tick_dur(6));
1363 assert_eq!(tick(10).duration_since(&tick(10)), tick_dur(0));
1365 assert_eq!(tick(0).duration_since(&tick(u64::MAX)), tick_dur(0));
1367 assert_eq!(tick(u64::MAX).duration_since(&tick(0)), tick_dur(u64::MAX));
1369 }
1370
1371 #[cfg(feature = "std")]
1372 #[test]
1373 fn duration_since_mismatched_kinds_is_zero_tick_both_directions() {
1374 let sys = Instant::now();
1375 assert_eq!(sys.duration_since(&tick(5)), tick_dur(0));
1376 assert_eq!(tick(5).duration_since(&sys), tick_dur(0));
1377 }
1378
1379 #[cfg(feature = "std")]
1380 #[test]
1381 fn into_std_instant_round_trips_a_system_instant() {
1382 let base = StdInstant::now();
1383 let wrapped: Instant = base.into();
1384 assert_eq!(wrapped.into_std_instant(), base);
1385 }
1386
1387 #[cfg(feature = "std")]
1388 #[test]
1389 #[should_panic(expected = "internal error: entered unreachable code")]
1390 fn into_std_instant_on_tick_variant_panics() {
1391 let _ = tick(1).into_std_instant();
1393 }
1394
1395 #[test]
1400 fn system_tick_new_stores_the_counter_verbatim() {
1401 for n in [0_u64, 1, 0x0100, u64::MAX / 2, u64::MAX] {
1402 assert_eq!(SystemTick::new(n).tick_counter, n);
1403 }
1404 assert!(SystemTick::new(0) < SystemTick::new(u64::MAX));
1406 assert_eq!(SystemTick::new(7), SystemTick::new(7));
1407 }
1408
1409 #[cfg(feature = "std")]
1414 #[test]
1415 fn instant_ptr_get_returns_the_wrapped_instant() {
1416 let base = StdInstant::now();
1417 let p: InstantPtr = base.into();
1418 assert_eq!(p.get(), base);
1419 assert_eq!(p.get(), p.get());
1421 assert!(p.run_destructor);
1422 assert!(!alloc::format!("{p:?}").is_empty());
1424 }
1425
1426 #[cfg(feature = "std")]
1427 #[test]
1428 fn std_instant_clone_deep_copies_and_arms_the_destructor() {
1429 let base = StdInstant::now();
1430 let a: InstantPtr = base.into();
1431 let cloned = std_instant_clone(core::ptr::from_ref(&a));
1432 assert_eq!(cloned.get(), base);
1433 assert!(!core::ptr::eq(&**a.ptr, &**cloned.ptr));
1435 assert!(cloned.run_destructor, "clone handed back a disarmed destructor");
1436 drop(cloned);
1437 assert_eq!(a.get(), base);
1439 }
1440
1441 #[cfg(feature = "std")]
1442 #[test]
1443 fn std_instant_drop_is_a_noop_even_for_null() {
1444 std_instant_drop(core::ptr::null_mut());
1448
1449 let mut p: InstantPtr = StdInstant::now().into();
1450 let before = p.get();
1451 std_instant_drop(core::ptr::from_mut(&mut p));
1452 assert_eq!(p.get(), before);
1454 assert!(p.run_destructor);
1455 }
1456
1457 #[test]
1462 fn duration_display_tick_edge_values() {
1463 assert_eq!(alloc::format!("{}", tick_dur(0)), "0 ticks");
1464 assert_eq!(alloc::format!("{}", tick_dur(1)), "1 ticks");
1465 assert_eq!(
1466 alloc::format!("{}", tick_dur(u64::MAX)),
1467 "18446744073709551615 ticks"
1468 );
1469 }
1470
1471 #[cfg(feature = "std")]
1472 #[test]
1473 fn duration_display_system_edge_values_do_not_panic() {
1474 for d in [
1477 sys_dur(0, 0),
1478 sys_dur(1, 500_000_000),
1479 sys_dur(0, u32::MAX),
1480 sys_dur(u64::MAX, NANOS_PER_SEC - 1),
1481 Duration::max(),
1482 ] {
1483 let s = alloc::format!("{d}");
1484 assert!(!s.is_empty());
1485 assert!(!s.ends_with("ticks"), "System duration formatted as ticks: {s}");
1486 }
1487 }
1488
1489 #[cfg(feature = "std")]
1494 #[test]
1495 fn duration_max_is_the_upper_bound() {
1496 let m = Duration::max();
1497 assert_eq!(m, sys_dur(u64::MAX, NANOS_PER_SEC - 1));
1498 assert!(m.greater_than(&sys_dur(u64::MAX, NANOS_PER_SEC - 2)));
1500 assert!(m.greater_than(&sys_dur(0, 0)));
1501 assert!(!m.greater_than(&m));
1503 assert!(!m.smaller_than(&m));
1504 let Duration::System(inner) = m else {
1506 panic!("Duration::max() is not a System duration under std")
1507 };
1508 assert_eq!(inner.get(), StdDuration::new(u64::MAX, NANOS_PER_SEC - 1));
1509 }
1510
1511 #[test]
1512 fn duration_div_by_zero_yields_inf_or_nan_not_a_panic() {
1513 assert!(tick_dur(0).div(&tick_dur(0)).is_nan());
1515 let inf = tick_dur(5).div(&tick_dur(0));
1516 assert!(inf.is_infinite() && inf.is_sign_positive());
1517
1518 assert!(sys_dur(0, 0).div(&sys_dur(0, 0)).is_nan());
1519 let sinf = sys_dur(1, 0).div(&sys_dur(0, 0));
1520 assert!(sinf.is_infinite() && sinf.is_sign_positive());
1521 }
1522
1523 #[test]
1524 fn duration_div_extremes_stay_finite_in_f32() {
1525 let r = tick_dur(u64::MAX).div(&tick_dur(1));
1528 assert!(r.is_finite(), "u64::MAX tick ratio overflowed f32: {r}");
1529 assert!(r > 1e19);
1530 assert_eq!(tick_dur(u64::MAX).div(&tick_dur(u64::MAX)), 1.0);
1532 assert_eq!(sys_dur(3, 0).div(&sys_dur(2, 0)), 1.5);
1533 }
1534
1535 #[test]
1536 fn duration_div_mismatched_kinds_saturates_to_zero_both_ways() {
1537 assert_eq!(sys_dur(1, 0).div(&tick_dur(10)), 0.0);
1538 assert_eq!(tick_dur(10).div(&sys_dur(1, 0)), 0.0);
1539 }
1540
1541 #[test]
1542 fn duration_min_picks_the_smaller_of_the_same_kind() {
1543 assert_eq!(tick_dur(5).min(tick_dur(10)), tick_dur(5));
1544 assert_eq!(tick_dur(10).min(tick_dur(5)), tick_dur(5));
1545 assert_eq!(tick_dur(7).min(tick_dur(7)), tick_dur(7));
1546 assert_eq!(tick_dur(0).min(tick_dur(u64::MAX)), tick_dur(0));
1547 #[cfg(feature = "std")]
1549 assert_eq!(sys_dur(1, 0).min(sys_dur(1, 1)), sys_dur(1, 0));
1550 }
1551
1552 #[test]
1553 fn duration_min_across_kinds_falls_back_to_other() {
1554 assert_eq!(tick_dur(5).min(sys_dur(1, 0)), sys_dur(1, 0));
1558 assert_eq!(sys_dur(1, 0).min(tick_dur(5)), tick_dur(5));
1559 }
1560
1561 #[test]
1562 fn duration_comparison_is_a_strict_total_order_within_a_kind() {
1563 let mut pairs = alloc::vec![(tick_dur(0), tick_dur(u64::MAX)), (tick_dur(1), tick_dur(2))];
1564 #[cfg(feature = "std")]
1566 pairs.extend_from_slice(&[
1567 (sys_dur(0, 0), sys_dur(u64::MAX, 0)),
1568 (sys_dur(1, 999_999_999), sys_dur(2, 0)),
1569 ]);
1570
1571 for (a, b) in pairs {
1572 assert!(a.smaller_than(&b));
1573 assert!(b.greater_than(&a));
1574 assert!(!a.greater_than(&b));
1575 assert!(!b.smaller_than(&a));
1576 }
1577 let eq = tick_dur(4);
1579 assert!(!eq.greater_than(&eq));
1580 assert!(!eq.smaller_than(&eq));
1581 let eq_sys = sys_dur(4, 2);
1582 assert!(!eq_sys.greater_than(&eq_sys));
1583 assert!(!eq_sys.smaller_than(&eq_sys));
1584 }
1585
1586 #[cfg(feature = "std")]
1587 #[test]
1588 fn duration_comparison_normalizes_denormalized_nanos() {
1589 let denorm = sys_dur(0, u32::MAX);
1592 assert!(denorm.greater_than(&sys_dur(4, 0)));
1593 assert!(denorm.smaller_than(&sys_dur(5, 0)));
1594 }
1595
1596 #[test]
1601 fn system_tick_diff_div_edge_cases() {
1602 let zero = SystemTickDiff { tick_diff: 0 };
1603 let one = SystemTickDiff { tick_diff: 1 };
1604 let max = SystemTickDiff { tick_diff: u64::MAX };
1605
1606 assert!(zero.div(&zero).is_nan());
1607 assert!(one.div(&zero).is_infinite());
1608 assert_eq!(zero.div(&one), 0.0);
1609 assert_eq!(max.div(&max), 1.0);
1610 assert!(max.div(&one).is_finite());
1611 assert_eq!(SystemTickDiff { tick_diff: 5 }.div(&SystemTickDiff { tick_diff: 10 }), 0.5);
1612 }
1613
1614 #[test]
1615 fn system_time_diff_as_secs_f64_is_exact_for_representable_values() {
1616 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.as_secs_f64(), 0.0);
1617 assert_eq!(SystemTimeDiff { secs: 1, nanos: 500_000_000 }.as_secs_f64(), 1.5);
1618 assert_eq!(SystemTimeDiff { secs: 0, nanos: 500_000_000 }.as_secs_f64(), 0.5);
1619 let huge = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
1621 assert!(huge.as_secs_f64().is_finite());
1622 assert!(huge.as_secs_f64() > 1e19);
1623 assert!(
1625 SystemTimeDiff::from_secs(2).as_secs_f64() > SystemTimeDiff::from_secs(1).as_secs_f64()
1626 );
1627 }
1628
1629 #[test]
1630 fn system_time_diff_div_edge_cases() {
1631 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
1632 let one = SystemTimeDiff::from_secs(1);
1633 let half = SystemTimeDiff { secs: 0, nanos: 500_000_000 };
1634
1635 assert!(zero.div(&zero).is_nan());
1636 assert!(one.div(&zero).is_infinite());
1637 assert_eq!(zero.div(&one), 0.0);
1638 assert_eq!(one.div(&one), 1.0);
1639 assert_eq!(one.div(&half), 2.0);
1640 let max = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
1641 assert_eq!(max.div(&max), 1.0);
1642 assert!(max.div(&one).is_finite());
1643 }
1644
1645 #[test]
1650 fn from_secs_invariants() {
1651 for s in [0_u64, 1, 1_000, u64::MAX] {
1652 let d = SystemTimeDiff::from_secs(s);
1653 assert_eq!(d.secs, s);
1654 assert_eq!(d.nanos, 0, "from_secs must leave nanos at zero");
1655 }
1656 }
1657
1658 #[test]
1659 fn from_millis_normalizes_and_keeps_nanos_in_range() {
1660 assert_eq!(SystemTimeDiff::from_millis(0), SystemTimeDiff { secs: 0, nanos: 0 });
1661 assert_eq!(
1662 SystemTimeDiff::from_millis(999),
1663 SystemTimeDiff { secs: 0, nanos: 999_000_000 }
1664 );
1665 assert_eq!(SystemTimeDiff::from_millis(1_000), SystemTimeDiff { secs: 1, nanos: 0 });
1666 assert_eq!(
1667 SystemTimeDiff::from_millis(1_500),
1668 SystemTimeDiff { secs: 1, nanos: 500_000_000 }
1669 );
1670 let max = SystemTimeDiff::from_millis(u64::MAX);
1672 assert!(max.nanos < NANOS_PER_SEC, "from_millis produced denormalized nanos");
1673 assert_eq!(max.secs, u64::MAX / MILLIS_PER_SEC);
1674 }
1675
1676 #[test]
1677 fn from_nanos_normalizes_and_keeps_nanos_in_range() {
1678 assert_eq!(SystemTimeDiff::from_nanos(0), SystemTimeDiff { secs: 0, nanos: 0 });
1679 assert_eq!(
1680 SystemTimeDiff::from_nanos(999_999_999),
1681 SystemTimeDiff { secs: 0, nanos: 999_999_999 }
1682 );
1683 assert_eq!(
1684 SystemTimeDiff::from_nanos(1_000_000_000),
1685 SystemTimeDiff { secs: 1, nanos: 0 }
1686 );
1687 for n in [0_u64, 1, 999_999_999, 1_000_000_001, u64::MAX] {
1688 let d = SystemTimeDiff::from_nanos(n);
1689 assert!(d.nanos < NANOS_PER_SEC, "from_nanos({n}) produced denormalized nanos");
1690 let back =
1692 u128::from(d.secs) * u128::from(NANOS_PER_SEC) + u128::from(d.nanos);
1693 assert_eq!(back, u128::from(n), "from_nanos({n}) lost information");
1694 }
1695 }
1696
1697 #[test]
1702 fn millis_round_trips_through_from_millis() {
1703 for m in [0_u64, 1, 999, 1_000, 1_500, 86_400_000, u64::MAX] {
1706 assert_eq!(
1707 SystemTimeDiff::from_millis(m).millis(),
1708 m,
1709 "from_millis({m}).millis() is not lossless"
1710 );
1711 }
1712 }
1713
1714 #[test]
1715 fn millis_truncates_and_saturates_instead_of_panicking() {
1716 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999 }.millis(), 0);
1718 assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999_999 }.millis(), 999);
1719 assert_eq!(SystemTimeDiff { secs: u64::MAX, nanos: 0 }.millis(), u64::MAX);
1721 assert_eq!(
1722 SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 }.millis(),
1723 u64::MAX
1724 );
1725 assert_eq!(SystemTimeDiff::from_secs(u64::MAX / 1_000).millis(), (u64::MAX / 1_000) * 1_000);
1726 }
1727
1728 #[test]
1733 fn checked_add_carries_nanos_into_secs() {
1734 let a = SystemTimeDiff { secs: 0, nanos: 999_999_999 };
1735 let sum = a.checked_add(a).expect("0.999s + 0.999s must not overflow");
1736 assert_eq!(sum, SystemTimeDiff { secs: 1, nanos: 999_999_998 });
1737 let b = SystemTimeDiff { secs: 1, nanos: 500_000_000 };
1739 assert_eq!(
1740 b.checked_add(b),
1741 Some(SystemTimeDiff { secs: 3, nanos: 0 })
1742 );
1743 }
1744
1745 #[test]
1746 fn checked_add_returns_none_on_overflow_instead_of_panicking() {
1747 let max_secs = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
1748 assert_eq!(max_secs.checked_add(SystemTimeDiff::from_secs(1)), None);
1750 assert_eq!(
1752 max_secs.checked_add(SystemTimeDiff { secs: 0, nanos: NANOS_PER_SEC - 1 }),
1753 Some(SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 })
1754 );
1755 let brim = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
1757 assert_eq!(brim.checked_add(SystemTimeDiff { secs: 0, nanos: 1 }), None);
1758 }
1759
1760 #[test]
1761 fn checked_add_identity_and_commutativity() {
1762 let zero = SystemTimeDiff { secs: 0, nanos: 0 };
1763 for d in [
1764 SystemTimeDiff::from_secs(0),
1765 SystemTimeDiff::from_millis(1_500),
1766 SystemTimeDiff::from_nanos(u64::MAX),
1767 SystemTimeDiff { secs: u64::MAX, nanos: 0 },
1768 ] {
1769 assert_eq!(d.checked_add(zero), Some(d));
1770 assert_eq!(zero.checked_add(d), Some(d));
1771 let other = SystemTimeDiff::from_millis(750);
1773 assert_eq!(d.checked_add(other), other.checked_add(d));
1774 }
1775 }
1776
1777 #[cfg(feature = "std")]
1782 #[test]
1783 fn system_time_diff_get_round_trips_std_duration() {
1784 for std_d in [
1785 StdDuration::ZERO,
1786 StdDuration::from_millis(1_500),
1787 StdDuration::from_nanos(1),
1788 StdDuration::new(u64::MAX, NANOS_PER_SEC - 1),
1789 ] {
1790 let mid: SystemTimeDiff = std_d.into();
1791 assert_eq!(mid.get(), std_d, "StdDuration -> SystemTimeDiff -> StdDuration lost data");
1792 }
1793 }
1794
1795 #[cfg(feature = "std")]
1796 #[test]
1797 fn system_time_diff_get_on_edge_values_does_not_panic() {
1798 assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.get(), StdDuration::ZERO);
1799 assert_eq!(
1801 SystemTimeDiff::from_secs(u64::MAX).get(),
1802 StdDuration::new(u64::MAX, 0)
1803 );
1804 assert_eq!(
1806 SystemTimeDiff { secs: 0, nanos: u32::MAX }.get(),
1807 StdDuration::new(0, u32::MAX)
1808 );
1809 }
1810
1811 #[cfg(feature = "std")]
1816 extern "C" fn test_thread_recv(ptr: *const c_void) -> OptionThreadSendMsg {
1817 let receiver = unsafe { &*(ptr.cast::<Receiver<ThreadSendMsg>>()) };
1820 receiver.try_recv().ok().into()
1821 }
1822
1823 #[cfg(feature = "std")]
1824 const extern "C" fn test_thread_recv_destructor(_: *mut ThreadReceiverInner) {}
1825
1826 #[cfg(feature = "std")]
1827 fn test_receiver() -> (Sender<ThreadSendMsg>, ThreadReceiver) {
1828 let (tx, rx) = std::sync::mpsc::channel::<ThreadSendMsg>();
1829 let inner = ThreadReceiverInner {
1830 ptr: Box::new(rx),
1831 recv_fn: ThreadRecvCallback { cb: test_thread_recv },
1832 destructor: ThreadReceiverDestructorCallback {
1833 cb: test_thread_recv_destructor,
1834 },
1835 };
1836 (tx, ThreadReceiver::new(inner))
1837 }
1838
1839 #[cfg(feature = "std")]
1840 #[test]
1841 fn thread_receiver_new_arms_destructor_and_has_no_ctx() {
1842 let (_tx, r) = test_receiver();
1843 assert!(r.run_destructor, "ThreadReceiver::new left the destructor disarmed");
1844 assert!(r.get_ctx().is_none(), "a fresh receiver must have no FFI context");
1845 }
1846
1847 #[cfg(feature = "std")]
1848 #[test]
1849 fn thread_receiver_recv_on_empty_and_disconnected_channel_is_none() {
1850 let (tx, mut r) = test_receiver();
1851 assert!(r.recv().is_none());
1853 drop(tx);
1855 assert!(r.recv().is_none());
1856 assert!(r.recv().is_none());
1857 }
1858
1859 #[cfg(feature = "std")]
1860 #[test]
1861 fn thread_receiver_recv_delivers_messages_in_order() {
1862 let (tx, mut r) = test_receiver();
1863 tx.send(ThreadSendMsg::Tick).unwrap();
1864 tx.send(ThreadSendMsg::Custom(RefAny::new(42_u32))).unwrap();
1865 tx.send(ThreadSendMsg::TerminateThread).unwrap();
1866
1867 assert_eq!(r.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
1868 assert!(matches!(
1869 r.recv(),
1870 OptionThreadSendMsg::Some(ThreadSendMsg::Custom(_))
1871 ));
1872 assert_eq!(
1873 r.recv(),
1874 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
1875 );
1876 assert!(r.recv().is_none());
1878 }
1879
1880 #[cfg(feature = "std")]
1881 #[test]
1882 fn thread_receiver_clone_shares_the_same_channel() {
1883 let (tx, mut a) = test_receiver();
1884 let mut b = a.clone();
1885 assert!(b.run_destructor);
1886
1887 tx.send(ThreadSendMsg::Tick).unwrap();
1888 assert_eq!(a.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
1892 assert!(b.recv().is_none());
1893
1894 tx.send(ThreadSendMsg::TerminateThread).unwrap();
1895 assert_eq!(
1896 b.recv(),
1897 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
1898 );
1899 assert!(a.recv().is_none());
1900 }
1901
1902 #[cfg(feature = "std")]
1903 #[test]
1904 fn thread_receiver_get_ctx_clones_rather_than_takes() {
1905 let (_tx, mut r) = test_receiver();
1906 r.ctx = OptionRefAny::Some(RefAny::new(7_u64));
1907 assert!(r.get_ctx().is_some());
1910 assert!(r.get_ctx().is_some());
1911 let held = r.get_ctx();
1912 drop(r);
1913 assert!(held.is_some());
1915 }
1916}