Lines
29.55 %
Functions
16.67 %
Branches
100 %
//! Timer and thread management for asynchronous operations.
//!
//! This module provides:
//! - `TimerId` / `ThreadId`: Unique identifiers for timers and background threads
//! - `Instant` / `Duration`: Cross-platform time types (works on no_std with tick counters)
//! - `ThreadReceiver`: Channel for receiving messages from the main thread
//! - Callback types for thread communication and system time queries
#[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,
sync::atomic::{AtomicUsize, Ordering},
#[cfg(feature = "std")]
use std::sync::mpsc::{Receiver, Sender};
use std::sync::Mutex;
use std::thread::{self, JoinHandle};
use std::time::Duration as StdDuration;
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,
/// Should a timer terminate or not - used to remove active timers
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C)]
pub enum TerminateTimer {
/// Remove the timer from the list of active timers
Terminate,
/// Do nothing and let the timers continue to run
Continue,
}
// ============================================================================
// Reserved System Timer IDs (0x0000 - 0x00FF)
// User timers start at 0x0100 to avoid conflicts with system timers.
// These constants define well-known timer IDs for internal framework use.
/// Timer ID for cursor blinking in contenteditable elements (~530ms interval)
pub const CURSOR_BLINK_TIMER_ID: TimerId = TimerId { id: 0x0001 };
/// Timer ID for scroll momentum/inertia animation
pub const SCROLL_MOMENTUM_TIMER_ID: TimerId = TimerId { id: 0x0002 };
/// Timer ID for auto-scroll during drag operations near edges
pub const DRAG_AUTOSCROLL_TIMER_ID: TimerId = TimerId { id: 0x0003 };
/// Timer ID for tooltip show delay.
///
/// Started by the platform event loop when the hover target changes to a node
/// that advertises a tooltip source (`aria-label` / `alt` / `title`); fires
/// once after `SystemStyle::input_metrics.hover_time_ms` (SPI_GETMOUSEHOVERTIME
/// on Windows, default 400ms) and emits a `ShowTooltip` `CallbackChange`. The
/// timer is torn down on hover loss, which also emits `HideTooltip`.
/// Double-click detection used to live on a neighbouring reserved ID but is
/// now handled entirely by `GestureManager::detect_double_click`, so no
/// equivalent `DOUBLE_CLICK_TIMER_ID` exists.
pub const TOOLTIP_DELAY_TIMER_ID: TimerId = TimerId { id: 0x0004 };
/// First available ID for user-defined timers
pub const USER_TIMER_ID_START: usize = 0x0100;
// User timers start at 0x0100 to avoid conflicts with reserved system timer IDs
static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
/// ID for uniquely identifying a timer
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TimerId {
pub id: usize,
impl TimerId {
/// Generates a new, unique `TimerId`.
#[must_use]
pub fn unique() -> Self {
TimerId {
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);
// Thread IDs 0-4 are reserved for internal framework use.
// User threads start at RESERVED_THREAD_ID_COUNT.
const RESERVED_THREAD_ID_COUNT: usize = 5;
static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
/// ID for uniquely identifying a background thread
pub struct ThreadId {
id: usize,
ThreadId,
OptionThreadId,
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 {
/// Generates a new, unique `ThreadId`.
ThreadId {
id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
/// A point in time, either from the system clock or a tick counter.
/// Use `Instant::System` on platforms with std, `Instant::Tick` on embedded/no_std.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(C, u8)]
pub enum Instant {
/// System time from std::time::Instant (requires "std" feature)
System(InstantPtr),
/// Tick-based time for embedded systems without a real-time clock
Tick(SystemTick),
impl From<StdInstant> for Instant {
fn from(s: StdInstant) -> Instant {
Instant::System(s.into())
impl Instant {
/// Returns the current system time.
/// On systems with std, this uses `std::time::Instant::now()`.
/// On no_std systems, this returns a zero tick.
pub fn now() -> Self {
StdInstant::now().into()
/// Returns the current system time (no_std fallback).
Instant::Tick(SystemTick::new(0))
/// Returns a number from 0.0 to 1.0 indicating the current
/// linear interpolation value between (start, end)
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;
let duration_total = end.duration_since(&start);
let duration_current = self.duration_since(&start);
duration_current.div(&duration_total).max(0.0).min(1.0)
/// Adds a duration to the instant, does nothing in undefined cases
/// (i.e. trying to add a Duration::Tick to an Instant::System)
pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
match duration {
Some(d) => match (self, d) {
(Instant::System(i), Duration::System(d)) => {
{
let s: StdInstant = i.clone().into();
let d: StdDuration = d.clone().into();
let new: InstantPtr = (s + d).into();
Instant::System(new)
unreachable!()
(Instant::Tick(s), Duration::Tick(d)) => Instant::Tick(SystemTick {
tick_counter: s.tick_counter + d.tick_diff,
}),
_ => {
panic!(
"invalid: trying to add a duration {:?} to an instant {:?}",
d, self
},
None => self.clone(),
/// Converts to std::time::Instant (panics if Tick variant).
pub fn into_std_instant(self) -> StdInstant {
match self {
Instant::System(s) => s.into(),
Instant::Tick(_) => unreachable!(),
/// Calculates the duration since an earlier point in time
/// - Panics if the earlier Instant was created after the current Instant
/// - Panics if the two enums do not have the same variant (tick / std)
pub fn duration_since(&self, earlier: &Instant) -> Duration {
match (earlier, self) {
(Instant::System(prev), Instant::System(now)) => {
let prev_instant: StdInstant = prev.clone().into();
let now_instant: StdInstant = now.clone().into();
Duration::System((now_instant.duration_since(prev_instant)).into())
unreachable!() // cannot construct a System instant on no_std
(
Instant::Tick(SystemTick { tick_counter: prev }),
Instant::Tick(SystemTick { tick_counter: now }),
) => {
if prev > now {
"illegal: subtraction 'Instant - Instant' would result in a negative \
duration"
)
} else {
Duration::Tick(SystemTickDiff {
tick_diff: now - prev,
})
_ => panic!(
"illegal: trying to calculate a Duration from a SystemTime and a Tick instant"
),
/// Tick-based timestamp for systems without a real-time clock.
/// Used on embedded systems where time is measured in frame ticks or cycles.
pub struct SystemTick {
pub tick_counter: u64,
impl SystemTick {
/// Creates a new tick timestamp from a counter value.
pub const fn new(tick_counter: u64) -> Self {
Self { tick_counter }
/// FFI-safe wrapper around std::time::Instant with custom clone/drop callbacks.
/// Allows crossing FFI boundaries while maintaining proper memory management.
pub struct InstantPtr {
pub ptr: Box<StdInstant>,
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;
pub struct InstantPtrCloneCallback {
pub cb: InstantPtrCloneCallbackType,
impl_callback_simple!(InstantPtrCloneCallback);
pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
pub struct InstantPtrDestructorCallback {
pub cb: InstantPtrDestructorCallbackType,
impl_callback_simple!(InstantPtrDestructorCallback);
// ---- LIBSTD implementation for InstantPtr BEGIN
impl core::fmt::Debug for InstantPtr {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
write!(f, "{:?}", self.get())
write!(f, "{:?}", self.ptr as usize)
impl core::hash::Hash for InstantPtr {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.get().hash(state);
(self.ptr as usize).hash(state);
impl PartialEq for InstantPtr {
fn eq(&self, other: &InstantPtr) -> bool {
self.get() == other.get()
(self.ptr as usize).eq(&(other.ptr as usize))
impl Eq for InstantPtr {}
impl PartialOrd for InstantPtr {
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
Some((self.get()).cmp(&(other.get())))
Some((self.ptr as usize).cmp(&(other.ptr as usize)))
impl Ord for InstantPtr {
fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
(self.get()).cmp(&(other.get()))
(self.ptr as usize).cmp(&(other.ptr as usize))
impl InstantPtr {
fn get(&self) -> StdInstant {
*(self.ptr).clone()
impl Clone for InstantPtr {
fn clone(&self) -> Self {
(self.clone_fn.cb)(self)
extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
let az_instant_ptr = unsafe { &*ptr };
InstantPtr {
ptr: az_instant_ptr.ptr.clone(),
clone_fn: az_instant_ptr.clone_fn.clone(),
destructor: az_instant_ptr.destructor.clone(),
run_destructor: true,
impl From<StdInstant> for InstantPtr {
fn from(s: StdInstant) -> InstantPtr {
Self {
ptr: Box::new(s),
clone_fn: InstantPtrCloneCallback {
cb: std_instant_clone,
destructor: InstantPtrDestructorCallback {
cb: std_instant_drop,
impl From<InstantPtr> for StdInstant {
fn from(s: InstantPtr) -> StdInstant {
s.get()
impl Drop for InstantPtr {
fn drop(&mut self) {
if self.run_destructor {
self.run_destructor = false;
(self.destructor.cb)(self);
extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
// ---- LIBSTD implementation for InstantPtr END
/// A span of time, either from the system clock or as tick difference.
/// Mirrors `Instant` variants - System durations work with System instants,
/// Tick durations work with Tick instants.
pub enum Duration {
/// System duration from std::time::Duration (requires "std" feature)
System(SystemTimeDiff),
/// Tick-based duration for embedded systems
Tick(SystemTickDiff),
impl core::fmt::Display for Duration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Duration::System(s) => {
let s: StdDuration = s.clone().into();
write!(f, "{:?}", s)
Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
Duration::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
impl From<StdDuration> for Duration {
fn from(s: StdDuration) -> Self {
Duration::System(s.into())
impl Duration {
/// Returns the maximum possible duration.
pub fn max() -> Self {
Duration::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
tick_diff: u64::MAX,
/// Divides this duration by another, returning the ratio as f32.
pub fn div(&self, other: &Self) -> f32 {
use self::Duration::*;
match (self, other) {
(System(s), System(s2)) => s.div(s2) as f32,
(Tick(t), Tick(t2)) => t.div(t2) as f32,
_ => 0.0,
/// Returns the smaller of two durations.
pub fn min(self, other: Self) -> Self {
if self.smaller_than(&other) {
self
other
/// Returns true if self > other (panics if variants differ).
#[allow(unused_variables)]
pub fn greater_than(&self, other: &Self) -> bool {
// self > other
(Duration::System(s), Duration::System(o)) => {
let o: StdDuration = o.clone().into();
s > o
(Duration::Tick(s), Duration::Tick(o)) => s.tick_diff > o.tick_diff,
panic!("illegal: trying to compare a SystemDuration with a TickDuration");
/// Returns true if self < other (panics if variants differ).
pub fn smaller_than(&self, other: &Self) -> bool {
// self < other
s < o
(Duration::Tick(s), Duration::Tick(o)) => s.tick_diff < o.tick_diff,
/// Represents a difference in ticks for systems that
/// don't support timing
pub struct SystemTickDiff {
pub tick_diff: u64,
impl SystemTickDiff {
/// Divide duration A by duration B.
/// Returns `Inf` or `NaN` if `other` is zero.
pub fn div(&self, other: &Self) -> f64 {
self.tick_diff as f64 / other.tick_diff as f64
/// Duration represented as seconds + nanoseconds (mirrors std::time::Duration).
pub struct SystemTimeDiff {
pub secs: u64,
pub nanos: u32,
impl SystemTimeDiff {
self.as_secs_f64() / other.as_secs_f64()
fn as_secs_f64(&self) -> f64 {
(self.secs as f64) + ((self.nanos as f64) / (NANOS_PER_SEC as f64))
impl From<StdDuration> for SystemTimeDiff {
fn from(d: StdDuration) -> SystemTimeDiff {
SystemTimeDiff {
secs: d.as_secs(),
nanos: d.subsec_nanos(),
impl From<SystemTimeDiff> for StdDuration {
fn from(d: SystemTimeDiff) -> StdDuration {
StdDuration::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;
/// Creates a duration from whole seconds.
pub const fn from_secs(secs: u64) -> Self {
SystemTimeDiff { secs, nanos: 0 }
/// Creates a duration from milliseconds.
pub const fn from_millis(millis: u64) -> Self {
secs: millis / MILLIS_PER_SEC,
nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
/// Creates a duration from nanoseconds.
pub const fn from_nanos(nanos: u64) -> Self {
secs: nanos / (NANOS_PER_SEC as u64),
nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
/// Adds two durations, returning None on overflow.
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;
return None;
Some(SystemTimeDiff { secs, nanos })
None
/// Returns the total duration in milliseconds.
pub fn millis(&self) -> u64 {
(self.secs * MILLIS_PER_SEC) + (self.nanos / NANOS_PER_MILLI) as u64
/// Converts to std::time::Duration.
pub fn get(&self) -> StdDuration {
(*self).into()
Instant,
OptionInstant,
copy = false,
[Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
Duration,
OptionDuration,
/// Message that can be sent from the main thread to the Thread using the ThreadId.
/// The thread can ignore the event.
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum ThreadSendMsg {
/// The thread should terminate at the nearest
TerminateThread,
/// Next frame tick
Tick,
/// Custom data
Custom(RefAny),
ThreadSendMsg,
OptionThreadSendMsg,
[Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
/// Channel endpoint for receiving messages from the main thread in a background thread.
/// Thread-safe wrapper around the receiver end of a message channel.
#[derive(Debug)]
pub struct ThreadReceiver {
pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
/// For FFI: stores the foreign callable (e.g., PyFunction)
pub ctx: OptionRefAny,
impl Clone for ThreadReceiver {
ptr: self.ptr.clone(),
ctx: self.ctx.clone(),
impl Drop for ThreadReceiver {
impl ThreadReceiver {
/// Creates a new receiver (no-op on no_std).
pub fn new(_t: ThreadReceiverInner) -> Self {
ptr: core::ptr::null(),
run_destructor: false,
ctx: OptionRefAny::None,
/// Creates a new receiver wrapping the inner channel.
pub fn new(t: ThreadReceiverInner) -> Self {
ptr: Box::new(Arc::new(Mutex::new(t))),
/// Get the FFI context (e.g., Python callable)
pub fn get_ctx(&self) -> OptionRefAny {
self.ctx.clone()
/// Receives a message (returns None on no_std).
pub fn recv(&mut self) -> OptionThreadSendMsg {
None.into()
/// Receives a message from the main thread, if available.
let ts = match self.ptr.lock().ok() {
Some(s) => s,
None => return None.into(),
(ts.recv_fn.cb)(ts.ptr.as_ref() as *const _ as *const c_void)
/// Inner receiver state containing the actual channel and callbacks.
#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
pub struct ThreadReceiverInner {
pub ptr: Box<Receiver<ThreadSendMsg>>,
pub recv_fn: ThreadRecvCallback,
pub destructor: ThreadReceiverDestructorCallback,
unsafe impl Send for ThreadReceiverInner {}
impl core::hash::Hash for ThreadReceiverInner {
(self.ptr.as_ref() as *const _ as usize).hash(state);
impl PartialEq for ThreadReceiverInner {
fn eq(&self, other: &Self) -> bool {
(self.ptr.as_ref() as *const _ as usize) == (other.ptr.as_ref() as *const _ as usize)
impl Eq for ThreadReceiverInner {}
impl PartialOrd for ThreadReceiverInner {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(
(self.ptr.as_ref() as *const _ as usize)
.cmp(&(other.ptr.as_ref() as *const _ as usize)),
impl Ord for ThreadReceiverInner {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
(self.ptr.as_ref() as *const _ as usize).cmp(&(other.ptr.as_ref() as *const _ as usize))
impl Drop for ThreadReceiverInner {
/// Get the current system type, equivalent to `std::time::Instant::now()`, except it
/// also works on systems that don't have a clock (such as embedded timers)
pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
pub struct GetSystemTimeCallback {
pub cb: GetSystemTimeCallbackType,
impl_callback_simple!(GetSystemTimeCallback);
/// Default implementation that gets the current system time.
/// On WASM targets `std::time::Instant::now()` panics, so we fall back to
/// a zero-tick instant instead.
#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
pub extern "C" fn get_system_time_libstd() -> Instant {
/// Fallback for WASM (where `Instant::now()` panics) and no-std targets.
#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
/// Callback to check if a thread has finished execution.
pub type CheckThreadFinishedCallbackType =
extern "C" fn(/* dropcheck */ *const c_void) -> bool;
/// Wrapper for thread completion check callback.
pub struct CheckThreadFinishedCallback {
pub cb: CheckThreadFinishedCallbackType,
impl_callback_simple!(CheckThreadFinishedCallback);
/// Callback to send a message to a background thread.
pub type LibrarySendThreadMsgCallbackType =
extern "C" fn(/* Sender<ThreadSendMsg> */ *const c_void, ThreadSendMsg) -> bool;
/// Wrapper for thread message send callback.
pub struct LibrarySendThreadMsgCallback {
pub cb: LibrarySendThreadMsgCallbackType,
impl_callback_simple!(LibrarySendThreadMsgCallback);
/// Callback for a running thread to receive messages from the main thread.
pub type ThreadRecvCallbackType =
extern "C" fn(/* receiver.ptr */ *const c_void) -> OptionThreadSendMsg;
/// Wrapper for thread message receive callback.
pub struct ThreadRecvCallback {
pub cb: ThreadRecvCallbackType,
impl_callback_simple!(ThreadRecvCallback);
/// Callback to destroy a ThreadReceiver.
pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
/// Wrapper for thread receiver destructor callback.
pub struct ThreadReceiverDestructorCallback {
pub cb: ThreadReceiverDestructorCallbackType,
impl_callback_simple!(ThreadReceiverDestructorCallback);