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