Skip to main content

azul_core/
task.rs

1//! Timer and thread management for asynchronous operations.
2//!
3//! This module provides:
4//! - `TimerId` / `ThreadId`: Unique identifiers for timers and background threads
5//! - `Instant` / `Duration`: Cross-platform time types (works on no_std with tick counters)
6//! - `ThreadReceiver`: Channel for receiving messages from the main thread
7//! - Callback types for thread communication and system time queries
8
9#[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/// Should a timer terminate or not - used to remove active timers
52#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[repr(C)]
54pub enum TerminateTimer {
55    /// Remove the timer from the list of active timers
56    Terminate,
57    /// Do nothing and let the timers continue to run
58    Continue,
59}
60
61// ============================================================================
62// Reserved System Timer IDs (0x0000 - 0x00FF)
63// ============================================================================
64// User timers start at 0x0100 to avoid conflicts with system timers.
65// These constants define well-known timer IDs for internal framework use.
66
67/// Timer ID for cursor blinking in contenteditable elements (~530ms interval)
68pub const CURSOR_BLINK_TIMER_ID: TimerId = TimerId { id: 0x0001 };
69/// Timer ID for scroll momentum/inertia animation
70pub const SCROLL_MOMENTUM_TIMER_ID: TimerId = TimerId { id: 0x0002 };
71/// Timer ID for auto-scroll during drag operations near edges
72pub const DRAG_AUTOSCROLL_TIMER_ID: TimerId = TimerId { id: 0x0003 };
73/// Timer ID for tooltip show delay.
74///
75/// Started by the platform event loop when the hover target changes to a node
76/// that advertises a tooltip source (`aria-label` / `alt` / `title`); fires
77/// once after `SystemStyle::input_metrics.hover_time_ms` (`SPI_GETMOUSEHOVERTIME`
78/// on Windows, default 400ms) and emits a `ShowTooltip` `CallbackChange`. The
79/// timer is torn down on hover loss, which also emits `HideTooltip`.
80///
81/// Double-click detection used to live on a neighbouring reserved ID but is
82/// now handled entirely by `GestureManager::detect_double_click`, so no
83/// equivalent `DOUBLE_CLICK_TIMER_ID` exists.
84pub const TOOLTIP_DELAY_TIMER_ID: TimerId = TimerId { id: 0x0004 };
85/// Timer ID for the single-threaded capability pump (MWA-A1).
86///
87/// Armed by `sync_capability_pump_timer` whenever a capability source needs
88/// polling or draining while the app is otherwise idle (gamepad listeners,
89/// sensor listeners, an active geolocation subscription). Each tick wakes the
90/// blocked platform loop; `invoke_expired_timers` then runs an event pass,
91/// whose top-of-pass pump drains the async capability channels. There is NO
92/// pump thread by design — a recurring shell timer is the only wake
93/// mechanism, so the identical code path works on WASM (no threads).
94pub const CAPABILITY_PUMP_TIMER_ID: TimerId = TimerId { id: 0x0005 };
95/// Timer ID for the one-shot long-press wake-up (MWA-B12).
96///
97/// Armed on every `MouseDown` for the long-press threshold: a motionless
98/// press generates no further events, so no pass would ever evaluate
99/// `detect_long_press` — this timer wakes the loop exactly once at the
100/// threshold, `invoke_expired_timers` runs an event pass, and the
101/// detection fires (or doesn't — moved/released holds are no-ops).
102pub const LONG_PRESS_TIMER_ID: TimerId = TimerId { id: 0x0006 };
103
104/// First available ID for user-defined timers
105pub const USER_TIMER_ID_START: usize = 0x0100;
106
107// User timers start at 0x0100 to avoid conflicts with reserved system timer IDs
108static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
109
110/// ID for uniquely identifying a timer
111#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
112#[repr(C)]
113pub struct TimerId {
114    pub id: usize,
115}
116
117impl TimerId {
118    /// Generates a new, unique `TimerId`.
119    #[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
139// Thread IDs 0-4 are reserved for internal framework use.
140// User threads start at RESERVED_THREAD_ID_COUNT.
141const RESERVED_THREAD_ID_COUNT: usize = 5;
142static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
143
144/// ID for uniquely identifying a background thread
145#[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    /// Generates a new, unique `ThreadId`.
165    #[must_use]
166    pub fn unique() -> Self {
167        Self {
168            id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
169        }
170    }
171}
172
173/// A point in time, either from the system clock or a tick counter.
174///
175/// Use `Instant::System` on platforms with std, `Instant::Tick` on `embedded/no_std`.
176#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
177#[repr(C, u8)]
178pub enum Instant {
179    /// System time from `std::time::Instant` (requires "std" feature)
180    System(InstantPtr),
181    /// Tick-based time for embedded systems without a real-time clock
182    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/// Injectable test-clock offset, in milliseconds, added to every `Instant::now()`.
193///
194/// Driven by the E2E `tick_ms` op. Everything time-driven in the engine — scroll
195/// momentum, scrollbar fade, cursor blink, animations, timers — reads the clock
196/// through `Instant::now()` / `get_system_time_libstd()`, so advancing this
197/// offset moves all of them forward by exactly N ms WITHOUT sleeping. That is
198/// what makes "drive the animation to completion and assert it converges"
199/// deterministic instead of a `wait { ms }` race.
200///
201/// Zero in production; only the debug-server `tick_ms` op ever writes it.
202#[cfg(feature = "std")]
203pub static TEST_CLOCK_OFFSET_MS: core::sync::atomic::AtomicU64 =
204    core::sync::atomic::AtomicU64::new(0);
205
206/// Advance the injectable test clock by `ms` (E2E `tick_ms`).
207#[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/// The current test-clock offset in ms (0 unless `tick_ms` was used).
213#[cfg(feature = "std")]
214#[must_use]
215pub fn test_clock_offset_ms() -> u64 {
216    TEST_CLOCK_OFFSET_MS.load(Ordering::SeqCst)
217}
218
219/// `std::time::Instant::now()` shifted by the injectable test-clock offset.
220#[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    /// Returns the current system time.
232    ///
233    /// On systems with std, this uses `std::time::Instant::now()`.
234    /// On `no_std` systems, this returns a zero tick.
235    #[cfg(feature = "std")]
236    #[must_use] pub fn now() -> Self {
237        std_now_with_test_offset().into()
238    }
239
240    /// Returns the current system time (no_std fallback).
241    #[cfg(not(feature = "std"))]
242    pub fn now() -> Self {
243        Instant::Tick(SystemTick::new(0))
244    }
245
246    /// Returns a number from 0.0 to 1.0 indicating the current
247    /// linear interpolation value between (start, end)
248    #[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        // Zero-length interval: `duration_current / duration_total` would be
263        // `0/0 = NaN`. Treat a collapsed interval as fully elapsed (1.0) rather
264        // than propagating NaN into animation progress.
265        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    /// Adds a duration to the instant, does nothing in undefined cases
280    /// (i.e. trying to add a `Duration::Tick` to an `Instant::System`).
281    ///
282    /// Mismatched kinds (`System` instant + `Tick` duration, or vice versa)
283    /// saturate to `self` unchanged instead of panicking — a stray mismatch
284    /// must never crash the event loop.
285    #[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                        // A `System` instant cannot be constructed on no_std, so
298                        // this arm is unreachable in practice; return self rather
299                        // than aborting.
300                        let _ = (i, d);
301                        self.clone()
302                    }
303                }
304                (Self::Tick(s), Duration::Tick(d)) => Self::Tick(SystemTick {
305                    // Saturate so a runaway tick delta cannot overflow-panic.
306                    tick_counter: s.tick_counter.saturating_add(d.tick_diff),
307                }),
308                // Mismatched kinds: undefined operation -> do nothing (saturate).
309                _ => self.clone(),
310            })
311    }
312
313    /// Converts to `std::time::Instant` (panics if Tick variant).
314    #[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    /// Calculates the duration since an earlier point in time.
323    ///
324    /// Saturates to a zero duration in the degenerate cases (earlier is actually
325    /// *later* than `self`, or the two instants are of mismatched kinds) instead
326    /// of panicking — this runs on the hot event-loop path and must not crash.
327    #[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                    // `saturating_duration_since` yields 0 if `prev` is later
335                    // than `now` (monotonic-clock skew / reordered instants).
336                    Duration::System(now_instant.saturating_duration_since(prev_instant).into())
337                }
338                #[cfg(not(feature = "std"))]
339                {
340                    // Unreachable on no_std (no System instants); saturate to 0.
341                    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                // Saturate: a "negative" span (prev > now) clamps to 0.
350                tick_diff: now.saturating_sub(*prev),
351            }),
352            // Mismatched kinds: no meaningful span -> saturate to 0.
353            _ => Duration::Tick(SystemTickDiff { tick_diff: 0 }),
354        }
355    }
356}
357
358/// Tick-based timestamp for systems without a real-time clock.
359///
360/// Used on embedded systems where time is measured in frame ticks or cycles.
361#[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    /// Creates a new tick timestamp from a counter value.
369    #[must_use] pub const fn new(tick_counter: u64) -> Self {
370        Self { tick_counter }
371    }
372}
373
374/// FFI-safe wrapper around `std::time::Instant` with custom clone/drop callbacks.
375///
376/// Allows crossing FFI boundaries while maintaining proper memory management.
377#[repr(C)]
378pub struct InstantPtr {
379    /// `ManuallyDrop` so the owned `Box` is freed ONLY when `run_destructor` is
380    /// still set (see `Drop`). The codegen FFI wrappers (`AzTimerCallbackInfo`
381    /// etc.) embed this by value AND have their own `Drop` that `drop_in_place`s
382    /// the real type first; Rust's drop glue would then drop this `ptr` field a
383    /// SECOND time on the same bytes. Gating the `Box` free on `run_destructor`
384    /// (cleared by the first drop) makes that second drop a safe no-op. Layout is
385    /// unchanged: `ManuallyDrop<Box<T>>` is one pointer, like the old `Box<T>`.
386    #[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// ----  LIBSTD implementation for InstantPtr BEGIN
410#[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            // Free the owned Box exactly once, here under the run_destructor guard.
535            // A second drop on the same bytes (the codegen wrapper's field-drop after
536            // its own `_delete` already ran the real drop) sees run_destructor=false
537            // and skips this -> no double-free. (non-std `ptr` is a raw POD pointer
538            // freed by the destructor callback above, so nothing to drop here.)
539            // SAFETY: `run_destructor` is set false above, so this arm runs at
540            // most once per InstantPtr value; the `Box` inside was never moved
541            // out, so it is live and owned here and safe to drop exactly once.
542            #[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// ----  LIBSTD implementation for InstantPtr END
554
555/// A span of time, either from the system clock or as tick difference.
556///
557/// Mirrors `Instant` variants - System durations work with System instants,
558/// Tick durations work with Tick instants.
559#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
560#[repr(C, u8)]
561pub enum Duration {
562    /// System duration from `std::time::Duration` (requires "std" feature)
563    System(SystemTimeDiff),
564    /// Tick-based duration for embedded systems
565    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    /// Returns the maximum possible duration.
592    #[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    /// Divides this duration by another, returning the ratio as f32.
606    // the f64 ratio is intentionally narrowed to the f32 return type; the value
607    // is a duration ratio, far inside f32's range.
608    #[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    /// Returns the smaller of two durations.
619    #[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    /// Returns true if self > other.
628    ///
629    /// Mismatched kinds (comparing a System duration with a Tick duration) are
630    /// undefined and saturate to `false` instead of panicking.
631    #[allow(unused_variables)]
632    #[must_use] pub fn greater_than(&self, other: &Self) -> bool {
633        match (self, other) {
634            // self > other
635            (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    /// Returns true if self < other.
653    ///
654    /// Mismatched kinds (comparing a System duration with a Tick duration) are
655    /// undefined and saturate to `false` instead of panicking.
656    #[allow(unused_variables)]
657    #[must_use] pub fn smaller_than(&self, other: &Self) -> bool {
658        // self < other
659        match (self, other) {
660            // self > other
661            (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/// Represents a difference in ticks for systems that
680/// don't support timing
681#[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    /// Divide duration A by duration B.
689    /// Returns `Inf` or `NaN` if `other` is zero.
690    // tick counts -> f64 for the ratio; precision only degrades past 2^53 ticks.
691    #[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/// Duration represented as seconds + nanoseconds (mirrors `std::time::Duration`).
698#[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    /// Divide duration A by duration B.
707    /// Returns `Inf` or `NaN` if `other` is zero.
708    #[must_use] pub fn div(&self, other: &Self) -> f64 {
709        self.as_secs_f64() / other.as_secs_f64()
710    }
711    // secs (u64) -> f64 loses precision only past 2^53 seconds (~285M years).
712    #[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    /// Creates a duration from whole seconds.
741    #[must_use] pub const fn from_secs(secs: u64) -> Self {
742        Self { secs, nanos: 0 }
743    }
744    /// Creates a duration from milliseconds.
745    #[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    /// Creates a duration from nanoseconds.
752    // const fn (no const TryFrom); `nanos % NANOS_PER_SEC` is always < 10^9, which
753    // fits u32, so the narrowing cast cannot truncate.
754    #[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    /// Adds two durations, returning None on overflow.
762    #[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    /// Returns the total duration in milliseconds.
780    ///
781    /// Saturates at `u64::MAX` instead of overflow-panicking for enormous
782    /// `secs` values (`secs * 1000` overflows around ~1.8e16 seconds).
783    #[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    /// Converts to `std::time::Duration`.
790    #[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)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
808/// Message that can be sent from the main thread to the Thread using the `ThreadId`.
809///
810/// The thread can ignore the event.
811#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
812#[repr(C, u8)]
813pub enum ThreadSendMsg {
814    /// The thread should terminate at the nearest
815    TerminateThread,
816    /// Next frame tick
817    Tick,
818    /// Custom data
819    Custom(RefAny),
820}
821
822impl_option!(
823    ThreadSendMsg,
824    OptionThreadSendMsg,
825    copy = false,
826    [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
827);
828
829/// Channel endpoint for receiving messages from the main thread in a background thread.
830///
831/// Thread-safe wrapper around the receiver end of a message channel.
832#[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    /// For FFI: stores the foreign callable (e.g., `PyFunction`)
841    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    /// Creates a new receiver (no-op on no_std).
862    #[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    /// Creates a new receiver wrapping the inner channel.
872    #[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    /// Get the FFI context (e.g., Python callable)
882    #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
883        self.ctx.clone()
884    }
885
886    /// Receives a message (returns None on no_std).
887    #[cfg(not(feature = "std"))]
888    pub fn recv(&mut self) -> OptionThreadSendMsg {
889        None.into()
890    }
891
892    /// Receives a message from the main thread, if available.
893    #[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/// Inner receiver state containing the actual channel and callbacks.
903#[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
958/// Get the current system type, equivalent to `std::time::Instant::now()`, except it
959/// also works on systems that don't have a clock (such as embedded timers)
960pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
961#[repr(C)]
962pub struct GetSystemTimeCallback {
963    pub cb: GetSystemTimeCallbackType,
964}
965impl_callback_simple!(GetSystemTimeCallback);
966
967/// Default implementation that gets the current system time.
968///
969/// On WASM targets `std::time::Instant::now()` panics, so we fall back to
970/// a zero-tick instant instead.
971#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
972#[must_use] pub extern "C" fn get_system_time_libstd() -> Instant {
973    // Honours the injectable E2E test clock (see TEST_CLOCK_OFFSET_MS).
974    std_now_with_test_offset().into()
975}
976
977/// Fallback for WASM (where `Instant::now()` panics) and no-std targets.
978#[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
983/// Callback to check if a thread has finished execution.
984pub type CheckThreadFinishedCallbackType =
985    extern "C" fn(/* dropcheck */ *const c_void) -> bool;
986/// Wrapper for thread completion check callback.
987#[repr(C)]
988pub struct CheckThreadFinishedCallback {
989    pub cb: CheckThreadFinishedCallbackType,
990}
991impl_callback_simple!(CheckThreadFinishedCallback);
992
993/// Callback to send a message to a background thread.
994pub type LibrarySendThreadMsgCallbackType =
995    extern "C" fn(/* Sender<ThreadSendMsg> */ *const c_void, ThreadSendMsg) -> bool;
996/// Wrapper for thread message send callback.
997#[repr(C)]
998pub struct LibrarySendThreadMsgCallback {
999    pub cb: LibrarySendThreadMsgCallbackType,
1000}
1001impl_callback_simple!(LibrarySendThreadMsgCallback);
1002
1003/// Callback for a running thread to receive messages from the main thread.
1004pub type ThreadRecvCallbackType =
1005    extern "C" fn(/* receiver.ptr */ *const c_void) -> OptionThreadSendMsg;
1006/// Wrapper for thread message receive callback.
1007#[repr(C)]
1008pub struct ThreadRecvCallback {
1009    pub cb: ThreadRecvCallbackType,
1010}
1011impl_callback_simple!(ThreadRecvCallback);
1012
1013/// Callback to destroy a `ThreadReceiver`.
1014pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
1015/// Wrapper for thread receiver destructor callback.
1016#[repr(C)]
1017pub struct ThreadReceiverDestructorCallback {
1018    pub cb: ThreadReceiverDestructorCallbackType,
1019}
1020impl_callback_simple!(ThreadReceiverDestructorCallback);
1021
1022#[cfg(test)]
1023#[allow(clippy::float_cmp)] // exact-value assertions on interpolation results
1024mod 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        // earlier is actually later -> saturate to zero, no panic.
1054        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        // greater_than / smaller_than across kinds must not panic (saturate to false).
1061        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        // Adding a System duration to a Tick instant is undefined -> returns self.
1073        let out = inst.add_optional_duration(Some(&sys_dur(1, 0)));
1074        assert_eq!(out, tick(100));
1075        // Matching kinds add and saturate.
1076        let out2 = inst.add_optional_duration(Some(&tick_dur(5)));
1077        assert_eq!(out2, tick(105));
1078        // Saturating add: near-max tick doesn't overflow-panic.
1079        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        // Dividing a Tick duration by a System duration is undefined -> 0.0.
1095        assert_eq!(tick_dur(10).div(&sys_dur(1, 0)), 0.0);
1096        // Matching Tick kinds divide normally.
1097        assert!((tick_dur(5).div(&tick_dur(10)) - 0.5).abs() < 1e-6);
1098    }
1099
1100    // Exercises the `unsafe` pointer work in `std_instant_clone` (`&*ptr`) and
1101    // the `ManuallyDrop::drop` guard in `InstantPtr::drop`: build an InstantPtr,
1102    // clone it (goes through the FFI clone callback + raw-ptr deref), then let
1103    // both drop. Under Miri this asserts the clone/drop path is UB-free and the
1104    // owned `Box` is freed exactly once per value (no double-free).
1105    #[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        // The clone must observe the same underlying instant.
1112        assert_eq!(a, b);
1113        // Both `a` and `b` own independent Boxes; dropping both must not
1114        // double-free (each has run_destructor == true).
1115        drop(a);
1116        drop(b);
1117    }
1118}
1119
1120#[cfg(test)]
1121#[allow(clippy::float_cmp)] // exact-value assertions on ratios / interpolation results
1122mod autotest_generated {
1123    use super::*;
1124
1125    // ---- helpers -----------------------------------------------------------
1126
1127    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    // ========================================================================
1138    // TimerId::unique / ThreadId::unique  (monotonic, never hits reserved IDs)
1139    // ========================================================================
1140
1141    #[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        // User IDs must never land inside the reserved system-timer block.
1148        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    // The counters are `AtomicUsize` + `fetch_add`, so concurrent callers must
1172    // never be handed the same ID. 8 threads x 64 IDs => 512 distinct values.
1173    #[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    // ========================================================================
1203    // Instant::now / get_system_time_libstd
1204    // ========================================================================
1205
1206    #[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        // A later instant is never "before" an earlier one.
1214        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        // On WASM / no_std `StdInstant::now()` would panic, so the fallback must
1231        // hand back a tick instant instead of exploding.
1232        assert_eq!(get_system_time_libstd(), tick(0));
1233    }
1234
1235    // ========================================================================
1236    // Instant::linear_interpolate  (must never return NaN / escape [0.0, 1.0])
1237    // ========================================================================
1238
1239    #[test]
1240    fn linear_interpolate_clamps_outside_the_interval() {
1241        // before start -> 0.0, after end -> 1.0 (never negative / >1).
1242        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        // exactly on the boundaries
1245        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        // `end < start` is swapped internally, so the ratio is the same as the
1252        // correctly-ordered call rather than a garbage / negative value.
1253        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        // Full u64 span: the tick diff hits u64::MAX and the f64->f32 narrowing
1262        // must not produce inf/NaN.
1263        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        // Degenerate zero-length interval at the extremes -> 1.0, not 0/0 = NaN.
1269        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        // Every mismatched (System / Tick) permutation feeds a 0/0 division
1279        // internally; the guard must turn that into a finite value in [0, 1].
1280        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    // ========================================================================
1299    // Instant::add_optional_duration
1300    // ========================================================================
1301
1302    #[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        // saturating_add: u64::MAX-1 + huge must clamp, not wrap or panic.
1312        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        // ... and the reverse span saturates to zero rather than going negative.
1329        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        // System instant + Tick duration -> unchanged.
1337        assert_eq!(sys.add_optional_duration(Some(&tick_dur(500))), sys);
1338        // Tick instant + System duration -> unchanged.
1339        let t = tick(7);
1340        assert_eq!(t.add_optional_duration(Some(&sys_dur(3, 0))), t);
1341    }
1342
1343    // A `System` instant plus an enormous `System` duration overflows the
1344    // platform clock representation: `StdInstant + StdDuration` panics with
1345    // "overflow when adding duration to instant". Unlike the mismatched-kind
1346    // case (documented to saturate), this arm has no guard -- characterized
1347    // here so a future saturating fix flips this test loudly.
1348    #[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    // ========================================================================
1357    // Instant::duration_since / into_std_instant
1358    // ========================================================================
1359
1360    #[test]
1361    fn duration_since_tick_saturates_and_is_exact() {
1362        assert_eq!(tick(10).duration_since(&tick(4)), tick_dur(6));
1363        // self == earlier -> zero span
1364        assert_eq!(tick(10).duration_since(&tick(10)), tick_dur(0));
1365        // earlier is later -> saturate to zero, no underflow panic
1366        assert_eq!(tick(0).duration_since(&tick(u64::MAX)), tick_dur(0));
1367        // full-range span does not overflow
1368        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        // Documented: `into_std_instant` is `unreachable!()` for Tick instants.
1392        let _ = tick(1).into_std_instant();
1393    }
1394
1395    // ========================================================================
1396    // SystemTick::new
1397    // ========================================================================
1398
1399    #[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        // Ordering follows the counter (used by Instant's derived Ord).
1405        assert!(SystemTick::new(0) < SystemTick::new(u64::MAX));
1406        assert_eq!(SystemTick::new(7), SystemTick::new(7));
1407    }
1408
1409    // ========================================================================
1410    // InstantPtr: get / std_instant_clone / std_instant_drop
1411    // ========================================================================
1412
1413    #[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        // `get` is a copy, not a move: repeated reads stay stable.
1420        assert_eq!(p.get(), p.get());
1421        assert!(p.run_destructor);
1422        // Debug must not panic and must not be empty.
1423        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        // The clone owns its OWN box (freeing both must not double-free).
1434        assert!(!core::ptr::eq(&**a.ptr, &**cloned.ptr));
1435        assert!(cloned.run_destructor, "clone handed back a disarmed destructor");
1436        drop(cloned);
1437        // The source survives its clone being dropped.
1438        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        // The libstd destructor callback is deliberately empty: the Box is freed
1445        // by `InstantPtr::drop` under the `run_destructor` guard. Calling it with
1446        // a null pointer must therefore be harmless.
1447        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        // Value is untouched and still owned afterwards.
1453        assert_eq!(p.get(), before);
1454        assert!(p.run_destructor);
1455    }
1456
1457    // ========================================================================
1458    // Duration::fmt (Display)
1459    // ========================================================================
1460
1461    #[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        // zero, sub-second, denormalized nanos and the absolute maximum all have
1475        // to format without panicking and without producing an empty string.
1476        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    // ========================================================================
1490    // Duration::max / div / min / greater_than / smaller_than
1491    // ========================================================================
1492
1493    #[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        // Nothing of the same kind is greater than it...
1499        assert!(m.greater_than(&sys_dur(u64::MAX, NANOS_PER_SEC - 2)));
1500        assert!(m.greater_than(&sys_dur(0, 0)));
1501        // ... and it is not greater/smaller than itself.
1502        assert!(!m.greater_than(&m));
1503        assert!(!m.smaller_than(&m));
1504        // Converting the maximum back to std must not overflow-panic.
1505        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        // 0/0 -> NaN, x/0 -> +inf. Neither may panic.
1514        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        // u64::MAX / 1 ~= 1.8e19, comfortably inside f32 range: the f64 -> f32
1526        // narrowing must not produce inf.
1527        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        // Identity ratios are exactly 1.0 for both kinds.
1531        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        // System comparisons need the std conversion (no_std saturates to false).
1548        #[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        // `min` is built on `smaller_than`, which saturates to `false` for
1555        // mismatched kinds -> `min` degenerates to "return `other`". Pinned here
1556        // because it makes `min` non-commutative across kinds.
1557        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        // System ordering only exists under std; no_std saturates it to `false`.
1565        #[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        // Equal values: neither greater nor smaller (holds for both kinds).
1578        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        // nanos == u32::MAX (> 1e9) is denormalized; the std conversion carries
1590        // it into secs, so {0, u32::MAX} == 4.294967295s > 4s.
1591        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    // ========================================================================
1597    // SystemTickDiff::div / SystemTimeDiff::div + as_secs_f64
1598    // ========================================================================
1599
1600    #[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        // Extremes stay finite (u64::MAX secs ~= 1.8e19, well inside f64).
1620        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        // Monotone in secs.
1624        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    // ========================================================================
1646    // SystemTimeDiff constructors: from_secs / from_millis / from_nanos
1647    // ========================================================================
1648
1649    #[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        // u64::MAX millis must not overflow the u32 nanos field.
1671        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            // Lossless round-trip: secs * 1e9 + nanos == n (checked in u128).
1691            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    // ========================================================================
1698    // Round-trip: from_millis <-> millis
1699    // ========================================================================
1700
1701    #[test]
1702    fn millis_round_trips_through_from_millis() {
1703        // Exact for every whole-millisecond value, INCLUDING u64::MAX (where
1704        // `secs * 1000 + 615` lands exactly on u64::MAX without saturating).
1705        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        // Sub-millisecond nanos truncate towards zero.
1717        assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999 }.millis(), 0);
1718        assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999_999 }.millis(), 999);
1719        // secs * 1000 overflows u64 -> saturate at u64::MAX, no panic.
1720        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    // ========================================================================
1729    // SystemTimeDiff::checked_add
1730    // ========================================================================
1731
1732    #[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        // Exactly one second of nanos carries cleanly.
1738        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        // secs overflow
1749        assert_eq!(max_secs.checked_add(SystemTimeDiff::from_secs(1)), None);
1750        // secs at max, nanos still fit -> Some
1751        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        // overflow that only happens because of the nanos CARRY
1756        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            // a + b == b + a for well-formed operands
1772            let other = SystemTimeDiff::from_millis(750);
1773            assert_eq!(d.checked_add(other), other.checked_add(d));
1774        }
1775    }
1776
1777    // ========================================================================
1778    // SystemTimeDiff::get  (std::time::Duration conversion round-trip)
1779    // ========================================================================
1780
1781    #[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        // secs at max with zero nanos: no carry, so no overflow in Duration::new.
1800        assert_eq!(
1801            SystemTimeDiff::from_secs(u64::MAX).get(),
1802            StdDuration::new(u64::MAX, 0)
1803        );
1804        // Denormalized nanos (>= 1e9) are carried by Duration::new, not rejected.
1805        assert_eq!(
1806            SystemTimeDiff { secs: 0, nanos: u32::MAX }.get(),
1807            StdDuration::new(0, u32::MAX)
1808        );
1809    }
1810
1811    // ========================================================================
1812    // ThreadReceiver: new / get_ctx / recv / clone
1813    // ========================================================================
1814
1815    #[cfg(feature = "std")]
1816    extern "C" fn test_thread_recv(ptr: *const c_void) -> OptionThreadSendMsg {
1817        // Mirrors the real callback: `ThreadReceiver::recv` hands over a pointer
1818        // to the boxed `Receiver<ThreadSendMsg>` inside `ThreadReceiverInner`.
1819        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        // Empty channel -> None (must not block / panic).
1852        assert!(r.recv().is_none());
1853        // Disconnected channel -> still None, not a panic.
1854        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        // Drained.
1877        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        // The clone shares the Arc<Mutex<..>>: whichever half receives first
1889        // consumes the message; the other must see an empty channel, not a
1890        // duplicate and not a deadlock.
1891        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        // Repeated reads must all succeed -- `get_ctx` clones the RefAny (refcount
1908        // bump); a take/move would leave the second call empty.
1909        assert!(r.get_ctx().is_some());
1910        assert!(r.get_ctx().is_some());
1911        let held = r.get_ctx();
1912        drop(r);
1913        // The cloned handle outlives the receiver it came from.
1914        assert!(held.is_some());
1915    }
1916}