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    sync::atomic::{AtomicUsize, Ordering},
21};
22#[cfg(feature = "std")]
23use std::sync::mpsc::{Receiver, Sender};
24#[cfg(feature = "std")]
25use std::sync::Mutex;
26#[cfg(feature = "std")]
27use std::thread::{self, JoinHandle};
28#[cfg(feature = "std")]
29use std::time::Duration as StdDuration;
30#[cfg(feature = "std")]
31use std::time::Instant as StdInstant;
32
33use azul_css::{props::property::CssProperty, AzString};
34use rust_fontconfig::FcFontCache;
35
36use crate::{
37    callbacks::{FocusTarget, TimerCallbackReturn, Update},
38    dom::{DomId, DomNodeId, OptionDomNodeId},
39    geom::{LogicalPosition, OptionLogicalPosition},
40    gl::OptionGlContextPtr,
41    hit_test::ScrollPosition,
42    id::NodeId,
43    refany::{OptionRefAny, RefAny},
44    resources::{ImageCache, ImageMask, ImageRef},
45    styled_dom::NodeHierarchyItemId,
46    window::RawWindowHandle,
47    FastBTreeSet, OrderedMap,
48};
49
50/// Should a timer terminate or not - used to remove active timers
51#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
52#[repr(C)]
53pub enum TerminateTimer {
54    /// Remove the timer from the list of active timers
55    Terminate,
56    /// Do nothing and let the timers continue to run
57    Continue,
58}
59
60// ============================================================================
61// Reserved System Timer IDs (0x0000 - 0x00FF)
62// ============================================================================
63// User timers start at 0x0100 to avoid conflicts with system timers.
64// These constants define well-known timer IDs for internal framework use.
65
66/// Timer ID for cursor blinking in contenteditable elements (~530ms interval)
67pub const CURSOR_BLINK_TIMER_ID: TimerId = TimerId { id: 0x0001 };
68/// Timer ID for scroll momentum/inertia animation
69pub const SCROLL_MOMENTUM_TIMER_ID: TimerId = TimerId { id: 0x0002 };
70/// Timer ID for auto-scroll during drag operations near edges
71pub const DRAG_AUTOSCROLL_TIMER_ID: TimerId = TimerId { id: 0x0003 };
72/// Timer ID for tooltip show delay.
73///
74/// Started by the platform event loop when the hover target changes to a node
75/// that advertises a tooltip source (`aria-label` / `alt` / `title`); fires
76/// once after `SystemStyle::input_metrics.hover_time_ms` (SPI_GETMOUSEHOVERTIME
77/// on Windows, default 400ms) and emits a `ShowTooltip` `CallbackChange`. The
78/// timer is torn down on hover loss, which also emits `HideTooltip`.
79///
80/// Double-click detection used to live on a neighbouring reserved ID but is
81/// now handled entirely by `GestureManager::detect_double_click`, so no
82/// equivalent `DOUBLE_CLICK_TIMER_ID` exists.
83pub const TOOLTIP_DELAY_TIMER_ID: TimerId = TimerId { id: 0x0004 };
84
85/// First available ID for user-defined timers
86pub const USER_TIMER_ID_START: usize = 0x0100;
87
88// User timers start at 0x0100 to avoid conflicts with reserved system timer IDs
89static MAX_TIMER_ID: AtomicUsize = AtomicUsize::new(USER_TIMER_ID_START);
90
91/// ID for uniquely identifying a timer
92#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
93#[repr(C)]
94pub struct TimerId {
95    pub id: usize,
96}
97
98impl TimerId {
99    /// Generates a new, unique `TimerId`.
100    #[must_use]
101    pub fn unique() -> Self {
102        TimerId {
103            id: MAX_TIMER_ID.fetch_add(1, Ordering::SeqCst),
104        }
105    }
106}
107
108impl_option!(
109    TimerId,
110    OptionTimerId,
111    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
112);
113
114impl_vec!(TimerId, TimerIdVec, TimerIdVecDestructor, TimerIdVecDestructorType, TimerIdVecSlice, OptionTimerId);
115impl_vec_debug!(TimerId, TimerIdVec);
116impl_vec_clone!(TimerId, TimerIdVec, TimerIdVecDestructor);
117impl_vec_partialeq!(TimerId, TimerIdVec);
118impl_vec_partialord!(TimerId, TimerIdVec);
119
120// Thread IDs 0-4 are reserved for internal framework use.
121// User threads start at RESERVED_THREAD_ID_COUNT.
122const RESERVED_THREAD_ID_COUNT: usize = 5;
123static MAX_THREAD_ID: AtomicUsize = AtomicUsize::new(RESERVED_THREAD_ID_COUNT);
124
125/// ID for uniquely identifying a background thread
126#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
127#[repr(C)]
128pub struct ThreadId {
129    id: usize,
130}
131
132impl_option!(
133    ThreadId,
134    OptionThreadId,
135    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
136);
137
138impl_vec!(ThreadId, ThreadIdVec, ThreadIdVecDestructor, ThreadIdVecDestructorType, ThreadIdVecSlice, OptionThreadId);
139impl_vec_debug!(ThreadId, ThreadIdVec);
140impl_vec_clone!(ThreadId, ThreadIdVec, ThreadIdVecDestructor);
141impl_vec_partialeq!(ThreadId, ThreadIdVec);
142impl_vec_partialord!(ThreadId, ThreadIdVec);
143
144impl ThreadId {
145    /// Generates a new, unique `ThreadId`.
146    #[must_use]
147    pub fn unique() -> Self {
148        ThreadId {
149            id: MAX_THREAD_ID.fetch_add(1, Ordering::SeqCst),
150        }
151    }
152}
153
154/// A point in time, either from the system clock or a tick counter.
155///
156/// Use `Instant::System` on platforms with std, `Instant::Tick` on embedded/no_std.
157#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
158#[repr(C, u8)]
159pub enum Instant {
160    /// System time from std::time::Instant (requires "std" feature)
161    System(InstantPtr),
162    /// Tick-based time for embedded systems without a real-time clock
163    Tick(SystemTick),
164}
165
166#[cfg(feature = "std")]
167impl From<StdInstant> for Instant {
168    fn from(s: StdInstant) -> Instant {
169        Instant::System(s.into())
170    }
171}
172
173impl Instant {
174    /// Returns the current system time.
175    /// 
176    /// On systems with std, this uses `std::time::Instant::now()`.
177    /// On no_std systems, this returns a zero tick.
178    #[cfg(feature = "std")]
179    pub fn now() -> Self {
180        StdInstant::now().into()
181    }
182
183    /// Returns the current system time (no_std fallback).
184    #[cfg(not(feature = "std"))]
185    pub fn now() -> Self {
186        Instant::Tick(SystemTick::new(0))
187    }
188
189    /// Returns a number from 0.0 to 1.0 indicating the current
190    /// linear interpolation value between (start, end)
191    pub fn linear_interpolate(&self, mut start: Self, mut end: Self) -> f32 {
192        use core::mem;
193
194        if end < start {
195            mem::swap(&mut start, &mut end);
196        }
197
198        if *self < start {
199            return 0.0;
200        }
201        if *self > end {
202            return 1.0;
203        }
204
205        let duration_total = end.duration_since(&start);
206        let duration_current = self.duration_since(&start);
207
208        duration_current.div(&duration_total).max(0.0).min(1.0)
209    }
210
211    /// Adds a duration to the instant, does nothing in undefined cases
212    /// (i.e. trying to add a Duration::Tick to an Instant::System)
213    pub fn add_optional_duration(&self, duration: Option<&Duration>) -> Self {
214        match duration {
215            Some(d) => match (self, d) {
216                (Instant::System(i), Duration::System(d)) => {
217                    #[cfg(feature = "std")]
218                    {
219                        let s: StdInstant = i.clone().into();
220                        let d: StdDuration = d.clone().into();
221                        let new: InstantPtr = (s + d).into();
222                        Instant::System(new)
223                    }
224                    #[cfg(not(feature = "std"))]
225                    {
226                        unreachable!()
227                    }
228                }
229                (Instant::Tick(s), Duration::Tick(d)) => Instant::Tick(SystemTick {
230                    tick_counter: s.tick_counter + d.tick_diff,
231                }),
232                _ => {
233                    panic!(
234                        "invalid: trying to add a duration {:?} to an instant {:?}",
235                        d, self
236                    );
237                }
238            },
239            None => self.clone(),
240        }
241    }
242
243    /// Converts to std::time::Instant (panics if Tick variant).
244    #[cfg(feature = "std")]
245    pub fn into_std_instant(self) -> StdInstant {
246        match self {
247            Instant::System(s) => s.into(),
248            Instant::Tick(_) => unreachable!(),
249        }
250    }
251
252    /// Calculates the duration since an earlier point in time
253    ///
254    /// - Panics if the earlier Instant was created after the current Instant
255    /// - Panics if the two enums do not have the same variant (tick / std)
256    pub fn duration_since(&self, earlier: &Instant) -> Duration {
257        match (earlier, self) {
258            (Instant::System(prev), Instant::System(now)) => {
259                #[cfg(feature = "std")]
260                {
261                    let prev_instant: StdInstant = prev.clone().into();
262                    let now_instant: StdInstant = now.clone().into();
263                    Duration::System((now_instant.duration_since(prev_instant)).into())
264                }
265                #[cfg(not(feature = "std"))]
266                {
267                    unreachable!() // cannot construct a System instant on no_std
268                }
269            }
270            (
271                Instant::Tick(SystemTick { tick_counter: prev }),
272                Instant::Tick(SystemTick { tick_counter: now }),
273            ) => {
274                if prev > now {
275                    panic!(
276                        "illegal: subtraction 'Instant - Instant' would result in a negative \
277                         duration"
278                    )
279                } else {
280                    Duration::Tick(SystemTickDiff {
281                        tick_diff: now - prev,
282                    })
283                }
284            }
285            _ => panic!(
286                "illegal: trying to calculate a Duration from a SystemTime and a Tick instant"
287            ),
288        }
289    }
290}
291
292/// Tick-based timestamp for systems without a real-time clock.
293///
294/// Used on embedded systems where time is measured in frame ticks or cycles.
295#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
296#[repr(C)]
297pub struct SystemTick {
298    pub tick_counter: u64,
299}
300
301impl SystemTick {
302    /// Creates a new tick timestamp from a counter value.
303    pub const fn new(tick_counter: u64) -> Self {
304        Self { tick_counter }
305    }
306}
307
308/// FFI-safe wrapper around std::time::Instant with custom clone/drop callbacks.
309///
310/// Allows crossing FFI boundaries while maintaining proper memory management.
311#[repr(C)]
312pub struct InstantPtr {
313    #[cfg(feature = "std")]
314    pub ptr: Box<StdInstant>,
315    #[cfg(not(feature = "std"))]
316    pub ptr: *const c_void,
317    pub clone_fn: InstantPtrCloneCallback,
318    pub destructor: InstantPtrDestructorCallback,
319    pub run_destructor: bool,
320}
321
322pub type InstantPtrCloneCallbackType = extern "C" fn(*const InstantPtr) -> InstantPtr;
323#[repr(C)]
324pub struct InstantPtrCloneCallback {
325    pub cb: InstantPtrCloneCallbackType,
326}
327impl_callback_simple!(InstantPtrCloneCallback);
328
329pub type InstantPtrDestructorCallbackType = extern "C" fn(*mut InstantPtr);
330#[repr(C)]
331pub struct InstantPtrDestructorCallback {
332    pub cb: InstantPtrDestructorCallbackType,
333}
334impl_callback_simple!(InstantPtrDestructorCallback);
335
336// ----  LIBSTD implementation for InstantPtr BEGIN
337#[cfg(feature = "std")]
338impl core::fmt::Debug for InstantPtr {
339    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
340        write!(f, "{:?}", self.get())
341    }
342}
343
344#[cfg(not(feature = "std"))]
345impl core::fmt::Debug for InstantPtr {
346    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
347        write!(f, "{:?}", self.ptr as usize)
348    }
349}
350
351#[cfg(feature = "std")]
352impl core::hash::Hash for InstantPtr {
353    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
354        self.get().hash(state);
355    }
356}
357
358#[cfg(not(feature = "std"))]
359impl core::hash::Hash for InstantPtr {
360    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
361        (self.ptr as usize).hash(state);
362    }
363}
364
365#[cfg(feature = "std")]
366impl PartialEq for InstantPtr {
367    fn eq(&self, other: &InstantPtr) -> bool {
368        self.get() == other.get()
369    }
370}
371
372#[cfg(not(feature = "std"))]
373impl PartialEq for InstantPtr {
374    fn eq(&self, other: &InstantPtr) -> bool {
375        (self.ptr as usize).eq(&(other.ptr as usize))
376    }
377}
378
379impl Eq for InstantPtr {}
380
381#[cfg(feature = "std")]
382impl PartialOrd for InstantPtr {
383    fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
384        Some((self.get()).cmp(&(other.get())))
385    }
386}
387
388#[cfg(not(feature = "std"))]
389impl PartialOrd for InstantPtr {
390    fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
391        Some((self.ptr as usize).cmp(&(other.ptr as usize)))
392    }
393}
394
395#[cfg(feature = "std")]
396impl Ord for InstantPtr {
397    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
398        (self.get()).cmp(&(other.get()))
399    }
400}
401
402#[cfg(not(feature = "std"))]
403impl Ord for InstantPtr {
404    fn cmp(&self, other: &Self) -> ::core::cmp::Ordering {
405        (self.ptr as usize).cmp(&(other.ptr as usize))
406    }
407}
408
409#[cfg(feature = "std")]
410impl InstantPtr {
411    fn get(&self) -> StdInstant {
412        *(self.ptr).clone()
413    }
414}
415
416impl Clone for InstantPtr {
417    fn clone(&self) -> Self {
418        (self.clone_fn.cb)(self)
419    }
420}
421
422#[cfg(feature = "std")]
423extern "C" fn std_instant_clone(ptr: *const InstantPtr) -> InstantPtr {
424    let az_instant_ptr = unsafe { &*ptr };
425    InstantPtr {
426        ptr: az_instant_ptr.ptr.clone(),
427        clone_fn: az_instant_ptr.clone_fn.clone(),
428        destructor: az_instant_ptr.destructor.clone(),
429        run_destructor: true,
430    }
431}
432
433#[cfg(feature = "std")]
434impl From<StdInstant> for InstantPtr {
435    fn from(s: StdInstant) -> InstantPtr {
436        Self {
437            ptr: Box::new(s),
438            clone_fn: InstantPtrCloneCallback {
439                cb: std_instant_clone,
440            },
441            destructor: InstantPtrDestructorCallback {
442                cb: std_instant_drop,
443            },
444            run_destructor: true,
445        }
446    }
447}
448
449#[cfg(feature = "std")]
450impl From<InstantPtr> for StdInstant {
451    fn from(s: InstantPtr) -> StdInstant {
452        s.get()
453    }
454}
455
456impl Drop for InstantPtr {
457    fn drop(&mut self) {
458        if self.run_destructor {
459            self.run_destructor = false;
460            (self.destructor.cb)(self);
461        }
462    }
463}
464
465#[cfg(feature = "std")]
466extern "C" fn std_instant_drop(_: *mut InstantPtr) {}
467
468// ----  LIBSTD implementation for InstantPtr END
469
470/// A span of time, either from the system clock or as tick difference.
471///
472/// Mirrors `Instant` variants - System durations work with System instants,
473/// Tick durations work with Tick instants.
474#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
475#[repr(C, u8)]
476pub enum Duration {
477    /// System duration from std::time::Duration (requires "std" feature)
478    System(SystemTimeDiff),
479    /// Tick-based duration for embedded systems
480    Tick(SystemTickDiff),
481}
482
483impl core::fmt::Display for Duration {
484    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
485        match self {
486            #[cfg(feature = "std")]
487            Duration::System(s) => {
488                let s: StdDuration = s.clone().into();
489                write!(f, "{:?}", s)
490            }
491            #[cfg(not(feature = "std"))]
492            Duration::System(s) => write!(f, "({}s, {}ns)", s.secs, s.nanos),
493            Duration::Tick(tick) => write!(f, "{} ticks", tick.tick_diff),
494        }
495    }
496}
497
498#[cfg(feature = "std")]
499impl From<StdDuration> for Duration {
500    fn from(s: StdDuration) -> Self {
501        Duration::System(s.into())
502    }
503}
504
505impl Duration {
506    /// Returns the maximum possible duration.
507    pub fn max() -> Self {
508        #[cfg(feature = "std")]
509        {
510            Duration::System(StdDuration::new(core::u64::MAX, NANOS_PER_SEC - 1).into())
511        }
512        #[cfg(not(feature = "std"))]
513        {
514            Duration::Tick(SystemTickDiff {
515                tick_diff: u64::MAX,
516            })
517        }
518    }
519
520    /// Divides this duration by another, returning the ratio as f32.
521    pub fn div(&self, other: &Self) -> f32 {
522        use self::Duration::*;
523        match (self, other) {
524            (System(s), System(s2)) => s.div(s2) as f32,
525            (Tick(t), Tick(t2)) => t.div(t2) as f32,
526            _ => 0.0,
527        }
528    }
529
530    /// Returns the smaller of two durations.
531    pub fn min(self, other: Self) -> Self {
532        if self.smaller_than(&other) {
533            self
534        } else {
535            other
536        }
537    }
538
539    /// Returns true if self > other (panics if variants differ).
540    #[allow(unused_variables)]
541    pub fn greater_than(&self, other: &Self) -> bool {
542        match (self, other) {
543            // self > other
544            (Duration::System(s), Duration::System(o)) => {
545                #[cfg(feature = "std")]
546                {
547                    let s: StdDuration = s.clone().into();
548                    let o: StdDuration = o.clone().into();
549                    s > o
550                }
551                #[cfg(not(feature = "std"))]
552                {
553                    unreachable!()
554                }
555            }
556            (Duration::Tick(s), Duration::Tick(o)) => s.tick_diff > o.tick_diff,
557            _ => {
558                panic!("illegal: trying to compare a SystemDuration with a TickDuration");
559            }
560        }
561    }
562
563    /// Returns true if self < other (panics if variants differ).
564    #[allow(unused_variables)]
565    pub fn smaller_than(&self, other: &Self) -> bool {
566        // self < other
567        match (self, other) {
568            // self > other
569            (Duration::System(s), Duration::System(o)) => {
570                #[cfg(feature = "std")]
571                {
572                    let s: StdDuration = s.clone().into();
573                    let o: StdDuration = o.clone().into();
574                    s < o
575                }
576                #[cfg(not(feature = "std"))]
577                {
578                    unreachable!()
579                }
580            }
581            (Duration::Tick(s), Duration::Tick(o)) => s.tick_diff < o.tick_diff,
582            _ => {
583                panic!("illegal: trying to compare a SystemDuration with a TickDuration");
584            }
585        }
586    }
587}
588
589/// Represents a difference in ticks for systems that
590/// don't support timing
591#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
592#[repr(C)]
593pub struct SystemTickDiff {
594    pub tick_diff: u64,
595}
596
597impl SystemTickDiff {
598    /// Divide duration A by duration B.
599    /// Returns `Inf` or `NaN` if `other` is zero.
600    pub fn div(&self, other: &Self) -> f64 {
601        self.tick_diff as f64 / other.tick_diff as f64
602    }
603}
604
605/// Duration represented as seconds + nanoseconds (mirrors std::time::Duration).
606#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
607#[repr(C)]
608pub struct SystemTimeDiff {
609    pub secs: u64,
610    pub nanos: u32,
611}
612
613impl SystemTimeDiff {
614    /// Divide duration A by duration B.
615    /// Returns `Inf` or `NaN` if `other` is zero.
616    pub fn div(&self, other: &Self) -> f64 {
617        self.as_secs_f64() / other.as_secs_f64()
618    }
619    fn as_secs_f64(&self) -> f64 {
620        (self.secs as f64) + ((self.nanos as f64) / (NANOS_PER_SEC as f64))
621    }
622}
623
624#[cfg(feature = "std")]
625impl From<StdDuration> for SystemTimeDiff {
626    fn from(d: StdDuration) -> SystemTimeDiff {
627        SystemTimeDiff {
628            secs: d.as_secs(),
629            nanos: d.subsec_nanos(),
630        }
631    }
632}
633
634#[cfg(feature = "std")]
635impl From<SystemTimeDiff> for StdDuration {
636    fn from(d: SystemTimeDiff) -> StdDuration {
637        StdDuration::new(d.secs, d.nanos)
638    }
639}
640
641const MILLIS_PER_SEC: u64 = 1_000;
642const NANOS_PER_MILLI: u32 = 1_000_000;
643const NANOS_PER_SEC: u32 = 1_000_000_000;
644
645impl SystemTimeDiff {
646    /// Creates a duration from whole seconds.
647    pub const fn from_secs(secs: u64) -> Self {
648        SystemTimeDiff { secs, nanos: 0 }
649    }
650    /// Creates a duration from milliseconds.
651    pub const fn from_millis(millis: u64) -> Self {
652        SystemTimeDiff {
653            secs: millis / MILLIS_PER_SEC,
654            nanos: ((millis % MILLIS_PER_SEC) as u32) * NANOS_PER_MILLI,
655        }
656    }
657    /// Creates a duration from nanoseconds.
658    pub const fn from_nanos(nanos: u64) -> Self {
659        SystemTimeDiff {
660            secs: nanos / (NANOS_PER_SEC as u64),
661            nanos: (nanos % (NANOS_PER_SEC as u64)) as u32,
662        }
663    }
664    /// Adds two durations, returning None on overflow.
665    pub const fn checked_add(self, rhs: Self) -> Option<Self> {
666        if let Some(mut secs) = self.secs.checked_add(rhs.secs) {
667            let mut nanos = self.nanos + rhs.nanos;
668            if nanos >= NANOS_PER_SEC {
669                nanos -= NANOS_PER_SEC;
670                if let Some(new_secs) = secs.checked_add(1) {
671                    secs = new_secs;
672                } else {
673                    return None;
674                }
675            }
676            Some(SystemTimeDiff { secs, nanos })
677        } else {
678            None
679        }
680    }
681
682    /// Returns the total duration in milliseconds.
683    pub fn millis(&self) -> u64 {
684        (self.secs * MILLIS_PER_SEC) + (self.nanos / NANOS_PER_MILLI) as u64
685    }
686
687    /// Converts to std::time::Duration.
688    #[cfg(feature = "std")]
689    pub fn get(&self) -> StdDuration {
690        (*self).into()
691    }
692}
693
694impl_option!(
695    Instant,
696    OptionInstant,
697    copy = false,
698    [Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
699);
700impl_option!(
701    Duration,
702    OptionDuration,
703    [Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash]
704);
705
706/// Message that can be sent from the main thread to the Thread using the ThreadId.
707///
708/// The thread can ignore the event.
709#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
710#[repr(C, u8)]
711pub enum ThreadSendMsg {
712    /// The thread should terminate at the nearest
713    TerminateThread,
714    /// Next frame tick
715    Tick,
716    /// Custom data
717    Custom(RefAny),
718}
719
720impl_option!(
721    ThreadSendMsg,
722    OptionThreadSendMsg,
723    copy = false,
724    [Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash]
725);
726
727/// Channel endpoint for receiving messages from the main thread in a background thread.
728///
729/// Thread-safe wrapper around the receiver end of a message channel.
730#[derive(Debug)]
731#[repr(C)]
732pub struct ThreadReceiver {
733    #[cfg(feature = "std")]
734    pub ptr: Box<Arc<Mutex<ThreadReceiverInner>>>,
735    #[cfg(not(feature = "std"))]
736    pub ptr: *const c_void,
737    pub run_destructor: bool,
738    /// For FFI: stores the foreign callable (e.g., PyFunction)
739    pub ctx: OptionRefAny,
740}
741
742impl Clone for ThreadReceiver {
743    fn clone(&self) -> Self {
744        Self {
745            ptr: self.ptr.clone(),
746            run_destructor: true,
747            ctx: self.ctx.clone(),
748        }
749    }
750}
751
752impl Drop for ThreadReceiver {
753    fn drop(&mut self) {
754        self.run_destructor = false;
755    }
756}
757
758impl ThreadReceiver {
759    /// Creates a new receiver (no-op on no_std).
760    #[cfg(not(feature = "std"))]
761    pub fn new(_t: ThreadReceiverInner) -> Self {
762        Self {
763            ptr: core::ptr::null(),
764            run_destructor: false,
765            ctx: OptionRefAny::None,
766        }
767    }
768
769    /// Creates a new receiver wrapping the inner channel.
770    #[cfg(feature = "std")]
771    pub fn new(t: ThreadReceiverInner) -> Self {
772        Self {
773            ptr: Box::new(Arc::new(Mutex::new(t))),
774            run_destructor: true,
775            ctx: OptionRefAny::None,
776        }
777    }
778
779    /// Get the FFI context (e.g., Python callable)
780    pub fn get_ctx(&self) -> OptionRefAny {
781        self.ctx.clone()
782    }
783
784    /// Receives a message (returns None on no_std).
785    #[cfg(not(feature = "std"))]
786    pub fn recv(&mut self) -> OptionThreadSendMsg {
787        None.into()
788    }
789
790    /// Receives a message from the main thread, if available.
791    #[cfg(feature = "std")]
792    pub fn recv(&mut self) -> OptionThreadSendMsg {
793        let ts = match self.ptr.lock().ok() {
794            Some(s) => s,
795            None => return None.into(),
796        };
797        (ts.recv_fn.cb)(ts.ptr.as_ref() as *const _ as *const c_void)
798    }
799}
800
801/// Inner receiver state containing the actual channel and callbacks.
802#[derive(Debug)]
803#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
804#[repr(C)]
805pub struct ThreadReceiverInner {
806    #[cfg(feature = "std")]
807    pub ptr: Box<Receiver<ThreadSendMsg>>,
808    #[cfg(not(feature = "std"))]
809    pub ptr: *const c_void,
810    pub recv_fn: ThreadRecvCallback,
811    pub destructor: ThreadReceiverDestructorCallback,
812}
813
814#[cfg(not(feature = "std"))]
815unsafe impl Send for ThreadReceiverInner {}
816
817#[cfg(feature = "std")]
818impl core::hash::Hash for ThreadReceiverInner {
819    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
820        (self.ptr.as_ref() as *const _ as usize).hash(state);
821    }
822}
823
824#[cfg(feature = "std")]
825impl PartialEq for ThreadReceiverInner {
826    fn eq(&self, other: &Self) -> bool {
827        (self.ptr.as_ref() as *const _ as usize) == (other.ptr.as_ref() as *const _ as usize)
828    }
829}
830
831#[cfg(feature = "std")]
832impl Eq for ThreadReceiverInner {}
833
834#[cfg(feature = "std")]
835impl PartialOrd for ThreadReceiverInner {
836    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
837        Some(
838            (self.ptr.as_ref() as *const _ as usize)
839                .cmp(&(other.ptr.as_ref() as *const _ as usize)),
840        )
841    }
842}
843
844#[cfg(feature = "std")]
845impl Ord for ThreadReceiverInner {
846    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
847        (self.ptr.as_ref() as *const _ as usize).cmp(&(other.ptr.as_ref() as *const _ as usize))
848    }
849}
850
851impl Drop for ThreadReceiverInner {
852    fn drop(&mut self) {
853        (self.destructor.cb)(self);
854    }
855}
856
857/// Get the current system type, equivalent to `std::time::Instant::now()`, except it
858/// also works on systems that don't have a clock (such as embedded timers)
859pub type GetSystemTimeCallbackType = extern "C" fn() -> Instant;
860#[repr(C)]
861pub struct GetSystemTimeCallback {
862    pub cb: GetSystemTimeCallbackType,
863}
864impl_callback_simple!(GetSystemTimeCallback);
865
866/// Default implementation that gets the current system time.
867///
868/// On WASM targets `std::time::Instant::now()` panics, so we fall back to
869/// a zero-tick instant instead.
870#[cfg(all(feature = "std", not(target_arch = "wasm32")))]
871pub extern "C" fn get_system_time_libstd() -> Instant {
872    StdInstant::now().into()
873}
874
875/// Fallback for WASM (where `Instant::now()` panics) and no-std targets.
876#[cfg(any(not(feature = "std"), target_arch = "wasm32"))]
877pub extern "C" fn get_system_time_libstd() -> Instant {
878    Instant::Tick(SystemTick::new(0))
879}
880
881/// Callback to check if a thread has finished execution.
882pub type CheckThreadFinishedCallbackType =
883    extern "C" fn(/* dropcheck */ *const c_void) -> bool;
884/// Wrapper for thread completion check callback.
885#[repr(C)]
886pub struct CheckThreadFinishedCallback {
887    pub cb: CheckThreadFinishedCallbackType,
888}
889impl_callback_simple!(CheckThreadFinishedCallback);
890
891/// Callback to send a message to a background thread.
892pub type LibrarySendThreadMsgCallbackType =
893    extern "C" fn(/* Sender<ThreadSendMsg> */ *const c_void, ThreadSendMsg) -> bool;
894/// Wrapper for thread message send callback.
895#[repr(C)]
896pub struct LibrarySendThreadMsgCallback {
897    pub cb: LibrarySendThreadMsgCallbackType,
898}
899impl_callback_simple!(LibrarySendThreadMsgCallback);
900
901/// Callback for a running thread to receive messages from the main thread.
902pub type ThreadRecvCallbackType =
903    extern "C" fn(/* receiver.ptr */ *const c_void) -> OptionThreadSendMsg;
904/// Wrapper for thread message receive callback.
905#[repr(C)]
906pub struct ThreadRecvCallback {
907    pub cb: ThreadRecvCallbackType,
908}
909impl_callback_simple!(ThreadRecvCallback);
910
911/// Callback to destroy a ThreadReceiver.
912pub type ThreadReceiverDestructorCallbackType = extern "C" fn(*mut ThreadReceiverInner);
913/// Wrapper for thread receiver destructor callback.
914#[repr(C)]
915pub struct ThreadReceiverDestructorCallback {
916    pub cb: ThreadReceiverDestructorCallbackType,
917}
918impl_callback_simple!(ThreadReceiverDestructorCallback);