Skip to main content

azul_layout/
thread.rs

1//! Thread callback information and utilities for azul-layout
2//!
3//! This module provides thread-related callback structures for background tasks
4//! that need to interact with the UI thread and query layout information.
5
6#[cfg(feature = "std")]
7use alloc::sync::Arc;
8#[cfg(feature = "std")]
9use std::sync::{
10    mpsc::{channel, Receiver, Sender},
11    Mutex,
12};
13#[cfg(feature = "std")]
14use std::thread::{self, JoinHandle};
15
16use azul_core::{
17    callbacks::Update,
18    refany::{OptionRefAny, RefAny},
19    task::{
20        CheckThreadFinishedCallback, CheckThreadFinishedCallbackType, LibrarySendThreadMsgCallback,
21        LibrarySendThreadMsgCallbackType, OptionThreadSendMsg, ThreadId, ThreadReceiver,
22        ThreadReceiverDestructorCallback, ThreadReceiverInner, ThreadRecvCallback, ThreadSendMsg,
23    },
24};
25
26use crate::callbacks::CallbackInfo;
27
28macro_rules! impl_callback_traits {
29    ($name:ident) => {
30        impl core::fmt::Debug for $name {
31            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
32                write!(f, concat!(stringify!($name), " {{ cb: {:p} }}"), self.cb as *const ())
33            }
34        }
35        // generated for both Copy and non-Copy callback structs; the explicit field
36        // copy works uniformly (a derive can't be emitted for an externally-defined struct).
37        #[allow(clippy::expl_impl_clone_on_copy, clippy::non_canonical_clone_impl)]
38        impl Clone for $name {
39            fn clone(&self) -> Self { Self { cb: self.cb } }
40        }
41        impl PartialEq for $name {
42            fn eq(&self, other: &Self) -> bool {
43                self.cb as *const () as usize == other.cb as *const () as usize
44            }
45        }
46        impl Eq for $name {}
47        impl PartialOrd for $name {
48            fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
49                Some(self.cmp(other))
50            }
51        }
52        impl Ord for $name {
53            fn cmp(&self, other: &Self) -> core::cmp::Ordering {
54                (self.cb as *const () as usize).cmp(&(other.cb as *const () as usize))
55            }
56        }
57        impl core::hash::Hash for $name {
58            fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
59                (self.cb as *const () as usize).hash(state);
60            }
61        }
62    };
63}
64
65// Types that need to be defined locally (not in azul-core)
66#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
67/// Message that is sent back from the running thread to the main thread
68#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
69#[repr(C, u8)]
70pub enum ThreadReceiveMsg {
71    WriteBack(ThreadWriteBackMsg),
72    Update(Update),
73}
74#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
75#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
76#[repr(C, u8)]
77pub enum OptionThreadReceiveMsg {
78    None,
79    Some(ThreadReceiveMsg),
80}
81
82impl From<Option<ThreadReceiveMsg>> for OptionThreadReceiveMsg {
83    fn from(inner: Option<ThreadReceiveMsg>) -> Self {
84        inner.map_or_else(|| Self::None, Self::Some)
85    }
86}
87
88impl OptionThreadReceiveMsg {
89    #[must_use] pub fn into_option(self) -> Option<ThreadReceiveMsg> {
90        match self {
91            Self::None => None,
92            Self::Some(v) => Some(v),
93        }
94    }
95
96    #[must_use] pub const fn as_ref(&self) -> Option<&ThreadReceiveMsg> {
97        match self {
98            Self::None => None,
99            Self::Some(v) => Some(v),
100        }
101    }
102}
103
104/// Message containing writeback data and callback
105#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
106#[repr(C)]
107pub struct ThreadWriteBackMsg {
108    pub refany: RefAny,
109    pub callback: WriteBackCallback,
110}
111
112impl ThreadWriteBackMsg {
113    pub fn new<C: Into<WriteBackCallback>>(callback: C, data: RefAny) -> Self {
114        Self {
115            refany: data,
116            callback: callback.into(),
117        }
118    }
119}
120
121/// `ThreadSender` allows sending messages from the background thread to the main thread
122#[derive(Debug)]
123#[repr(C)]
124pub struct ThreadSender {
125    #[cfg(feature = "std")]
126    pub ptr: Box<Arc<Mutex<ThreadSenderInner>>>,
127    #[cfg(not(feature = "std"))]
128    pub ptr: *const core::ffi::c_void,
129    pub run_destructor: bool,
130    /// For FFI: stores the foreign callable (e.g., `PyFunction`)
131    pub ctx: OptionRefAny,
132}
133
134impl Clone for ThreadSender {
135    fn clone(&self) -> Self {
136        Self {
137            ptr: self.ptr.clone(),
138            run_destructor: true,
139            ctx: self.ctx.clone(),
140        }
141    }
142}
143
144impl Drop for ThreadSender {
145    fn drop(&mut self) {
146        self.run_destructor = false;
147    }
148}
149
150impl ThreadSender {
151    #[cfg(not(feature = "std"))]
152    pub fn new(_t: ThreadSenderInner) -> Self {
153        Self {
154            ptr: core::ptr::null(),
155            run_destructor: false,
156            ctx: OptionRefAny::None,
157        }
158    }
159
160    #[cfg(feature = "std")]
161    #[must_use] pub fn new(t: ThreadSenderInner) -> Self {
162        Self {
163            ptr: Box::new(Arc::new(Mutex::new(t))),
164            run_destructor: true,
165            ctx: OptionRefAny::None,
166        }
167    }
168
169    /// Get the FFI context (e.g., Python callable)
170    #[must_use] pub fn get_ctx(&self) -> OptionRefAny {
171        self.ctx.clone()
172    }
173
174    #[cfg(not(feature = "std"))]
175    pub fn send(&mut self, _msg: ThreadReceiveMsg) -> bool {
176        false
177    }
178
179    #[cfg(feature = "std")]
180    pub fn send(&mut self, msg: ThreadReceiveMsg) -> bool {
181        let Some(ts) = self.ptr.lock().ok() else {
182            return false;
183        };
184        (ts.send_fn.cb)(std::ptr::from_ref(ts.ptr.as_ref()).cast::<core::ffi::c_void>(), msg)
185    }
186}
187
188/// Inner state of a `ThreadSender`, holding the channel sender and associated callbacks
189#[derive(Debug)]
190#[cfg_attr(not(feature = "std"), derive(PartialEq, PartialOrd, Eq, Ord))]
191#[repr(C)]
192pub struct ThreadSenderInner {
193    #[cfg(feature = "std")]
194    pub ptr: Box<Sender<ThreadReceiveMsg>>,
195    #[cfg(not(feature = "std"))]
196    pub ptr: *const core::ffi::c_void,
197    pub send_fn: ThreadSendCallback,
198    pub destructor: ThreadSenderDestructorCallback,
199}
200
201#[cfg(not(feature = "std"))]
202unsafe impl Send for ThreadSenderInner {}
203
204#[cfg(feature = "std")]
205impl core::hash::Hash for ThreadSenderInner {
206    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
207        (std::ptr::from_ref(self.ptr.as_ref()) as usize).hash(state);
208    }
209}
210
211#[cfg(feature = "std")]
212impl PartialEq for ThreadSenderInner {
213    fn eq(&self, other: &Self) -> bool {
214        std::ptr::eq(self.ptr.as_ref(), other.ptr.as_ref())
215    }
216}
217
218#[cfg(feature = "std")]
219impl Eq for ThreadSenderInner {}
220
221#[cfg(feature = "std")]
222impl PartialOrd for ThreadSenderInner {
223    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
224        Some(
225            (std::ptr::from_ref(self.ptr.as_ref()) as usize)
226                .cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize)),
227        )
228    }
229}
230
231#[cfg(feature = "std")]
232impl Ord for ThreadSenderInner {
233    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
234        (std::ptr::from_ref(self.ptr.as_ref()) as usize).cmp(&(std::ptr::from_ref(other.ptr.as_ref()) as usize))
235    }
236}
237
238impl Drop for ThreadSenderInner {
239    fn drop(&mut self) {
240        (self.destructor.cb)(self);
241    }
242}
243
244/// Callback for sending messages from thread to main thread
245pub type ThreadSendCallbackType = extern "C" fn(*const core::ffi::c_void, ThreadReceiveMsg) -> bool;
246
247#[allow(missing_copy_implementations)] // C-ABI fn-ptr wrapper; Clone is macro-generated (impl_callback_traits!), so Copy would trip expl_impl_clone_on_copy
248#[repr(C)]
249pub struct ThreadSendCallback {
250    pub cb: ThreadSendCallbackType,
251}
252
253impl_callback_traits!(ThreadSendCallback);
254
255/// Destructor callback for `ThreadSender`
256pub type ThreadSenderDestructorCallbackType = extern "C" fn(*mut ThreadSenderInner);
257
258#[allow(missing_copy_implementations)] // C-ABI fn-ptr wrapper; Clone is macro-generated (impl_callback_traits!), so Copy would trip expl_impl_clone_on_copy
259#[repr(C)]
260pub struct ThreadSenderDestructorCallback {
261    pub cb: ThreadSenderDestructorCallbackType,
262}
263
264impl_callback_traits!(ThreadSenderDestructorCallback);
265
266/// Callback that runs when a thread receives a `WriteBack` message
267///
268/// This callback runs on the main UI thread and has access to:
269/// - The thread's original data
270/// - Data sent back from the background thread
271/// - Full `CallbackInfo` for DOM queries and UI updates
272pub type WriteBackCallbackType = extern "C" fn(
273    /* original thread data */ RefAny,
274    /* data to write back */ RefAny,
275    /* callback info */ CallbackInfo,
276) -> Update;
277
278/// Callback that can run when a thread receives a `WriteBack` message
279#[repr(C)]
280pub struct WriteBackCallback {
281    pub cb: WriteBackCallbackType,
282    /// For FFI: stores the foreign callable (e.g., `PyFunction`)
283    /// Native Rust code sets this to None
284    pub ctx: OptionRefAny,
285}
286
287impl WriteBackCallback {
288    /// Create a new `WriteBackCallback`
289    pub fn new(cb: WriteBackCallbackType) -> Self {
290        Self {
291            cb,
292            ctx: OptionRefAny::None,
293        }
294    }
295
296    /// Invoke the callback
297    #[must_use] pub fn invoke(
298        &self,
299        thread_data: RefAny,
300        writeback_data: RefAny,
301        callback_info: CallbackInfo,
302    ) -> Update {
303        (self.cb)(thread_data, writeback_data, callback_info)
304    }
305}
306
307impl core::fmt::Debug for WriteBackCallback {
308    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
309        write!(f, "WriteBackCallback {{ cb: {:p} }}", self.cb as *const ())
310    }
311}
312
313impl Clone for WriteBackCallback {
314    fn clone(&self) -> Self {
315        Self {
316            cb: self.cb,
317            ctx: self.ctx.clone(),
318        }
319    }
320}
321
322impl From<WriteBackCallbackType> for WriteBackCallback {
323    fn from(cb: WriteBackCallbackType) -> Self {
324        Self {
325            cb,
326            ctx: OptionRefAny::None,
327        }
328    }
329}
330
331impl PartialEq for WriteBackCallback {
332    fn eq(&self, other: &Self) -> bool {
333        std::ptr::eq(self.cb as *const (), other.cb as *const ())
334    }
335}
336
337impl Eq for WriteBackCallback {}
338
339impl PartialOrd for WriteBackCallback {
340    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
341        Some(self.cmp(other))
342    }
343}
344
345impl Ord for WriteBackCallback {
346    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
347        (self.cb as *const () as usize).cmp(&(other.cb as *const () as usize))
348    }
349}
350
351impl core::hash::Hash for WriteBackCallback {
352    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
353        (self.cb as *const () as usize).hash(state);
354    }
355}
356
357/// Callback type for the function that runs in the background thread
358pub type ThreadCallbackType = extern "C" fn(RefAny, ThreadSender, ThreadReceiver);
359
360#[repr(C)]
361pub struct ThreadCallback {
362    pub cb: ThreadCallbackType,
363    /// For FFI: stores the foreign callable (e.g., `PyFunction`)
364    /// Native Rust code sets this to None
365    pub ctx: OptionRefAny,
366}
367
368impl ThreadCallback {
369    /// Create a new `ThreadCallback`
370    pub fn new(cb: ThreadCallbackType) -> Self {
371        Self {
372            cb,
373            ctx: OptionRefAny::None,
374        }
375    }
376}
377
378impl core::fmt::Debug for ThreadCallback {
379    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
380        write!(f, "ThreadCallback {{ cb: {:p} }}", self.cb as *const ())
381    }
382}
383
384impl Clone for ThreadCallback {
385    fn clone(&self) -> Self {
386        Self {
387            cb: self.cb,
388            ctx: self.ctx.clone(),
389        }
390    }
391}
392
393impl From<ThreadCallbackType> for ThreadCallback {
394    fn from(cb: ThreadCallbackType) -> Self {
395        Self {
396            cb,
397            ctx: OptionRefAny::None,
398        }
399    }
400}
401
402impl PartialEq for ThreadCallback {
403    fn eq(&self, other: &Self) -> bool {
404        std::ptr::eq(self.cb as *const (), other.cb as *const ())
405    }
406}
407
408impl Eq for ThreadCallback {}
409
410impl PartialOrd for ThreadCallback {
411    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
412        Some(self.cmp(other))
413    }
414}
415
416impl Ord for ThreadCallback {
417    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
418        (self.cb as *const () as usize).cmp(&(other.cb as *const () as usize))
419    }
420}
421
422impl core::hash::Hash for ThreadCallback {
423    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
424        (self.cb as *const () as usize).hash(state);
425    }
426}
427
428// Host-invoker plumbing for ThreadCallback. NOTE: this callback fires
429// on a worker thread (spawned by `Thread::create`), not the main
430// `App.run` thread. The per-language host-invoker thunk MUST acquire
431// the host VM lock before dispatching to user code:
432//   * CPython: PyGILState_Ensure / _Release
433//   * MRI Ruby: rb_thread_call_with_gvl
434//   * OpenJDK: AttachCurrentThread / DetachCurrentThread
435//   * CLR / .NET: nothing ([UnmanagedCallersOnly] auto-trampolines)
436//   * OCaml: caml_acquire_runtime_system / _release
437//   * Lua / Perl / PHP / Pharo: cannot be called from worker thread
438//     (single-threaded interpreter) — fall back to writeback-only
439//     pattern (Rust extern "C" cb on worker, host fn on main via
440//     WriteBackCallback).
441// See `scripts/BINDING_STRATEGY_PER_LANGUAGE.md` for the lock-acquire
442// table per VM.
443azul_core::impl_managed_callback! {
444    wrapper:        ThreadCallback,
445    info_ty:        ThreadSender,
446    return_ty:      (),
447    // unit default-return; written via Default::default() so clippy's unused_unit
448    // doesn't fire on a bare `()` in this macro-argument position.
449    default_ret:    Default::default(),
450    invoker_static: THREAD_CALLBACK_INVOKER,
451    invoker_ty:     AzThreadCallbackInvoker,
452    thunk_fn:       az_thread_callback_thunk,
453    setter_fn:      AzApp_setThreadCallbackInvoker,
454    from_handle_fn: AzThreadCallback_createFromHostHandle,
455    extra_args:     [receiver: ThreadReceiver],
456}
457
458/// Callback type for receiving messages from a background thread
459pub type LibraryReceiveThreadMsgCallbackType =
460    extern "C" fn(*const core::ffi::c_void) -> OptionThreadReceiveMsg;
461
462#[allow(missing_copy_implementations)] // C-ABI fn-ptr wrapper; Clone is macro-generated (impl_callback_traits!), so Copy would trip expl_impl_clone_on_copy
463#[repr(C)]
464pub struct LibraryReceiveThreadMsgCallback {
465    pub cb: LibraryReceiveThreadMsgCallbackType,
466}
467
468impl_callback_traits!(LibraryReceiveThreadMsgCallback);
469
470/// Callback type for the destructor that cleans up a `ThreadInner`
471pub type ThreadDestructorCallbackType = extern "C" fn(*mut ThreadInner);
472
473#[allow(missing_copy_implementations)] // C-ABI fn-ptr wrapper; Clone is macro-generated (impl_callback_traits!), so Copy would trip expl_impl_clone_on_copy
474#[repr(C)]
475pub struct ThreadDestructorCallback {
476    pub cb: ThreadDestructorCallbackType,
477}
478
479impl_callback_traits!(ThreadDestructorCallback);
480
481/// Wrapper around Thread because Thread needs to be clone-able
482#[derive(Debug)]
483#[repr(C)]
484pub struct Thread {
485    #[cfg(feature = "std")]
486    pub ptr: Box<Arc<Mutex<ThreadInner>>>,
487    #[cfg(not(feature = "std"))]
488    pub ptr: *const core::ffi::c_void,
489    pub run_destructor: bool,
490}
491
492impl Clone for Thread {
493    fn clone(&self) -> Self {
494        Self {
495            ptr: self.ptr.clone(),
496            run_destructor: true,
497        }
498    }
499}
500
501impl Drop for Thread {
502    fn drop(&mut self) {
503        self.run_destructor = false;
504    }
505}
506
507impl Thread {
508    #[cfg(feature = "std")]
509    #[must_use] pub fn new(ti: ThreadInner) -> Self {
510        Self {
511            ptr: Box::new(Arc::new(Mutex::new(ti))),
512            run_destructor: true,
513        }
514    }
515
516    #[cfg(not(feature = "std"))]
517    pub fn new(_ti: ThreadInner) -> Self {
518        Self {
519            ptr: core::ptr::null(),
520            run_destructor: false,
521        }
522    }
523
524    /// Creates a new thread that will execute the given callback function.
525    ///
526    /// # Arguments
527    /// * `thread_initialize_data` - Data passed to the callback when the thread starts
528    /// * `writeback_data` - Data that will be passed back when writeback messages are received
529    /// * `callback` - The callback to execute in the background thread
530    ///
531    /// # Returns
532    /// A new Thread handle that can be added to the event loop with `CallbackInfo::add_thread`
533    pub fn create<C: Into<ThreadCallback>>(
534        thread_initialize_data: RefAny,
535        writeback_data: RefAny,
536        callback: C,
537    ) -> Self {
538        create_thread_libstd(thread_initialize_data, writeback_data, callback.into())
539    }
540
541    /// Send a control message to the running worker. Interior-mutable (the worker
542    /// state is behind a `Mutex`), so this takes `&self` and is callable from a
543    /// callback that only holds `&Thread` via `CallbackInfo::get_thread`. Used to push
544    /// resize / seek / source-change messages to a persistent worker. Returns false
545    /// if the channel is closed (or always, on `no_std`).
546    #[cfg(feature = "std")]
547    #[must_use] pub fn send_message(&self, msg: ThreadSendMsg) -> bool {
548        self.ptr.lock().is_ok_and(|inner| inner.sender.send(msg).is_ok())
549    }
550    #[cfg(not(feature = "std"))]
551    pub fn send_message(&self, _msg: ThreadSendMsg) -> bool {
552        false
553    }
554
555    /// Clone the main→worker `Sender` so a holder without a `CallbackInfo` (e.g. a
556    /// dataset-merge callback) can message the running worker later — used for the
557    /// scrub/seek path, where the merge callback compares the old/new `VideoConfig`
558    /// and pushes a seek to the worker. `None` on `no_std`.
559    #[cfg(feature = "std")]
560    #[must_use] pub fn clone_sender(&self) -> Option<Sender<ThreadSendMsg>> {
561        self.ptr.lock().ok().map(|inner| (*inner.sender).clone())
562    }
563    #[cfg(not(feature = "std"))]
564    pub fn clone_sender(&self) -> Option<Sender<ThreadSendMsg>> {
565        None
566    }
567}
568
569/// A `Thread` is a separate thread that is owned by the framework.
570///
571/// In difference to a regular thread, you don't have to `await()` the result,
572/// you can just hand the Thread to the framework and it will automatically
573/// update the UI when the Thread is finished.
574#[derive(Debug)]
575#[repr(C)]
576pub struct ThreadInner {
577    #[cfg(feature = "std")]
578    pub thread_handle: Box<Option<JoinHandle<()>>>,
579    #[cfg(not(feature = "std"))]
580    pub thread_handle: *const core::ffi::c_void,
581
582    #[cfg(feature = "std")]
583    pub sender: Box<Sender<ThreadSendMsg>>,
584    #[cfg(not(feature = "std"))]
585    pub sender: *const core::ffi::c_void,
586
587    #[cfg(feature = "std")]
588    pub receiver: Box<Receiver<ThreadReceiveMsg>>,
589    #[cfg(not(feature = "std"))]
590    pub receiver: *const core::ffi::c_void,
591
592    #[cfg(feature = "std")]
593    pub dropcheck: Box<alloc::sync::Weak<()>>,
594    #[cfg(not(feature = "std"))]
595    pub dropcheck: *const core::ffi::c_void,
596
597    pub writeback_data: RefAny,
598    pub check_thread_finished_fn: CheckThreadFinishedCallback,
599    pub send_thread_msg_fn: LibrarySendThreadMsgCallback,
600    pub receive_thread_msg_fn: LibraryReceiveThreadMsgCallback,
601    pub thread_destructor_fn: ThreadDestructorCallback,
602}
603
604#[cfg(feature = "std")]
605impl ThreadInner {
606    /// Returns true if the Thread has been finished, false otherwise
607    #[must_use] pub fn is_finished(&self) -> bool {
608        (self.check_thread_finished_fn.cb)(
609            std::ptr::from_ref(self.dropcheck.as_ref()).cast::<core::ffi::c_void>()
610        )
611    }
612
613    /// Send a message to the thread
614    pub fn sender_send(&mut self, msg: ThreadSendMsg) -> bool {
615        (self.send_thread_msg_fn.cb)(
616            std::ptr::from_ref(self.sender.as_ref()).cast::<core::ffi::c_void>(),
617            msg,
618        )
619    }
620
621    /// Try to receive a message from the thread (non-blocking)
622    pub fn receiver_try_recv(&mut self) -> OptionThreadReceiveMsg {
623        (self.receive_thread_msg_fn.cb)(
624            std::ptr::from_ref(self.receiver.as_ref()).cast::<core::ffi::c_void>()
625        )
626    }
627}
628
629#[cfg(not(feature = "std"))]
630impl ThreadInner {
631    /// Returns true if the Thread has been finished, false otherwise
632    pub fn is_finished(&self) -> bool {
633        true
634    }
635
636    /// Send a message to the thread (no-op in no_std)
637    pub fn sender_send(&mut self, _msg: ThreadSendMsg) -> bool {
638        false
639    }
640
641    /// Try to receive a message from the thread (always returns None in no_std)
642    pub fn receiver_try_recv(&mut self) -> OptionThreadReceiveMsg {
643        None.into()
644    }
645}
646
647impl Drop for ThreadInner {
648    fn drop(&mut self) {
649        (self.thread_destructor_fn.cb)(self);
650    }
651}
652
653// Default callback implementations for std
654#[cfg(feature = "std")]
655extern "C" fn default_thread_destructor_fn(thread: *mut ThreadInner) {
656    let thread = unsafe { &mut *thread };
657
658    if let Some(thread_handle) = thread.thread_handle.take() {
659        drop(thread.sender.send(ThreadSendMsg::TerminateThread));
660        drop(thread_handle.join()); // ignore the result, don't panic
661    }
662}
663
664#[cfg(not(feature = "std"))]
665extern "C" fn default_thread_destructor_fn(_thread: *mut ThreadInner) {}
666
667#[cfg(feature = "std")]
668extern "C" fn library_send_thread_msg_fn(
669    sender: *const core::ffi::c_void,
670    msg: ThreadSendMsg,
671) -> bool {
672    unsafe { &*sender.cast::<Sender<ThreadSendMsg>>() }
673        .send(msg)
674        .is_ok()
675}
676
677#[cfg(not(feature = "std"))]
678extern "C" fn library_send_thread_msg_fn(
679    _sender: *const core::ffi::c_void,
680    _msg: ThreadSendMsg,
681) -> bool {
682    false
683}
684
685#[cfg(feature = "std")]
686extern "C" fn library_receive_thread_msg_fn(
687    receiver: *const core::ffi::c_void,
688) -> OptionThreadReceiveMsg {
689    unsafe { &*receiver.cast::<Receiver<ThreadReceiveMsg>>() }
690        .try_recv()
691        .ok()
692        .into()
693}
694
695#[cfg(not(feature = "std"))]
696extern "C" fn library_receive_thread_msg_fn(
697    _receiver: *const core::ffi::c_void,
698) -> OptionThreadReceiveMsg {
699    None.into()
700}
701
702#[cfg(feature = "std")]
703extern "C" fn default_send_thread_msg_fn(
704    sender: *const core::ffi::c_void,
705    msg: ThreadReceiveMsg,
706) -> bool {
707    unsafe { &*sender.cast::<Sender<ThreadReceiveMsg>>() }
708        .send(msg)
709        .is_ok()
710}
711
712#[cfg(not(feature = "std"))]
713extern "C" fn default_send_thread_msg_fn(
714    _sender: *const core::ffi::c_void,
715    _msg: ThreadReceiveMsg,
716) -> bool {
717    false
718}
719
720#[cfg(feature = "std")]
721extern "C" fn default_receive_thread_msg_fn(
722    receiver: *const core::ffi::c_void,
723) -> OptionThreadSendMsg {
724    unsafe { &*receiver.cast::<Receiver<ThreadSendMsg>>() }
725        .try_recv()
726        .ok()
727        .into()
728}
729
730#[cfg(not(feature = "std"))]
731extern "C" fn default_receive_thread_msg_fn(
732    _receiver: *const core::ffi::c_void,
733) -> OptionThreadSendMsg {
734    None.into()
735}
736
737#[cfg(feature = "std")]
738extern "C" fn default_check_thread_finished(dropcheck: *const core::ffi::c_void) -> bool {
739    let weak = unsafe { &*dropcheck.cast::<alloc::sync::Weak<()>>() };
740    weak.upgrade().is_none()
741}
742
743#[cfg(not(feature = "std"))]
744extern "C" fn default_check_thread_finished(_dropcheck: *const core::ffi::c_void) -> bool {
745    true
746}
747
748#[cfg(feature = "std")]
749const extern "C" fn thread_sender_drop(_: *mut ThreadSenderInner) {}
750
751#[cfg(not(feature = "std"))]
752extern "C" fn thread_sender_drop(_: *mut ThreadSenderInner) {}
753
754#[cfg(feature = "std")]
755const extern "C" fn thread_receiver_drop(_: *mut ThreadReceiverInner) {}
756
757#[cfg(not(feature = "std"))]
758extern "C" fn thread_receiver_drop(_: *mut ThreadReceiverInner) {}
759
760/// Function that creates a new Thread object
761pub type CreateThreadCallbackType = extern "C" fn(RefAny, RefAny, ThreadCallback) -> Thread;
762
763#[repr(C)]
764pub struct CreateThreadCallback {
765    pub cb: CreateThreadCallbackType,
766}
767
768impl_callback_traits!(CreateThreadCallback);
769impl Copy for CreateThreadCallback {}
770
771/// Create a new thread using the standard library
772#[cfg(feature = "std")]
773#[must_use] pub extern "C" fn create_thread_libstd(
774    thread_initialize_data: RefAny,
775    writeback_data: RefAny,
776    callback: ThreadCallback,
777) -> Thread {
778    let (sender_receiver, receiver_receiver) = channel::<ThreadReceiveMsg>();
779    let mut sender_receiver = ThreadSender::new(ThreadSenderInner {
780        ptr: Box::new(sender_receiver),
781        send_fn: ThreadSendCallback {
782            cb: default_send_thread_msg_fn,
783        },
784        destructor: ThreadSenderDestructorCallback {
785            cb: thread_sender_drop,
786        },
787    });
788    // Set the ctx from the callback for FFI
789    sender_receiver.ctx = callback.ctx.clone();
790
791    let (sender_sender, receiver_sender) = channel::<ThreadSendMsg>();
792    let mut receiver_sender = ThreadReceiver::new(ThreadReceiverInner {
793        ptr: Box::new(receiver_sender),
794        recv_fn: ThreadRecvCallback {
795            cb: default_receive_thread_msg_fn,
796        },
797        destructor: ThreadReceiverDestructorCallback {
798            cb: thread_receiver_drop,
799        },
800    });
801    // Set the ctx from the callback for FFI
802    receiver_sender.ctx = callback.ctx.clone();
803
804    let thread_check = Arc::new(());
805    let dropcheck = Arc::downgrade(&thread_check);
806
807    let thread_handle = Some(thread::spawn(move || {
808        // Keep thread_check alive for the entire duration of the thread
809        // by binding it to a named variable (not `_` which drops immediately)
810        let _thread_check_guard = thread_check;
811        (callback.cb)(thread_initialize_data, sender_receiver, receiver_sender);
812        // _thread_check_guard gets dropped here, signals that the thread has finished
813    }));
814
815    let thread_handle: Box<Option<JoinHandle<()>>> =
816        Box::new(thread_handle);
817    let sender: Box<Sender<ThreadSendMsg>> = Box::new(sender_sender);
818    let receiver: Box<Receiver<ThreadReceiveMsg>> =
819        Box::new(receiver_receiver);
820    let dropcheck: Box<alloc::sync::Weak<()>> = Box::new(dropcheck);
821
822    Thread::new(ThreadInner {
823        thread_handle,
824        sender,
825        receiver,
826        writeback_data,
827        dropcheck,
828        thread_destructor_fn: ThreadDestructorCallback {
829            cb: default_thread_destructor_fn,
830        },
831        check_thread_finished_fn: CheckThreadFinishedCallback {
832            cb: default_check_thread_finished,
833        },
834        send_thread_msg_fn: LibrarySendThreadMsgCallback {
835            cb: library_send_thread_msg_fn,
836        },
837        receive_thread_msg_fn: LibraryReceiveThreadMsgCallback {
838            cb: library_receive_thread_msg_fn,
839        },
840    })
841}
842
843#[cfg(not(feature = "std"))]
844pub extern "C" fn create_thread_libstd(
845    _thread_initialize_data: RefAny,
846    _writeback_data: RefAny,
847    _callback: ThreadCallback,
848) -> Thread {
849    Thread {
850        ptr: core::ptr::null(),
851        run_destructor: false,
852    }
853}
854
855#[cfg(test)]
856mod tests {
857    use super::*;
858
859    extern "C" fn test_writeback_callback(
860        _thread_data: RefAny,
861        _writeback_data: RefAny,
862        _callback_info: CallbackInfo,
863    ) -> Update {
864        Update::DoNothing
865    }
866
867    #[test]
868    fn test_writeback_callback_creation() {
869        let callback = WriteBackCallback::new(test_writeback_callback);
870        assert_eq!(callback.cb as *const () as usize, test_writeback_callback as *const () as usize);
871    }
872
873    #[test]
874    fn test_writeback_callback_clone() {
875        let callback = WriteBackCallback::new(test_writeback_callback);
876        let cloned = callback.clone();
877        assert_eq!(callback, cloned);
878    }
879}
880#[allow(variant_size_differences)] // repr(C,u8) FFI enum: boxing the large variant would change the C ABI (api.json bindings); size disparity accepted
881/// Optional Thread type for API compatibility
882#[derive(Debug, Clone)]
883#[repr(C, u8)]
884pub enum OptionThread {
885    None,
886    Some(Thread),
887}
888
889impl From<Option<Thread>> for OptionThread {
890    fn from(o: Option<Thread>) -> Self {
891        o.map_or_else(|| Self::None, Self::Some)
892    }
893}
894
895impl OptionThread {
896    #[must_use] pub fn into_option(self) -> Option<Thread> {
897        match self {
898            Self::None => None,
899            Self::Some(t) => Some(t),
900        }
901    }
902}
903
904// ============================================================================
905// Sleep utilities
906// ============================================================================
907
908/// Sleeps the current thread for the specified number of milliseconds.
909///
910/// This is a cross-platform utility that can be called from C/C++/Python.
911///
912/// # Arguments
913/// * `milliseconds` - Number of milliseconds to sleep
914#[cfg(feature = "std")]
915#[must_use] pub fn thread_sleep_ms(milliseconds: u64) -> azul_css::corety::EmptyStruct {
916    thread::sleep(std::time::Duration::from_millis(milliseconds));
917    azul_css::corety::EmptyStruct::new()
918}
919
920/// Sleeps the current thread for the specified number of milliseconds (no-op on no_std).
921#[cfg(not(feature = "std"))]
922pub fn thread_sleep_ms(_milliseconds: u64) -> azul_css::corety::EmptyStruct {
923    // No-op on no_std - can't sleep without OS
924    azul_css::corety::EmptyStruct::new()
925}
926
927/// Sleeps the current thread for the specified number of microseconds.
928///
929/// # Arguments
930/// * `microseconds` - Number of microseconds to sleep
931#[cfg(feature = "std")]
932#[must_use] pub fn thread_sleep_us(microseconds: u64) -> azul_css::corety::EmptyStruct {
933    thread::sleep(std::time::Duration::from_micros(microseconds));
934    azul_css::corety::EmptyStruct::new()
935}
936
937/// Sleeps the current thread for the specified number of microseconds (no-op on no_std).
938#[cfg(not(feature = "std"))]
939pub fn thread_sleep_us(_microseconds: u64) -> azul_css::corety::EmptyStruct {
940    // No-op on no_std - can't sleep without OS
941    azul_css::corety::EmptyStruct::new()
942}
943
944/// Sleeps the current thread for the specified number of nanoseconds.
945///
946/// # Arguments
947/// * `nanoseconds` - Number of nanoseconds to sleep
948#[cfg(feature = "std")]
949#[must_use] pub fn thread_sleep_ns(nanoseconds: u64) -> azul_css::corety::EmptyStruct {
950    thread::sleep(std::time::Duration::from_nanos(nanoseconds));
951    azul_css::corety::EmptyStruct::new()
952}
953
954/// Sleeps the current thread for the specified number of nanoseconds (no-op on no_std).
955#[cfg(not(feature = "std"))]
956pub fn thread_sleep_ns(_nanoseconds: u64) -> azul_css::corety::EmptyStruct {
957    // No-op on no_std - can't sleep without OS
958    azul_css::corety::EmptyStruct::new()
959}