#[cfg(not(feature = "std"))]
use alloc::string::{String, ToString};
use alloc::{
boxed::Box,
collections::btree_map::BTreeMap,
sync::{Arc, Weak},
vec::Vec,
};
use core::{
ffi::c_void,
fmt,
mem::ManuallyDrop,
sync::atomic::{AtomicUsize, Ordering},
};
#[cfg(feature = "std")]
use std::sync::mpsc::{Receiver, Sender};
#[cfg(feature = "std")]
use std::sync::Mutex;
#[cfg(feature = "std")]
use std::thread::{self, JoinHandle};
#[cfg(feature = "std")]
use std::time::Duration as StdDuration;
#[cfg(feature = "std")]
use std::time::Instant as StdInstant;
use azul_css::{props::property::CssProperty, AzString};
use rust_fontconfig::FcFontCache;
use crate::{
callbacks::{FocusTarget, TimerCallbackReturn, Update},
dom::{DomId, DomNodeId, OptionDomNodeId},
geom::{LogicalPosition, OptionLogicalPosition},
gl::OptionGlContextPtr,
hit_test::ScrollPosition,
id::NodeId,
refany::{OptionRefAny, RefAny},
resources::{ImageCache, ImageMask, ImageRef},
styled_dom::NodeHierarchyItemId,
window::RawWindowHandle,
FastBTreeSet, OrderedMap,
};
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TerminateTimer {
Terminate,
Continue,
}
pub const CURSOR_BLINK_TIMER_ID: TimerId = TimerId { id: 0x0001 };
pub const SCROLL_MOMENTUM_TIMER_ID: TimerId = TimerId { id: 0x0002 };
pub const DRAG_AUTOSCROLL_TIMER_ID: TimerId = TimerId { id: 0x0003 };
pub const TOOLTIP_DELAY_TIMER_ID: TimerId = TimerId { id: 0x0004 };
pub const CAPABILITY_PUMP_TIMER_ID: TimerId = TimerId { id: 0x0005 };
pub const LONG_PRESS_TIMER_ID: TimerId = TimerId { id: 0x0006 };
pub const USER_TIMER_ID_START: usize = 0x0100;
static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct TimerId {
pub id: usize,
}
impl TimerId {
#[must_use]
pub fn unique() -> Self {
Self {
id: MAX_TIMER_ID.fetch_add(1, Ordering::SeqCst),
}
}
}
impl_option!(
TimerId,
OptionTimerId,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(TimerId, TimerIdVec, TimerIdVecDestructor, TimerIdVecDestructorType, TimerIdVecSlice, OptionTimerId);
impl_vec_debug!(TimerId, TimerIdVec);
impl_vec_clone!(TimerId, TimerIdVec, TimerIdVecDestructor);
impl_vec_partialeq!(TimerId, TimerIdVec);
impl_vec_partialord!(TimerId, TimerIdVec);
const RESERVED_THREAD_ID_COUNT: usize = 5;
static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct ThreadId {
id: usize,
}
impl_option!(
ThreadId,
OptionThreadId,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_vec!(ThreadId, ThreadIdVec, ThreadIdVecDestructor, ThreadIdVecDestructorType, ThreadIdVecSlice, OptionThreadId);
impl_vec_debug!(ThreadId, ThreadIdVec);
impl_vec_clone!(ThreadId, ThreadIdVec, ThreadIdVecDestructor);
impl_vec_partialeq!(ThreadId, ThreadIdVec);
impl_vec_partialord!(ThreadId, ThreadIdVec);
impl ThreadId {
#[must_use]
pub fn unique() -> Self {
Self {
id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum Instant {
System(InstantPtr),
Tick(SystemTick),
}
#[cfg(feature = "std")]
impl From<StdInstant> for Instant {
fn from(s: StdInstant) -> Self {
Self::System(s.into())
}
}
#[cfg(feature = "std")]
pub static TEST_CLOCK_OFFSET_MS: core::sync::atomic::AtomicU64 =
core::sync::atomic::AtomicU64::new(0);
#[cfg(feature = "std")]
pub fn advance_test_clock_ms(ms: u64) -> u64 {
TEST_CLOCK_OFFSET_MS.fetch_add(ms, core::sync::atomic::Ordering::SeqCst) + ms
}
#[cfg(feature = "std")]
#[must_use]
pub fn test_clock_offset_ms() -> u64 {
TEST_CLOCK_OFFSET_MS.load(core::sync::atomic::Ordering::SeqCst)
}
#[cfg(feature = "std")]
fn std_now_with_test_offset() -> StdInstant {
let offset = test_clock_offset_ms();
if offset == 0 {
StdInstant::now()
} else {
StdInstant::now() + core::time::Duration::from_millis(offset)
}
}
impl Instant {
#[cfg(feature = "std")]
#[must_use] pub fn now() -> Self {
std_now_with_test_offset().into()
}
#[cfg(not(feature = "std"))]
pub fn now() -> Self {
Instant::Tick(SystemTick::new(0))
}
#[must_use] pub fn linear_interpolate(&self, mut start: Self, mut end: Self) -> f32 {
use core::mem;
if end < start {
mem::swap(&mut start, &mut end);
}
if *self < start {
return 0.0;
}
if *self > end {
return 1.0;
}
if start == end {
return 1.0;
}
let duration_total = end.duration_since(&start);
let duration_current = self.duration_since(&start);
let ratio = duration_current.div(&duration_total);
if ratio.is_nan() {
return 1.0;
}
ratio.clamp(0.0, 1.0)
}
#[must_use] pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
duration.map_or_else(|| self.clone(), |d| match (self, d) {
(Self::System(i), Duration::System(d)) => {
#[cfg(feature = "std")]
{
let s: StdInstant = i.clone().into();
let d: StdDuration = (*d).into();
let new: InstantPtr = (s + d).into();
Self::System(new)
}
#[cfg(not(feature = "std"))]
{
let _ = (i, d);
self.clone()
}
}
(Self::Tick(s), Duration::Tick(d)) => Self::Tick(SystemTick {
tick_counter: s.tick_counter.saturating_add(d.tick_diff),
}),
_ => self.clone(),
})
}
#[cfg(feature = "std")]
#[must_use] pub fn into_std_instant(self) -> StdInstant {
match self {
Self::System(s) => s.into(),
Self::Tick(_) => unreachable!(),
}
}
#[must_use] pub fn duration_since(&self, earlier: &Self) -> Duration {
match (earlier, self) {
(Self::System(prev), Self::System(now)) => {
#[cfg(feature = "std")]
{
let prev_instant: StdInstant = prev.clone().into();
let now_instant: StdInstant = now.clone().into();
Duration::System(now_instant.saturating_duration_since(prev_instant).into())
}
#[cfg(not(feature = "std"))]
{
let _ = (prev, now);
Duration::Tick(SystemTickDiff { tick_diff: 0 })
}
}
(
Self::Tick(SystemTick { tick_counter: prev }),
Self::Tick(SystemTick { tick_counter: now }),
) => Duration::Tick(SystemTickDiff {
tick_diff: now.saturating_sub(*prev),
}),
_ => Duration::Tick(SystemTickDiff { tick_diff: 0 }),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct SystemTick {
pub tick_counter: u64,
}
impl SystemTick {
#[must_use] pub const fn new(tick_counter: u64) -> Self {
Self { tick_counter }
}
}
#[repr(C)]
pub struct InstantPtr {
#[cfg(feature = "std")]
pub ptr: ManuallyDrop<Box<StdInstant>>,
#[cfg(not(feature = "std"))]
pub ptr: *const c_void,
pub clone_fn: InstantPtrCloneCallback,
pub destructor: InstantPtrDestructorCallback,
pub run_destructor: bool,
}
pub type InstantPtrCloneCallbackType = extern "C" fn(*const InstantPtr) -> InstantPtr;
#[repr(C)]
pub struct InstantPtrCloneCallback {
pub cb: InstantPtrCloneCallbackType,
}
impl_callback_simple!(InstantPtrCloneCallback);
pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
#[repr(C)]
pub struct InstantPtrDestructorCallback {
pub cb: InstantPtrDestructorCallbackType,
}
impl_callback_simple!(InstantPtrDestructorCallback);
#[cfg(feature = "std")]
impl fmt::Debug for InstantPtr {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
write!(f, "{:?}", self.get())
}
}
#[cfg(not(feature = "std"))]
impl core::fmt::Debug for InstantPtr {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "{:?}", self.ptr as usize)
}
}
#[cfg(feature = "std")]
impl core::hash::Hash for InstantPtr {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.get().hash(state);
}
}
#[cfg(not(feature = "std"))]
impl core::hash::Hash for InstantPtr {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(self.ptr as usize).hash(state);
}
}
#[cfg(feature = "std")]
impl PartialEq for InstantPtr {
fn eq(&self, other: &Self) -> bool {
self.get() == other.get()
}
}
#[cfg(not(feature = "std"))]
impl PartialEq for InstantPtr {
fn eq(&self, other: &InstantPtr) -> bool {
(self.ptr as usize).eq(&(other.ptr as usize))
}
}
impl Eq for InstantPtr {}
#[cfg(feature = "std")]
impl PartialOrd for InstantPtr {
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
Some((self.get()).cmp(&(other.get())))
}
}
#[cfg(not(feature = "std"))]
impl PartialOrd for InstantPtr {
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
Some((self.ptr as usize).cmp(&(other.ptr as usize)))
}
}
#[cfg(feature = "std")]
impl Ord for InstantPtr {
fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
(self.get()).cmp(&(other.get()))
}
}
#[cfg(not(feature = "std"))]
impl Ord for InstantPtr {
fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
(self.ptr as usize).cmp(&(other.ptr as usize))
}
}
#[cfg(feature = "std")]
impl InstantPtr {
fn get(&self) -> StdInstant {
(**self.ptr)
}
}
impl Clone for InstantPtr {
fn clone(&self) -> Self {
(self.clone_fn.cb)(self)
}
}
#[cfg(feature = "std")]
extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
let az_instant_ptr = unsafe { &*ptr };
InstantPtr {
ptr: ManuallyDrop::new((*az_instant_ptr.ptr).clone()),
clone_fn: az_instant_ptr.clone_fn,
destructor: az_instant_ptr.destructor,
run_destructor: true,
}
}
#[cfg(feature = "std")]
impl From<StdInstant> for InstantPtr {
fn from(s: StdInstant) -> Self {
Self {
ptr: ManuallyDrop::new(Box::new(s)),
clone_fn: InstantPtrCloneCallback {
cb: std_instant_clone,
},
destructor: InstantPtrDestructorCallback {
cb: std_instant_drop,
},
run_destructor: true,
}
}
}
#[cfg(feature = "std")]
impl From<InstantPtr> for StdInstant {
fn from(s: InstantPtr) -> Self {
s.get()
}
}
impl Drop for InstantPtr {
fn drop(&mut self) {
if self.run_destructor {
self.run_destructor = false;
(self.destructor.cb)(self);
#[cfg(feature = "std")]
unsafe {
ManuallyDrop::drop(&mut self.ptr);
}
}
}
}
#[cfg(feature = "std")]
const extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum Duration {
System(SystemTimeDiff),
Tick(SystemTickDiff),
}
impl fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
#[cfg(feature = "std")]
Self::System(s) => {
let s: StdDuration = (*s).into();
write!(f, "{s:?}")
}
#[cfg(not(feature = "std"))]
Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
Self::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
}
}
}
#[cfg(feature = "std")]
impl From<StdDuration> for Duration {
fn from(s: StdDuration) -> Self {
Self::System(s.into())
}
}
impl Duration {
#[must_use] pub fn max() -> Self {
#[cfg(feature = "std")]
{
Self::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
}
#[cfg(not(feature = "std"))]
{
Duration::Tick(SystemTickDiff {
tick_diff: u64::MAX,
})
}
}
#[allow(clippy::cast_possible_truncation)]
#[must_use] pub fn div(&self, other: &Self) -> f32 {
use self::Duration::{System, Tick};
match (self, other) {
(System(s), System(s2)) => s.div(s2) as f32,
(Tick(t), Tick(t2)) => t.div(t2) as f32,
_ => 0.0,
}
}
#[must_use] pub fn min(self, other: Self) -> Self {
if self.smaller_than(&other) {
self
} else {
other
}
}
#[allow(unused_variables)]
#[must_use] pub fn greater_than(&self, other: &Self) -> bool {
match (self, other) {
(Self::System(s), Self::System(o)) => {
#[cfg(feature = "std")]
{
let s: StdDuration = (*s).into();
let o: StdDuration = (*o).into();
s > o
}
#[cfg(not(feature = "std"))]
{
false
}
}
(Self::Tick(s), Self::Tick(o)) => s.tick_diff > o.tick_diff,
_ => false,
}
}
#[allow(unused_variables)]
#[must_use] pub fn smaller_than(&self, other: &Self) -> bool {
match (self, other) {
(Self::System(s), Self::System(o)) => {
#[cfg(feature = "std")]
{
let s: StdDuration = (*s).into();
let o: StdDuration = (*o).into();
s < o
}
#[cfg(not(feature = "std"))]
{
false
}
}
(Self::Tick(s), Self::Tick(o)) => s.tick_diff < o.tick_diff,
_ => false,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct SystemTickDiff {
pub tick_diff: u64,
}
impl SystemTickDiff {
#[allow(clippy::cast_precision_loss)]
#[must_use] pub fn div(&self, other: &Self) -> f64 {
self.tick_diff as f64 / other.tick_diff as f64
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub struct SystemTimeDiff {
pub secs: u64,
pub nanos: u32,
}
impl SystemTimeDiff {
#[must_use] pub fn div(&self, other: &Self) -> f64 {
self.as_secs_f64() / other.as_secs_f64()
}
#[allow(clippy::cast_precision_loss)]
fn as_secs_f64(&self) -> f64 {
(self.secs as f64) + (f64::from(self.nanos) / f64::from(NANOS_PER_SEC))
}
}
#[cfg(feature = "std")]
impl From<StdDuration> for SystemTimeDiff {
fn from(d: StdDuration) -> Self {
Self {
secs: d.as_secs(),
nanos: d.subsec_nanos(),
}
}
}
#[cfg(feature = "std")]
impl From<SystemTimeDiff> for StdDuration {
fn from(d: SystemTimeDiff) -> Self {
Self::new(d.secs, d.nanos)
}
}
const MILLIS_PER_SEC: u64 = 1_000;
const NANOS_PER_MILLI: u32 = 1_000_000;
const NANOS_PER_SEC: u32 = 1_000_000_000;
impl SystemTimeDiff {
#[must_use] pub const fn from_secs(secs: u64) -> Self {
Self { secs, nanos: 0 }
}
#[must_use] pub const fn from_millis(millis: u64) -> Self {
Self {
secs: millis / MILLIS_PER_SEC,
nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
}
}
#[allow(clippy::cast_possible_truncation)]
#[must_use] pub const fn from_nanos(nanos: u64) -> Self {
Self {
secs: nanos / (NANOS_PER_SEC as u64),
nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
}
}
#[must_use] pub const fn checked_add(self, rhs: Self) -> Option<Self> {
if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
let mut nanos = self.nanos + rhs.nanos;
if nanos >= NANOS_PER_SEC {
nanos -= NANOS_PER_SEC;
if let Some(new_secs) = secs.checked_add(1) {
secs = new_secs;
} else {
return None;
}
}
Some(Self { secs, nanos })
} else {
None
}
}
#[must_use] pub const fn millis(&self) -> u64 {
self.secs
.saturating_mul(MILLIS_PER_SEC)
.saturating_add((self.nanos / NANOS_PER_MILLI) as u64)
}
#[cfg(feature = "std")]
#[must_use] pub fn get(&self) -> StdDuration {
(*self).into()
}
}
impl_option!(
Instant,
OptionInstant,
copy = false,
[Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
impl_option!(
Duration,
OptionDuration,
[Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
);
#[allow(variant_size_differences)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
#[repr(C, u8)]
pub enum ThreadSendMsg {
TerminateThread,
Tick,
Custom(RefAny),
}
impl_option!(
ThreadSendMsg,
OptionThreadSendMsg,
copy = false,
[Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
);
#[derive(Debug)]
#[repr(C)]
pub struct ThreadReceiver {
#[cfg(feature = "std")]
pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
#[cfg(not(feature = "std"))]
pub ptr: *const c_void,
pub run_destructor: bool,
pub ctx: OptionRefAny,
}
impl Clone for ThreadReceiver {
fn clone(&self) -> Self {
Self {
ptr: self.ptr.clone(),
run_destructor: true,
ctx: self.ctx.clone(),
}
}
}
impl Drop for ThreadReceiver {
fn drop(&mut self) {
self.run_destructor = false;
}
}
impl ThreadReceiver {
#[cfg(not(feature = "std"))]
pub fn new(_t: ThreadReceiverInner) -> Self {
Self {
ptr: core::ptr::null(),
run_destructor: false,
ctx: OptionRefAny::None,
}
}
#[cfg(feature = "std")]
#[must_use] pub fn new(t: ThreadReceiverInner) -> Self {
Self {
ptr: Box::new(Arc::new(Mutex::new(t))),
run_destructor: true,
ctx: OptionRefAny::None,
}
}
#[must_use] pub fn get_ctx(&self) -> OptionRefAny {
self.ctx.clone()
}
#[cfg(not(feature = "std"))]
pub fn recv(&mut self) -> OptionThreadSendMsg {
None.into()
}
#[cfg(feature = "std")]
pub fn recv(&mut self) -> OptionThreadSendMsg {
let Some(ts) = self.ptr.lock().ok() else {
return None.into();
};
(ts.recv_fn.cb)(std::ptr::from_ref(ts.ptr.as_ref()) as *const c_void)
}
}
#[derive(Debug)]
#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
#[repr(C)]
pub struct ThreadReceiverInner {
#[cfg(feature = "std")]
pub ptr: Box<Receiver<ThreadSendMsg>>,
#[cfg(not(feature = "std"))]
pub ptr: *const c_void,
pub recv_fn: ThreadRecvCallback,
pub destructor: ThreadReceiverDestructorCallback,
}
#[cfg(not(feature = "std"))]
unsafe impl Send for ThreadReceiverInner {}
#[cfg(feature = "std")]
impl core::hash::Hash for ThreadReceiverInner {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
(std::ptr::from_ref(self.ptr.as_ref()) as usize).hash(state);
}
}
#[cfg(feature = "std")]
impl PartialEq for ThreadReceiverInner {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.ptr.as_ref(), other.ptr.as_ref())
}
}
#[cfg(feature = "std")]
impl Eq for ThreadReceiverInner {}
#[cfg(feature = "std")]
impl PartialOrd for ThreadReceiverInner {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(
(std::ptr::from_ref(self.ptr.as_ref()) as usize)
.cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize)),
)
}
}
#[cfg(feature = "std")]
impl Ord for ThreadReceiverInner {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
(std::ptr::from_ref(self.ptr.as_ref()) as usize).cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize))
}
}
impl Drop for ThreadReceiverInner {
fn drop(&mut self) {
(self.destructor.cb)(self);
}
}
pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
#[repr(C)]
pub struct GetSystemTimeCallback {
pub cb: GetSystemTimeCallbackType,
}
impl_callback_simple!(GetSystemTimeCallback);
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
#[must_use] pub extern "C" fn get_system_time_libstd() -> Instant {
std_now_with_test_offset().into()
}
#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
pub extern "C" fn get_system_time_libstd() -> Instant {
Instant::Tick(SystemTick::new(0))
}
pub type CheckThreadFinishedCallbackType =
extern "C" fn( *const c_void) -> bool;
#[repr(C)]
pub struct CheckThreadFinishedCallback {
pub cb: CheckThreadFinishedCallbackType,
}
impl_callback_simple!(CheckThreadFinishedCallback);
pub type LibrarySendThreadMsgCallbackType =
extern "C" fn( *const c_void, ThreadSendMsg) -> bool;
#[repr(C)]
pub struct LibrarySendThreadMsgCallback {
pub cb: LibrarySendThreadMsgCallbackType,
}
impl_callback_simple!(LibrarySendThreadMsgCallback);
pub type ThreadRecvCallbackType =
extern "C" fn( *const c_void) -> OptionThreadSendMsg;
#[repr(C)]
pub struct ThreadRecvCallback {
pub cb: ThreadRecvCallbackType,
}
impl_callback_simple!(ThreadRecvCallback);
pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
#[repr(C)]
pub struct ThreadReceiverDestructorCallback {
pub cb: ThreadReceiverDestructorCallbackType,
}
impl_callback_simple!(ThreadReceiverDestructorCallback);
#[cfg(test)]
#[allow(clippy::float_cmp)] mod tests {
use super::*;
fn tick(n: u64) -> Instant {
Instant::Tick(SystemTick::new(n))
}
fn tick_dur(n: u64) -> Duration {
Duration::Tick(SystemTickDiff { tick_diff: n })
}
fn sys_dur(secs: u64, nanos: u32) -> Duration {
Duration::System(SystemTimeDiff { secs, nanos })
}
#[test]
fn linear_interpolate_zero_interval_is_one_not_nan() {
let t = tick(5);
let v = t.linear_interpolate(tick(5), tick(5));
assert!(v.is_finite());
assert_eq!(v, 1.0);
}
#[test]
fn linear_interpolate_midpoint() {
let v = tick(5).linear_interpolate(tick(0), tick(10));
assert!((v - 0.5).abs() < 1e-6);
}
#[test]
fn duration_since_saturates_on_negative() {
let d = tick(1).duration_since(&tick(10));
assert_eq!(d, tick_dur(0));
}
#[test]
fn duration_compare_mismatched_kinds_saturates() {
let a = tick_dur(5);
let b = sys_dur(1, 0);
assert!(!a.greater_than(&b));
assert!(!a.smaller_than(&b));
assert!(!b.greater_than(&a));
assert!(!b.smaller_than(&a));
}
#[test]
fn add_optional_duration_mismatched_is_noop() {
let inst = tick(100);
let out = inst.add_optional_duration(Some(&sys_dur(1, 0)));
assert_eq!(out, tick(100));
let out2 = inst.add_optional_duration(Some(&tick_dur(5)));
assert_eq!(out2, tick(105));
let big = tick(u64::MAX);
let out3 = big.add_optional_duration(Some(&tick_dur(10)));
assert_eq!(out3, tick(u64::MAX));
}
#[test]
fn millis_saturates_on_overflow() {
let huge = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
assert_eq!(huge.millis(), u64::MAX);
let normal = SystemTimeDiff { secs: 2, nanos: 500_000_000 };
assert_eq!(normal.millis(), 2500);
}
#[test]
fn duration_div_mismatched_kinds_is_zero() {
assert_eq!(tick_dur(10).div(&sys_dur(1, 0)), 0.0);
assert!((tick_dur(5).div(&tick_dur(10)) - 0.5).abs() < 1e-6);
}
#[cfg(feature = "std")]
#[test]
fn instant_ptr_clone_and_drop_no_ub() {
let base = StdInstant::now();
let a: InstantPtr = base.into();
let b = a.clone();
assert_eq!(a, b);
drop(a);
drop(b);
}
}
#[cfg(test)]
#[allow(clippy::float_cmp)] mod autotest_generated {
use super::*;
fn tick(n: u64) -> Instant {
Instant::Tick(SystemTick::new(n))
}
fn tick_dur(n: u64) -> Duration {
Duration::Tick(SystemTickDiff { tick_diff: n })
}
fn sys_dur(secs: u64, nanos: u32) -> Duration {
Duration::System(SystemTimeDiff { secs, nanos })
}
#[test]
fn timer_id_unique_is_strictly_increasing_and_above_reserved_range() {
let a = TimerId::unique();
let b = TimerId::unique();
assert_ne!(a, b);
assert!(b.id > a.id, "unique() must strictly increase: {a:?} -> {b:?}");
for id in [a, b] {
assert!(
id.id >= USER_TIMER_ID_START,
"unique() handed out a reserved system ID: {id:?}"
);
assert_ne!(id, CURSOR_BLINK_TIMER_ID);
assert_ne!(id, SCROLL_MOMENTUM_TIMER_ID);
assert_ne!(id, DRAG_AUTOSCROLL_TIMER_ID);
assert_ne!(id, TOOLTIP_DELAY_TIMER_ID);
assert_ne!(id, CAPABILITY_PUMP_TIMER_ID);
assert_ne!(id, LONG_PRESS_TIMER_ID);
}
}
#[test]
fn thread_id_unique_is_strictly_increasing_and_above_reserved_range() {
let a = ThreadId::unique();
let b = ThreadId::unique();
assert_ne!(a, b);
assert!(b.id > a.id);
assert!(a.id >= RESERVED_THREAD_ID_COUNT);
}
#[cfg(feature = "std")]
#[test]
fn unique_ids_do_not_collide_across_threads() {
use alloc::collections::BTreeSet;
let handles: Vec<_> = (0..8)
.map(|_| {
std::thread::spawn(|| {
let mut out = Vec::new();
for _ in 0..64 {
out.push((TimerId::unique().id, ThreadId::unique().id));
}
out
})
})
.collect();
let mut timer_ids = BTreeSet::new();
let mut thread_ids = BTreeSet::new();
for h in handles {
for (t, th) in h.join().expect("worker thread panicked") {
assert!(timer_ids.insert(t), "duplicate TimerId handed out: {t}");
assert!(thread_ids.insert(th), "duplicate ThreadId handed out: {th}");
}
}
assert_eq!(timer_ids.len(), 8 * 64);
assert_eq!(thread_ids.len(), 8 * 64);
}
#[cfg(feature = "std")]
#[test]
fn instant_now_is_system_and_monotonic() {
let a = Instant::now();
let b = Instant::now();
assert!(matches!(a, Instant::System(_)));
assert!(a <= b, "Instant::now() went backwards");
assert_eq!(a.duration_since(&b), sys_dur(0, 0));
}
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
#[test]
fn get_system_time_libstd_is_monotonic_system_instant() {
let a = get_system_time_libstd();
let b = get_system_time_libstd();
assert!(matches!(a, Instant::System(_)));
assert!(matches!(b, Instant::System(_)));
assert!(a <= b);
}
#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
#[test]
fn get_system_time_libstd_wasm_fallback_is_zero_tick() {
assert_eq!(get_system_time_libstd(), tick(0));
}
#[test]
fn linear_interpolate_clamps_outside_the_interval() {
assert_eq!(tick(0).linear_interpolate(tick(10), tick(20)), 0.0);
assert_eq!(tick(999).linear_interpolate(tick(10), tick(20)), 1.0);
assert_eq!(tick(10).linear_interpolate(tick(10), tick(20)), 0.0);
assert_eq!(tick(20).linear_interpolate(tick(10), tick(20)), 1.0);
}
#[test]
fn linear_interpolate_reversed_interval_is_normalized() {
let forwards = tick(5).linear_interpolate(tick(0), tick(10));
let backwards = tick(5).linear_interpolate(tick(10), tick(0));
assert_eq!(forwards, backwards);
assert!((backwards - 0.5).abs() < 1e-6);
}
#[test]
fn linear_interpolate_saturating_extremes_stay_in_range() {
let v = tick(u64::MAX / 2).linear_interpolate(tick(0), tick(u64::MAX));
assert!(v.is_finite(), "interpolation over the full u64 span went non-finite");
assert!((0.0..=1.0).contains(&v));
assert!((v - 0.5).abs() < 1e-3, "expected ~0.5, got {v}");
let z = tick(u64::MAX).linear_interpolate(tick(u64::MAX), tick(u64::MAX));
assert_eq!(z, 1.0);
let z0 = tick(0).linear_interpolate(tick(0), tick(0));
assert_eq!(z0, 1.0);
}
#[cfg(feature = "std")]
#[test]
fn linear_interpolate_mismatched_kinds_never_nan() {
let sys = Instant::now();
let cases = [
(tick(5), sys.clone(), tick(10)),
(sys.clone(), tick(0), tick(10)),
(tick(5), tick(0), sys.clone()),
(sys.clone(), sys.clone(), tick(10)),
(tick(5), sys.clone(), sys.clone()),
];
for (this, start, end) in cases {
let v = this.linear_interpolate(start, end);
assert!(v.is_finite(), "mismatched-kind interpolation returned {v}");
assert!(
(0.0..=1.0).contains(&v),
"mismatched-kind interpolation escaped [0,1]: {v}"
);
}
}
#[test]
fn add_optional_duration_none_is_identity() {
let t = tick(42);
assert_eq!(t.add_optional_duration(None), t);
assert_eq!(tick(u64::MAX).add_optional_duration(None), tick(u64::MAX));
}
#[test]
fn add_optional_duration_tick_saturates_at_u64_max() {
let near_max = tick(u64::MAX - 1);
assert_eq!(
near_max.add_optional_duration(Some(&tick_dur(u64::MAX))),
tick(u64::MAX)
);
assert_eq!(tick(0).add_optional_duration(Some(&tick_dur(0))), tick(0));
}
#[cfg(feature = "std")]
#[test]
fn add_optional_duration_system_advances_by_the_duration() {
let base = Instant::now();
let later = base.add_optional_duration(Some(&Duration::System(SystemTimeDiff::from_secs(1))));
assert!(later > base);
let delta = later.duration_since(&base);
assert_eq!(delta, sys_dur(1, 0));
assert_eq!(base.duration_since(&later), sys_dur(0, 0));
}
#[cfg(feature = "std")]
#[test]
fn add_optional_duration_mismatched_kinds_saturate_both_ways() {
let sys = Instant::now();
assert_eq!(sys.add_optional_duration(Some(&tick_dur(500))), sys);
let t = tick(7);
assert_eq!(t.add_optional_duration(Some(&sys_dur(3, 0))), t);
}
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
#[test]
#[should_panic(expected = "overflow")]
fn add_optional_duration_system_overflow_panics_today() {
let base = Instant::now();
let _ = base.add_optional_duration(Some(&Duration::max()));
}
#[test]
fn duration_since_tick_saturates_and_is_exact() {
assert_eq!(tick(10).duration_since(&tick(4)), tick_dur(6));
assert_eq!(tick(10).duration_since(&tick(10)), tick_dur(0));
assert_eq!(tick(0).duration_since(&tick(u64::MAX)), tick_dur(0));
assert_eq!(tick(u64::MAX).duration_since(&tick(0)), tick_dur(u64::MAX));
}
#[cfg(feature = "std")]
#[test]
fn duration_since_mismatched_kinds_is_zero_tick_both_directions() {
let sys = Instant::now();
assert_eq!(sys.duration_since(&tick(5)), tick_dur(0));
assert_eq!(tick(5).duration_since(&sys), tick_dur(0));
}
#[cfg(feature = "std")]
#[test]
fn into_std_instant_round_trips_a_system_instant() {
let base = StdInstant::now();
let wrapped: Instant = base.into();
assert_eq!(wrapped.into_std_instant(), base);
}
#[cfg(feature = "std")]
#[test]
#[should_panic(expected = "internal error: entered unreachable code")]
fn into_std_instant_on_tick_variant_panics() {
let _ = tick(1).into_std_instant();
}
#[test]
fn system_tick_new_stores_the_counter_verbatim() {
for n in [0_u64, 1, 0x0100, u64::MAX / 2, u64::MAX] {
assert_eq!(SystemTick::new(n).tick_counter, n);
}
assert!(SystemTick::new(0) < SystemTick::new(u64::MAX));
assert_eq!(SystemTick::new(7), SystemTick::new(7));
}
#[cfg(feature = "std")]
#[test]
fn instant_ptr_get_returns_the_wrapped_instant() {
let base = StdInstant::now();
let p: InstantPtr = base.into();
assert_eq!(p.get(), base);
assert_eq!(p.get(), p.get());
assert!(p.run_destructor);
assert!(!alloc::format!("{p:?}").is_empty());
}
#[cfg(feature = "std")]
#[test]
fn std_instant_clone_deep_copies_and_arms_the_destructor() {
let base = StdInstant::now();
let a: InstantPtr = base.into();
let cloned = std_instant_clone(core::ptr::from_ref(&a));
assert_eq!(cloned.get(), base);
assert!(!core::ptr::eq(&**a.ptr, &**cloned.ptr));
assert!(cloned.run_destructor, "clone handed back a disarmed destructor");
drop(cloned);
assert_eq!(a.get(), base);
}
#[cfg(feature = "std")]
#[test]
fn std_instant_drop_is_a_noop_even_for_null() {
std_instant_drop(core::ptr::null_mut());
let mut p: InstantPtr = StdInstant::now().into();
let before = p.get();
std_instant_drop(core::ptr::from_mut(&mut p));
assert_eq!(p.get(), before);
assert!(p.run_destructor);
}
#[test]
fn duration_display_tick_edge_values() {
assert_eq!(alloc::format!("{}", tick_dur(0)), "0 ticks");
assert_eq!(alloc::format!("{}", tick_dur(1)), "1 ticks");
assert_eq!(
alloc::format!("{}", tick_dur(u64::MAX)),
"18446744073709551615 ticks"
);
}
#[cfg(feature = "std")]
#[test]
fn duration_display_system_edge_values_do_not_panic() {
for d in [
sys_dur(0, 0),
sys_dur(1, 500_000_000),
sys_dur(0, u32::MAX),
sys_dur(u64::MAX, NANOS_PER_SEC - 1),
Duration::max(),
] {
let s = alloc::format!("{d}");
assert!(!s.is_empty());
assert!(!s.ends_with("ticks"), "System duration formatted as ticks: {s}");
}
}
#[cfg(feature = "std")]
#[test]
fn duration_max_is_the_upper_bound() {
let m = Duration::max();
assert_eq!(m, sys_dur(u64::MAX, NANOS_PER_SEC - 1));
assert!(m.greater_than(&sys_dur(u64::MAX, NANOS_PER_SEC - 2)));
assert!(m.greater_than(&sys_dur(0, 0)));
assert!(!m.greater_than(&m));
assert!(!m.smaller_than(&m));
let Duration::System(inner) = m else {
panic!("Duration::max() is not a System duration under std")
};
assert_eq!(inner.get(), StdDuration::new(u64::MAX, NANOS_PER_SEC - 1));
}
#[test]
fn duration_div_by_zero_yields_inf_or_nan_not_a_panic() {
assert!(tick_dur(0).div(&tick_dur(0)).is_nan());
let inf = tick_dur(5).div(&tick_dur(0));
assert!(inf.is_infinite() && inf.is_sign_positive());
assert!(sys_dur(0, 0).div(&sys_dur(0, 0)).is_nan());
let sinf = sys_dur(1, 0).div(&sys_dur(0, 0));
assert!(sinf.is_infinite() && sinf.is_sign_positive());
}
#[test]
fn duration_div_extremes_stay_finite_in_f32() {
let r = tick_dur(u64::MAX).div(&tick_dur(1));
assert!(r.is_finite(), "u64::MAX tick ratio overflowed f32: {r}");
assert!(r > 1e19);
assert_eq!(tick_dur(u64::MAX).div(&tick_dur(u64::MAX)), 1.0);
assert_eq!(sys_dur(3, 0).div(&sys_dur(2, 0)), 1.5);
}
#[test]
fn duration_div_mismatched_kinds_saturates_to_zero_both_ways() {
assert_eq!(sys_dur(1, 0).div(&tick_dur(10)), 0.0);
assert_eq!(tick_dur(10).div(&sys_dur(1, 0)), 0.0);
}
#[test]
fn duration_min_picks_the_smaller_of_the_same_kind() {
assert_eq!(tick_dur(5).min(tick_dur(10)), tick_dur(5));
assert_eq!(tick_dur(10).min(tick_dur(5)), tick_dur(5));
assert_eq!(tick_dur(7).min(tick_dur(7)), tick_dur(7));
assert_eq!(tick_dur(0).min(tick_dur(u64::MAX)), tick_dur(0));
#[cfg(feature = "std")]
assert_eq!(sys_dur(1, 0).min(sys_dur(1, 1)), sys_dur(1, 0));
}
#[test]
fn duration_min_across_kinds_falls_back_to_other() {
assert_eq!(tick_dur(5).min(sys_dur(1, 0)), sys_dur(1, 0));
assert_eq!(sys_dur(1, 0).min(tick_dur(5)), tick_dur(5));
}
#[test]
fn duration_comparison_is_a_strict_total_order_within_a_kind() {
let mut pairs = alloc::vec![(tick_dur(0), tick_dur(u64::MAX)), (tick_dur(1), tick_dur(2))];
#[cfg(feature = "std")]
pairs.extend_from_slice(&[
(sys_dur(0, 0), sys_dur(u64::MAX, 0)),
(sys_dur(1, 999_999_999), sys_dur(2, 0)),
]);
for (a, b) in pairs {
assert!(a.smaller_than(&b));
assert!(b.greater_than(&a));
assert!(!a.greater_than(&b));
assert!(!b.smaller_than(&a));
}
let eq = tick_dur(4);
assert!(!eq.greater_than(&eq));
assert!(!eq.smaller_than(&eq));
let eq_sys = sys_dur(4, 2);
assert!(!eq_sys.greater_than(&eq_sys));
assert!(!eq_sys.smaller_than(&eq_sys));
}
#[cfg(feature = "std")]
#[test]
fn duration_comparison_normalizes_denormalized_nanos() {
let denorm = sys_dur(0, u32::MAX);
assert!(denorm.greater_than(&sys_dur(4, 0)));
assert!(denorm.smaller_than(&sys_dur(5, 0)));
}
#[test]
fn system_tick_diff_div_edge_cases() {
let zero = SystemTickDiff { tick_diff: 0 };
let one = SystemTickDiff { tick_diff: 1 };
let max = SystemTickDiff { tick_diff: u64::MAX };
assert!(zero.div(&zero).is_nan());
assert!(one.div(&zero).is_infinite());
assert_eq!(zero.div(&one), 0.0);
assert_eq!(max.div(&max), 1.0);
assert!(max.div(&one).is_finite());
assert_eq!(SystemTickDiff { tick_diff: 5 }.div(&SystemTickDiff { tick_diff: 10 }), 0.5);
}
#[test]
fn system_time_diff_as_secs_f64_is_exact_for_representable_values() {
assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.as_secs_f64(), 0.0);
assert_eq!(SystemTimeDiff { secs: 1, nanos: 500_000_000 }.as_secs_f64(), 1.5);
assert_eq!(SystemTimeDiff { secs: 0, nanos: 500_000_000 }.as_secs_f64(), 0.5);
let huge = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
assert!(huge.as_secs_f64().is_finite());
assert!(huge.as_secs_f64() > 1e19);
assert!(
SystemTimeDiff::from_secs(2).as_secs_f64() > SystemTimeDiff::from_secs(1).as_secs_f64()
);
}
#[test]
fn system_time_diff_div_edge_cases() {
let zero = SystemTimeDiff { secs: 0, nanos: 0 };
let one = SystemTimeDiff::from_secs(1);
let half = SystemTimeDiff { secs: 0, nanos: 500_000_000 };
assert!(zero.div(&zero).is_nan());
assert!(one.div(&zero).is_infinite());
assert_eq!(zero.div(&one), 0.0);
assert_eq!(one.div(&one), 1.0);
assert_eq!(one.div(&half), 2.0);
let max = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
assert_eq!(max.div(&max), 1.0);
assert!(max.div(&one).is_finite());
}
#[test]
fn from_secs_invariants() {
for s in [0_u64, 1, 1_000, u64::MAX] {
let d = SystemTimeDiff::from_secs(s);
assert_eq!(d.secs, s);
assert_eq!(d.nanos, 0, "from_secs must leave nanos at zero");
}
}
#[test]
fn from_millis_normalizes_and_keeps_nanos_in_range() {
assert_eq!(SystemTimeDiff::from_millis(0), SystemTimeDiff { secs: 0, nanos: 0 });
assert_eq!(
SystemTimeDiff::from_millis(999),
SystemTimeDiff { secs: 0, nanos: 999_000_000 }
);
assert_eq!(SystemTimeDiff::from_millis(1_000), SystemTimeDiff { secs: 1, nanos: 0 });
assert_eq!(
SystemTimeDiff::from_millis(1_500),
SystemTimeDiff { secs: 1, nanos: 500_000_000 }
);
let max = SystemTimeDiff::from_millis(u64::MAX);
assert!(max.nanos < NANOS_PER_SEC, "from_millis produced denormalized nanos");
assert_eq!(max.secs, u64::MAX / MILLIS_PER_SEC);
}
#[test]
fn from_nanos_normalizes_and_keeps_nanos_in_range() {
assert_eq!(SystemTimeDiff::from_nanos(0), SystemTimeDiff { secs: 0, nanos: 0 });
assert_eq!(
SystemTimeDiff::from_nanos(999_999_999),
SystemTimeDiff { secs: 0, nanos: 999_999_999 }
);
assert_eq!(
SystemTimeDiff::from_nanos(1_000_000_000),
SystemTimeDiff { secs: 1, nanos: 0 }
);
for n in [0_u64, 1, 999_999_999, 1_000_000_001, u64::MAX] {
let d = SystemTimeDiff::from_nanos(n);
assert!(d.nanos < NANOS_PER_SEC, "from_nanos({n}) produced denormalized nanos");
let back =
u128::from(d.secs) * u128::from(NANOS_PER_SEC) + u128::from(d.nanos);
assert_eq!(back, u128::from(n), "from_nanos({n}) lost information");
}
}
#[test]
fn millis_round_trips_through_from_millis() {
for m in [0_u64, 1, 999, 1_000, 1_500, 86_400_000, u64::MAX] {
assert_eq!(
SystemTimeDiff::from_millis(m).millis(),
m,
"from_millis({m}).millis() is not lossless"
);
}
}
#[test]
fn millis_truncates_and_saturates_instead_of_panicking() {
assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999 }.millis(), 0);
assert_eq!(SystemTimeDiff { secs: 0, nanos: 999_999_999 }.millis(), 999);
assert_eq!(SystemTimeDiff { secs: u64::MAX, nanos: 0 }.millis(), u64::MAX);
assert_eq!(
SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 }.millis(),
u64::MAX
);
assert_eq!(SystemTimeDiff::from_secs(u64::MAX / 1_000).millis(), (u64::MAX / 1_000) * 1_000);
}
#[test]
fn checked_add_carries_nanos_into_secs() {
let a = SystemTimeDiff { secs: 0, nanos: 999_999_999 };
let sum = a.checked_add(a).expect("0.999s + 0.999s must not overflow");
assert_eq!(sum, SystemTimeDiff { secs: 1, nanos: 999_999_998 });
let b = SystemTimeDiff { secs: 1, nanos: 500_000_000 };
assert_eq!(
b.checked_add(b),
Some(SystemTimeDiff { secs: 3, nanos: 0 })
);
}
#[test]
fn checked_add_returns_none_on_overflow_instead_of_panicking() {
let max_secs = SystemTimeDiff { secs: u64::MAX, nanos: 0 };
assert_eq!(max_secs.checked_add(SystemTimeDiff::from_secs(1)), None);
assert_eq!(
max_secs.checked_add(SystemTimeDiff { secs: 0, nanos: NANOS_PER_SEC - 1 }),
Some(SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 })
);
let brim = SystemTimeDiff { secs: u64::MAX, nanos: NANOS_PER_SEC - 1 };
assert_eq!(brim.checked_add(SystemTimeDiff { secs: 0, nanos: 1 }), None);
}
#[test]
fn checked_add_identity_and_commutativity() {
let zero = SystemTimeDiff { secs: 0, nanos: 0 };
for d in [
SystemTimeDiff::from_secs(0),
SystemTimeDiff::from_millis(1_500),
SystemTimeDiff::from_nanos(u64::MAX),
SystemTimeDiff { secs: u64::MAX, nanos: 0 },
] {
assert_eq!(d.checked_add(zero), Some(d));
assert_eq!(zero.checked_add(d), Some(d));
let other = SystemTimeDiff::from_millis(750);
assert_eq!(d.checked_add(other), other.checked_add(d));
}
}
#[cfg(feature = "std")]
#[test]
fn system_time_diff_get_round_trips_std_duration() {
for std_d in [
StdDuration::ZERO,
StdDuration::from_millis(1_500),
StdDuration::from_nanos(1),
StdDuration::new(u64::MAX, NANOS_PER_SEC - 1),
] {
let mid: SystemTimeDiff = std_d.into();
assert_eq!(mid.get(), std_d, "StdDuration -> SystemTimeDiff -> StdDuration lost data");
}
}
#[cfg(feature = "std")]
#[test]
fn system_time_diff_get_on_edge_values_does_not_panic() {
assert_eq!(SystemTimeDiff { secs: 0, nanos: 0 }.get(), StdDuration::ZERO);
assert_eq!(
SystemTimeDiff::from_secs(u64::MAX).get(),
StdDuration::new(u64::MAX, 0)
);
assert_eq!(
SystemTimeDiff { secs: 0, nanos: u32::MAX }.get(),
StdDuration::new(0, u32::MAX)
);
}
#[cfg(feature = "std")]
extern "C" fn test_thread_recv(ptr: *const c_void) -> OptionThreadSendMsg {
let receiver = unsafe { &*(ptr.cast::<Receiver<ThreadSendMsg>>()) };
receiver.try_recv().ok().into()
}
#[cfg(feature = "std")]
const extern "C" fn test_thread_recv_destructor(_: *mut ThreadReceiverInner) {}
#[cfg(feature = "std")]
fn test_receiver() -> (Sender<ThreadSendMsg>, ThreadReceiver) {
let (tx, rx) = std::sync::mpsc::channel::<ThreadSendMsg>();
let inner = ThreadReceiverInner {
ptr: Box::new(rx),
recv_fn: ThreadRecvCallback { cb: test_thread_recv },
destructor: ThreadReceiverDestructorCallback {
cb: test_thread_recv_destructor,
},
};
(tx, ThreadReceiver::new(inner))
}
#[cfg(feature = "std")]
#[test]
fn thread_receiver_new_arms_destructor_and_has_no_ctx() {
let (_tx, r) = test_receiver();
assert!(r.run_destructor, "ThreadReceiver::new left the destructor disarmed");
assert!(r.get_ctx().is_none(), "a fresh receiver must have no FFI context");
}
#[cfg(feature = "std")]
#[test]
fn thread_receiver_recv_on_empty_and_disconnected_channel_is_none() {
let (tx, mut r) = test_receiver();
assert!(r.recv().is_none());
drop(tx);
assert!(r.recv().is_none());
assert!(r.recv().is_none());
}
#[cfg(feature = "std")]
#[test]
fn thread_receiver_recv_delivers_messages_in_order() {
let (tx, mut r) = test_receiver();
tx.send(ThreadSendMsg::Tick).unwrap();
tx.send(ThreadSendMsg::Custom(RefAny::new(42_u32))).unwrap();
tx.send(ThreadSendMsg::TerminateThread).unwrap();
assert_eq!(r.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
assert!(matches!(
r.recv(),
OptionThreadSendMsg::Some(ThreadSendMsg::Custom(_))
));
assert_eq!(
r.recv(),
OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
);
assert!(r.recv().is_none());
}
#[cfg(feature = "std")]
#[test]
fn thread_receiver_clone_shares_the_same_channel() {
let (tx, mut a) = test_receiver();
let mut b = a.clone();
assert!(b.run_destructor);
tx.send(ThreadSendMsg::Tick).unwrap();
assert_eq!(a.recv(), OptionThreadSendMsg::Some(ThreadSendMsg::Tick));
assert!(b.recv().is_none());
tx.send(ThreadSendMsg::TerminateThread).unwrap();
assert_eq!(
b.recv(),
OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
);
assert!(a.recv().is_none());
}
#[cfg(feature = "std")]
#[test]
fn thread_receiver_get_ctx_clones_rather_than_takes() {
let (_tx, mut r) = test_receiver();
r.ctx = OptionRefAny::Some(RefAny::new(7_u64));
assert!(r.get_ctx().is_some());
assert!(r.get_ctx().is_some());
let held = r.get_ctx();
drop(r);
assert!(held.is_some());
}
}