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