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