1#[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 #[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#[allow(variant_size_differences)] #[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)] #[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#[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#[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 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 #[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#[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
244pub type ThreadSendCallbackType = extern "C" fn(*const core::ffi::c_void, ThreadReceiveMsg) -> bool;
246
247#[allow(missing_copy_implementations)] #[repr(C)]
249pub struct ThreadSendCallback {
250 pub cb: ThreadSendCallbackType,
251}
252
253impl_callback_traits!(ThreadSendCallback);
254
255pub type ThreadSenderDestructorCallbackType = extern "C" fn(*mut ThreadSenderInner);
257
258#[allow(missing_copy_implementations)] #[repr(C)]
260pub struct ThreadSenderDestructorCallback {
261 pub cb: ThreadSenderDestructorCallbackType,
262}
263
264impl_callback_traits!(ThreadSenderDestructorCallback);
265
266pub type WriteBackCallbackType = extern "C" fn(
273 RefAny,
274 RefAny,
275 CallbackInfo,
276) -> Update;
277
278#[repr(C)]
280pub struct WriteBackCallback {
281 pub cb: WriteBackCallbackType,
282 pub ctx: OptionRefAny,
285}
286
287impl WriteBackCallback {
288 pub fn new(cb: WriteBackCallbackType) -> Self {
290 Self {
291 cb,
292 ctx: OptionRefAny::None,
293 }
294 }
295
296 #[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
357pub type ThreadCallbackType = extern "C" fn(RefAny, ThreadSender, ThreadReceiver);
359
360#[repr(C)]
361pub struct ThreadCallback {
362 pub cb: ThreadCallbackType,
363 pub ctx: OptionRefAny,
366}
367
368impl ThreadCallback {
369 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
428azul_core::impl_managed_callback! {
444 wrapper: ThreadCallback,
445 info_ty: ThreadSender,
446 return_ty: (),
447 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
458pub type LibraryReceiveThreadMsgCallbackType =
460 extern "C" fn(*const core::ffi::c_void) -> OptionThreadReceiveMsg;
461
462#[allow(missing_copy_implementations)] #[repr(C)]
464pub struct LibraryReceiveThreadMsgCallback {
465 pub cb: LibraryReceiveThreadMsgCallbackType,
466}
467
468impl_callback_traits!(LibraryReceiveThreadMsgCallback);
469
470pub type ThreadDestructorCallbackType = extern "C" fn(*mut ThreadInner);
472
473#[allow(missing_copy_implementations)] #[repr(C)]
475pub struct ThreadDestructorCallback {
476 pub cb: ThreadDestructorCallbackType,
477}
478
479impl_callback_traits!(ThreadDestructorCallback);
480
481#[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 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 #[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 #[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#[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 #[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 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 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 pub fn is_finished(&self) -> bool {
633 true
634 }
635
636 pub fn sender_send(&mut self, _msg: ThreadSendMsg) -> bool {
638 false
639 }
640
641 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#[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()); }
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
760pub 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#[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 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 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 let _thread_check_guard = thread_check;
811 (callback.cb)(thread_initialize_data, sender_receiver, receiver_sender);
812 }));
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)] #[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#[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#[cfg(not(feature = "std"))]
922pub fn thread_sleep_ms(_milliseconds: u64) -> azul_css::corety::EmptyStruct {
923 azul_css::corety::EmptyStruct::new()
925}
926
927#[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#[cfg(not(feature = "std"))]
939pub fn thread_sleep_us(_microseconds: u64) -> azul_css::corety::EmptyStruct {
940 azul_css::corety::EmptyStruct::new()
942}
943
944#[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#[cfg(not(feature = "std"))]
956pub fn thread_sleep_ns(_nanoseconds: u64) -> azul_css::corety::EmptyStruct {
957 azul_css::corety::EmptyStruct::new()
959}
960
961#[cfg(all(test, feature = "std"))]
966#[allow(clippy::too_many_lines, clippy::unreadable_literal)]
967mod autotest_generated {
968 use core::{
969 hash::{Hash, Hasher},
970 sync::atomic::{AtomicBool, AtomicUsize, Ordering as AtomicOrd},
971 };
972 use std::{
973 collections::{hash_map::DefaultHasher, BTreeMap},
974 sync::{Arc, Mutex},
975 time::Instant as StdInstant,
976 };
977
978 use azul_core::{
979 dom::{DomId, DomNodeId},
980 geom::OptionLogicalPosition,
981 gl::OptionGlContextPtr,
982 hit_test::ScrollPosition,
983 resources::RendererResources,
984 styled_dom::NodeHierarchyItemId,
985 window::{MonitorVec, RawWindowHandle},
986 };
987 use azul_css::{corety::EmptyStruct, system::SystemStyle};
988 use rust_fontconfig::FcFontCache;
989
990 use super::*;
991 #[cfg(feature = "icu")]
992 use crate::icu::IcuLocalizerHandle;
993 use crate::{
994 callbacks::{CallbackChange, CallbackInfoRefData, ExternalSystemCallbacks},
995 window::LayoutWindow,
996 window_state::FullWindowState,
997 };
998
999 const MAX_WORKER_POLLS: usize = 10_000;
1007
1008 fn hash_of<T: Hash>(value: &T) -> u64 {
1009 let mut hasher = DefaultHasher::new();
1010 value.hash(&mut hasher);
1011 hasher.finish()
1012 }
1013
1014 fn make_sender() -> (Receiver<ThreadReceiveMsg>, ThreadSender) {
1016 let (tx, rx) = channel::<ThreadReceiveMsg>();
1017 let sender = ThreadSender::new(ThreadSenderInner {
1018 ptr: Box::new(tx),
1019 send_fn: ThreadSendCallback {
1020 cb: default_send_thread_msg_fn,
1021 },
1022 destructor: ThreadSenderDestructorCallback {
1023 cb: thread_sender_drop,
1024 },
1025 });
1026 (rx, sender)
1027 }
1028
1029 fn drain(inner: &mut ThreadInner) -> Vec<ThreadReceiveMsg> {
1031 let mut out = Vec::new();
1032 while let OptionThreadReceiveMsg::Some(msg) = inner.receiver_try_recv() {
1033 out.push(msg);
1034 }
1035 out
1036 }
1037
1038 fn join_worker(t: &Thread) {
1041 let mut guard = t.ptr.lock().expect("thread mutex must not be poisoned");
1042 default_thread_destructor_fn(core::ptr::from_mut::<ThreadInner>(&mut guard));
1043 }
1044
1045 fn with_callback_info<R>(f: impl FnOnce(CallbackInfo) -> R) -> R {
1048 let layout_window =
1049 LayoutWindow::new(FcFontCache::default()).expect("LayoutWindow::new failed");
1050 let renderer_resources = RendererResources::default();
1051 let previous_window_state: Option<FullWindowState> = None;
1052 let current_window_state = FullWindowState::default();
1053 let gl_context = OptionGlContextPtr::None;
1054 let scroll_states: BTreeMap<DomId, BTreeMap<NodeHierarchyItemId, ScrollPosition>> =
1055 BTreeMap::new();
1056 let window_handle = RawWindowHandle::Unsupported;
1057 let system_callbacks = ExternalSystemCallbacks::rust_internal();
1058
1059 let ref_data = CallbackInfoRefData {
1060 layout_window: &layout_window,
1061 renderer_resources: &renderer_resources,
1062 previous_window_state: &previous_window_state,
1063 current_window_state: ¤t_window_state,
1064 gl_context: &gl_context,
1065 current_scroll_manager: &scroll_states,
1066 current_window_handle: &window_handle,
1067 system_callbacks: &system_callbacks,
1068 system_style: Arc::new(SystemStyle::default()),
1069 monitors: Arc::new(Mutex::new(MonitorVec::from_const_slice(&[]))),
1070 #[cfg(feature = "icu")]
1071 icu_localizer: IcuLocalizerHandle::default(),
1072 ctx: OptionRefAny::None,
1073 };
1074 let changes: Arc<Mutex<Vec<CallbackChange>>> = Arc::new(Mutex::new(Vec::new()));
1075
1076 let info = CallbackInfo::new(
1077 &ref_data,
1078 &changes,
1079 DomNodeId {
1080 dom: DomId::ROOT_ID,
1081 node: NodeHierarchyItemId::NONE,
1082 },
1083 OptionLogicalPosition::None,
1084 OptionLogicalPosition::None,
1085 );
1086 f(info)
1087 }
1088
1089 static WB_THREAD_DATA: AtomicUsize = AtomicUsize::new(0);
1094 static WB_WRITEBACK_DATA: AtomicUsize = AtomicUsize::new(0);
1095
1096 extern "C" fn wb_record(
1097 mut thread_data: RefAny,
1098 mut writeback_data: RefAny,
1099 _callback_info: CallbackInfo,
1100 ) -> Update {
1101 if let Some(v) = thread_data.downcast_ref::<usize>() {
1102 WB_THREAD_DATA.store(*v, AtomicOrd::SeqCst);
1103 }
1104 if let Some(v) = writeback_data.downcast_ref::<usize>() {
1105 WB_WRITEBACK_DATA.store(*v, AtomicOrd::SeqCst);
1106 }
1107 Update::RefreshDomAllWindows
1108 }
1109
1110 extern "C" fn wb_do_nothing(
1111 _thread_data: RefAny,
1112 _writeback_data: RefAny,
1113 _callback_info: CallbackInfo,
1114 ) -> Update {
1115 Update::DoNothing
1116 }
1117
1118 extern "C" fn worker_quiet(_d: RefAny, _s: ThreadSender, _r: ThreadReceiver) {}
1120
1121 extern "C" fn worker_send_update(_d: RefAny, mut sender: ThreadSender, _r: ThreadReceiver) {
1123 let _sent = sender.send(ThreadReceiveMsg::Update(Update::RefreshDom));
1124 }
1125
1126 extern "C" fn worker_send_writeback(_d: RefAny, mut sender: ThreadSender, _r: ThreadReceiver) {
1128 let _sent = sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
1129 wb_do_nothing as WriteBackCallbackType,
1130 RefAny::new(77_usize),
1131 )));
1132 }
1133
1134 macro_rules! terminating_worker {
1137 ($fn_name:ident, $ticks:ident, $terminated:ident) => {
1138 static $ticks: AtomicUsize = AtomicUsize::new(0);
1139 static $terminated: AtomicBool = AtomicBool::new(false);
1140
1141 extern "C" fn $fn_name(
1142 _d: RefAny,
1143 mut sender: ThreadSender,
1144 mut receiver: ThreadReceiver,
1145 ) {
1146 for _ in 0..MAX_WORKER_POLLS {
1147 match receiver.recv() {
1148 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread) => {
1149 $terminated.store(true, AtomicOrd::SeqCst);
1150 break;
1151 }
1152 OptionThreadSendMsg::Some(ThreadSendMsg::Tick) => {
1153 $ticks.fetch_add(1, AtomicOrd::SeqCst);
1154 let _sent = sender.send(ThreadReceiveMsg::Update(Update::RefreshDom));
1155 }
1156 OptionThreadSendMsg::Some(ThreadSendMsg::Custom(_)) => {
1157 $ticks.fetch_add(1, AtomicOrd::SeqCst);
1158 }
1159 OptionThreadSendMsg::None => {
1160 let _slept = thread_sleep_ms(1);
1161 }
1162 }
1163 }
1164 }
1165 };
1166 }
1167
1168 terminating_worker!(worker_wait_a, WAIT_A_TICKS, WAIT_A_TERMINATED);
1169 terminating_worker!(worker_wait_b, WAIT_B_TICKS, WAIT_B_TERMINATED);
1170 terminating_worker!(worker_wait_c, WAIT_C_TICKS, WAIT_C_TERMINATED);
1171 terminating_worker!(worker_wait_d, WAIT_D_TICKS, WAIT_D_TERMINATED);
1172 terminating_worker!(worker_wait_e, WAIT_E_TICKS, WAIT_E_TERMINATED);
1173
1174 #[test]
1179 fn option_thread_receive_msg_none_into_option_is_none() {
1180 assert!(OptionThreadReceiveMsg::None.into_option().is_none());
1181 assert!(OptionThreadReceiveMsg::None.as_ref().is_none());
1182 }
1183
1184 #[test]
1185 fn option_thread_receive_msg_round_trips_through_from_and_into_option() {
1186 for update in [
1188 Update::DoNothing,
1189 Update::RefreshDom,
1190 Update::RefreshDomAllWindows,
1191 ] {
1192 let msg = ThreadReceiveMsg::Update(update);
1193 let ffi: OptionThreadReceiveMsg = Some(msg.clone()).into();
1194 assert_eq!(ffi.into_option(), Some(msg));
1195 }
1196 let empty: OptionThreadReceiveMsg = None.into();
1197 assert_eq!(empty, OptionThreadReceiveMsg::None);
1198 assert!(empty.into_option().is_none());
1199 }
1200
1201 #[test]
1202 fn option_thread_receive_msg_as_ref_does_not_consume() {
1203 let opt = OptionThreadReceiveMsg::Some(ThreadReceiveMsg::Update(Update::RefreshDom));
1204 for _ in 0..3 {
1206 assert_eq!(
1207 opt.as_ref(),
1208 Some(&ThreadReceiveMsg::Update(Update::RefreshDom))
1209 );
1210 }
1211 assert_eq!(
1213 opt.into_option(),
1214 Some(ThreadReceiveMsg::Update(Update::RefreshDom))
1215 );
1216 }
1217
1218 #[test]
1219 fn option_thread_receive_msg_as_ref_handles_writeback_variant() {
1220 let opt = OptionThreadReceiveMsg::Some(ThreadReceiveMsg::WriteBack(
1221 ThreadWriteBackMsg::new(
1222 wb_do_nothing as WriteBackCallbackType,
1223 RefAny::new(1_usize),
1224 ),
1225 ));
1226 let Some(ThreadReceiveMsg::WriteBack(inner)) = opt.as_ref() else {
1227 panic!("as_ref() must expose the WriteBack payload");
1228 };
1229 assert_eq!(
1230 inner.callback.cb as *const () as usize,
1231 wb_do_nothing as *const () as usize
1232 );
1233 assert!(opt.into_option().is_some());
1234 }
1235
1236 #[test]
1237 fn option_thread_receive_msg_ord_and_hash_are_consistent() {
1238 let none = OptionThreadReceiveMsg::None;
1239 let some = OptionThreadReceiveMsg::Some(ThreadReceiveMsg::Update(Update::DoNothing));
1240 assert!(none < some);
1242 assert_eq!(none.cmp(&none), core::cmp::Ordering::Equal);
1243 assert_eq!(hash_of(&none), hash_of(&OptionThreadReceiveMsg::None));
1245 assert_eq!(
1246 hash_of(&some),
1247 hash_of(&OptionThreadReceiveMsg::Some(ThreadReceiveMsg::Update(
1248 Update::DoNothing
1249 )))
1250 );
1251 }
1252
1253 #[test]
1254 fn thread_receive_msg_orders_writeback_before_update() {
1255 let wb = ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
1256 wb_do_nothing as WriteBackCallbackType,
1257 RefAny::new(0_usize),
1258 ));
1259 let up = ThreadReceiveMsg::Update(Update::DoNothing);
1260 assert!(wb < up, "variant order (WriteBack=0, Update=1) must decide");
1261 assert!(
1262 ThreadReceiveMsg::Update(Update::DoNothing)
1263 < ThreadReceiveMsg::Update(Update::RefreshDom)
1264 );
1265 }
1266
1267 #[test]
1272 fn thread_write_back_msg_new_stores_both_fields() {
1273 let mut msg = ThreadWriteBackMsg::new(
1274 wb_do_nothing as WriteBackCallbackType,
1275 RefAny::new(0xDEAD_BEEF_usize),
1276 );
1277 assert_eq!(
1278 msg.callback.cb as *const () as usize,
1279 wb_do_nothing as *const () as usize
1280 );
1281 assert_eq!(
1282 msg.refany
1283 .downcast_ref::<usize>()
1284 .map(|v| *v)
1285 .expect("payload type must survive construction"),
1286 0xDEAD_BEEF_usize
1287 );
1288 assert_eq!(msg.callback.ctx, OptionRefAny::None);
1290 }
1291
1292 #[test]
1293 fn thread_write_back_msg_new_accepts_both_into_impls() {
1294 let from_fn = ThreadWriteBackMsg::new(
1297 wb_record as WriteBackCallbackType,
1298 RefAny::new(1_usize),
1299 );
1300 let from_struct =
1301 ThreadWriteBackMsg::new(WriteBackCallback::new(wb_record), RefAny::new(1_usize));
1302 assert_eq!(from_fn.callback, from_struct.callback);
1303 }
1304
1305 #[test]
1306 fn thread_write_back_msg_clone_shares_payload_and_compares_equal() {
1307 let msg = ThreadWriteBackMsg::new(
1314 wb_do_nothing as WriteBackCallbackType,
1315 RefAny::new(5_usize),
1316 );
1317 let mut cloned = msg.clone();
1318
1319 assert_eq!(msg.callback, cloned.callback);
1321 assert_eq!(
1322 cloned.refany.downcast_ref::<usize>().map(|v| *v),
1323 Some(5_usize)
1324 );
1325 assert_eq!(msg, cloned);
1327 assert_eq!(msg.clone(), msg.clone());
1329 }
1330
1331 #[test]
1336 fn writeback_callback_new_has_no_ctx_and_matches_fn_ptr() {
1337 let cb = WriteBackCallback::new(wb_record);
1338 assert_eq!(cb.ctx, OptionRefAny::None);
1339 assert_eq!(cb.cb as *const () as usize, wb_record as *const () as usize);
1340 assert_eq!(cb, WriteBackCallback::from(wb_record as WriteBackCallbackType));
1342 }
1343
1344 #[test]
1345 fn writeback_callback_eq_ord_hash_key_off_the_fn_pointer_only() {
1346 let a = WriteBackCallback::new(wb_record);
1347 let b = WriteBackCallback::new(wb_record);
1348 let c = WriteBackCallback::new(wb_do_nothing);
1349
1350 assert_eq!(a, b);
1351 assert_eq!(hash_of(&a), hash_of(&b));
1352 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1353
1354 assert_ne!(a, c);
1355 assert!((a < c) ^ (c < a));
1357 assert_eq!(a.partial_cmp(&c), Some(a.cmp(&c)));
1358
1359 assert_eq!(a, a.clone());
1361 assert_eq!(hash_of(&a), hash_of(&a.clone()));
1362 }
1363
1364 #[test]
1365 fn writeback_callback_debug_does_not_panic() {
1366 let s = format!("{:?}", WriteBackCallback::new(wb_record));
1367 assert!(s.starts_with("WriteBackCallback {"), "got {s}");
1368 }
1369
1370 #[test]
1371 fn writeback_callback_invoke_forwards_args_and_returns_callback_update() {
1372 WB_THREAD_DATA.store(0, AtomicOrd::SeqCst);
1373 WB_WRITEBACK_DATA.store(0, AtomicOrd::SeqCst);
1374
1375 let cb = WriteBackCallback::new(wb_record);
1376 let update = with_callback_info(|info| {
1377 cb.invoke(RefAny::new(11_usize), RefAny::new(22_usize), info)
1378 });
1379
1380 assert_eq!(update, Update::RefreshDomAllWindows);
1382 assert_eq!(WB_THREAD_DATA.load(AtomicOrd::SeqCst), 11);
1384 assert_eq!(WB_WRITEBACK_DATA.load(AtomicOrd::SeqCst), 22);
1385 }
1386
1387 #[test]
1388 fn writeback_callback_invoke_is_repeatable() {
1389 let cb = WriteBackCallback::new(wb_do_nothing);
1390 with_callback_info(|info| {
1391 for _ in 0..4 {
1393 assert_eq!(
1394 cb.invoke(RefAny::new(0_usize), RefAny::new(0_usize), info),
1395 Update::DoNothing
1396 );
1397 }
1398 });
1399 }
1400
1401 #[test]
1402 fn thread_callback_new_has_no_ctx_and_orders_by_fn_ptr() {
1403 let a = ThreadCallback::new(worker_quiet);
1404 let b = ThreadCallback::from(worker_quiet as ThreadCallbackType);
1405 let c = ThreadCallback::new(worker_send_update);
1406
1407 assert_eq!(a.ctx, OptionRefAny::None);
1408 assert_eq!(a, b);
1409 assert_eq!(hash_of(&a), hash_of(&b));
1410 assert_ne!(a, c);
1411 assert!((a < c) ^ (c < a));
1412 assert_eq!(a, a.clone());
1413 assert!(format!("{a:?}").starts_with("ThreadCallback {"));
1414 }
1415
1416 #[test]
1417 fn thread_send_callback_wrapper_traits_are_consistent() {
1418 let a = ThreadSendCallback {
1420 cb: default_send_thread_msg_fn,
1421 };
1422 let b = a.clone();
1423 assert_eq!(a, b);
1424 assert_eq!(hash_of(&a), hash_of(&b));
1425 assert_eq!(a.cmp(&b), core::cmp::Ordering::Equal);
1426 assert!(format!("{a:?}").starts_with("ThreadSendCallback {"));
1427 }
1428
1429 #[test]
1434 fn thread_sender_send_delivers_the_exact_message() {
1435 let (rx, mut sender) = make_sender();
1436 assert!(sender.send(ThreadReceiveMsg::Update(Update::RefreshDom)));
1437 assert_eq!(
1438 rx.try_recv().ok(),
1439 Some(ThreadReceiveMsg::Update(Update::RefreshDom))
1440 );
1441 assert!(rx.try_recv().is_err(), "channel must now be empty");
1442 }
1443
1444 #[test]
1445 fn thread_sender_send_returns_false_when_receiver_is_gone() {
1446 let (rx, mut sender) = make_sender();
1447 drop(rx);
1448 assert!(!sender.send(ThreadReceiveMsg::Update(Update::RefreshDom)));
1450 assert!(!sender.send(ThreadReceiveMsg::WriteBack(ThreadWriteBackMsg::new(
1451 wb_do_nothing as WriteBackCallbackType,
1452 RefAny::new(0_usize),
1453 ))));
1454 }
1455
1456 #[test]
1457 fn thread_sender_send_survives_a_poisoned_mutex() {
1458 let (rx, mut sender) = make_sender();
1459 let arc = Arc::clone(&*sender.ptr);
1460 let handle = std::thread::spawn(move || {
1461 let _guard = arc.lock().expect("mutex is fresh here");
1462 panic!("intentional poison");
1463 });
1464 assert!(handle.join().is_err(), "helper thread must have panicked");
1465
1466 assert!(!sender.send(ThreadReceiveMsg::Update(Update::RefreshDom)));
1469 assert!(rx.try_recv().is_err());
1471 }
1472
1473 #[test]
1474 fn thread_sender_get_ctx_is_none_by_default_and_clones_the_payload() {
1475 let (_rx, mut sender) = make_sender();
1476 assert_eq!(sender.get_ctx(), OptionRefAny::None);
1477
1478 sender.ctx = OptionRefAny::Some(RefAny::new(9_usize));
1479 let OptionRefAny::Some(mut ctx) = sender.get_ctx() else {
1480 panic!("get_ctx must hand back the ctx that was set");
1481 };
1482 assert_eq!(ctx.downcast_ref::<usize>().map(|v| *v), Some(9_usize));
1483 assert!(matches!(sender.get_ctx(), OptionRefAny::Some(_)));
1485 }
1486
1487 #[test]
1488 fn thread_sender_clone_shares_the_underlying_channel() {
1489 let (rx, mut sender) = make_sender();
1490 sender.ctx = OptionRefAny::Some(RefAny::new(3_usize));
1491 let mut cloned = sender.clone();
1492
1493 assert!(sender.send(ThreadReceiveMsg::Update(Update::DoNothing)));
1494 assert!(cloned.send(ThreadReceiveMsg::Update(Update::RefreshDom)));
1495
1496 assert_eq!(
1498 rx.try_recv().ok(),
1499 Some(ThreadReceiveMsg::Update(Update::DoNothing))
1500 );
1501 assert_eq!(
1502 rx.try_recv().ok(),
1503 Some(ThreadReceiveMsg::Update(Update::RefreshDom))
1504 );
1505 assert!(matches!(cloned.get_ctx(), OptionRefAny::Some(_)));
1507 }
1508
1509 #[test]
1514 fn default_send_thread_msg_fn_reports_disconnect_instead_of_panicking() {
1515 let (tx, rx) = channel::<ThreadReceiveMsg>();
1516 let tx_ptr = core::ptr::from_ref::<Sender<ThreadReceiveMsg>>(&tx).cast::<core::ffi::c_void>();
1517
1518 assert!(default_send_thread_msg_fn(
1519 tx_ptr,
1520 ThreadReceiveMsg::Update(Update::RefreshDom)
1521 ));
1522 assert_eq!(
1523 rx.try_recv().ok(),
1524 Some(ThreadReceiveMsg::Update(Update::RefreshDom))
1525 );
1526
1527 drop(rx);
1528 assert!(!default_send_thread_msg_fn(
1529 tx_ptr,
1530 ThreadReceiveMsg::Update(Update::RefreshDom)
1531 ));
1532 }
1533
1534 #[test]
1535 fn library_send_thread_msg_fn_reports_disconnect_instead_of_panicking() {
1536 let (tx, rx) = channel::<ThreadSendMsg>();
1537 let tx_ptr = core::ptr::from_ref::<Sender<ThreadSendMsg>>(&tx).cast::<core::ffi::c_void>();
1538
1539 assert!(library_send_thread_msg_fn(tx_ptr, ThreadSendMsg::Tick));
1540 assert!(library_send_thread_msg_fn(
1541 tx_ptr,
1542 ThreadSendMsg::Custom(RefAny::new(4_usize))
1543 ));
1544 assert_eq!(rx.try_recv().ok(), Some(ThreadSendMsg::Tick));
1545 assert!(matches!(rx.try_recv(), Ok(ThreadSendMsg::Custom(_))));
1546
1547 drop(rx);
1548 assert!(!library_send_thread_msg_fn(
1549 tx_ptr,
1550 ThreadSendMsg::TerminateThread
1551 ));
1552 }
1553
1554 #[test]
1555 fn library_receive_thread_msg_fn_is_non_blocking_on_empty_and_disconnected() {
1556 let (tx, rx) = channel::<ThreadReceiveMsg>();
1557 let rx_ptr =
1558 core::ptr::from_ref::<Receiver<ThreadReceiveMsg>>(&rx).cast::<core::ffi::c_void>();
1559
1560 assert_eq!(library_receive_thread_msg_fn(rx_ptr), OptionThreadReceiveMsg::None);
1562
1563 tx.send(ThreadReceiveMsg::Update(Update::RefreshDomAllWindows))
1564 .expect("receiver is alive");
1565 assert_eq!(
1566 library_receive_thread_msg_fn(rx_ptr),
1567 OptionThreadReceiveMsg::Some(ThreadReceiveMsg::Update(Update::RefreshDomAllWindows))
1568 );
1569
1570 drop(tx);
1572 assert_eq!(library_receive_thread_msg_fn(rx_ptr), OptionThreadReceiveMsg::None);
1573 assert_eq!(library_receive_thread_msg_fn(rx_ptr), OptionThreadReceiveMsg::None);
1574 }
1575
1576 #[test]
1577 fn default_receive_thread_msg_fn_is_non_blocking_on_empty_and_disconnected() {
1578 let (tx, rx) = channel::<ThreadSendMsg>();
1579 let rx_ptr = core::ptr::from_ref::<Receiver<ThreadSendMsg>>(&rx).cast::<core::ffi::c_void>();
1580
1581 assert_eq!(default_receive_thread_msg_fn(rx_ptr), OptionThreadSendMsg::None);
1582
1583 tx.send(ThreadSendMsg::TerminateThread)
1584 .expect("receiver is alive");
1585 assert_eq!(
1586 default_receive_thread_msg_fn(rx_ptr),
1587 OptionThreadSendMsg::Some(ThreadSendMsg::TerminateThread)
1588 );
1589
1590 drop(tx);
1591 assert_eq!(default_receive_thread_msg_fn(rx_ptr), OptionThreadSendMsg::None);
1592 }
1593
1594 #[test]
1595 fn default_check_thread_finished_tracks_the_dropcheck_arc() {
1596 let alive = Arc::new(());
1597 let weak = Arc::downgrade(&alive);
1598 let weak_ptr =
1599 core::ptr::from_ref::<alloc::sync::Weak<()>>(&weak).cast::<core::ffi::c_void>();
1600
1601 assert!(!default_check_thread_finished(weak_ptr));
1603 drop(alive);
1604 assert!(default_check_thread_finished(weak_ptr));
1606 assert!(default_check_thread_finished(weak_ptr));
1607 }
1608
1609 #[test]
1610 fn sender_and_receiver_drop_stubs_ignore_their_argument() {
1611 thread_sender_drop(core::ptr::null_mut::<ThreadSenderInner>());
1614 thread_receiver_drop(core::ptr::null_mut::<ThreadReceiverInner>());
1615 }
1616
1617 #[test]
1622 fn create_thread_libstd_runs_the_callback_and_delivers_its_message() {
1623 let t = create_thread_libstd(
1624 RefAny::new(0_usize),
1625 RefAny::new(0_usize),
1626 ThreadCallback::new(worker_send_update),
1627 );
1628 join_worker(&t);
1629
1630 let mut guard = t.ptr.lock().expect("not poisoned");
1631 assert!(
1632 guard.is_finished(),
1633 "after join the dropcheck Arc must be gone"
1634 );
1635 assert_eq!(
1636 drain(&mut guard),
1637 vec![ThreadReceiveMsg::Update(Update::RefreshDom)]
1638 );
1639 }
1640
1641 #[test]
1642 fn thread_create_delivers_a_writeback_message_intact() {
1643 let t = Thread::create(
1644 RefAny::new(0_usize),
1645 RefAny::new(0_usize),
1646 worker_send_writeback as ThreadCallbackType,
1647 );
1648 join_worker(&t);
1649
1650 let mut guard = t.ptr.lock().expect("not poisoned");
1651 let msgs = drain(&mut guard);
1652 assert_eq!(msgs.len(), 1);
1653 let ThreadReceiveMsg::WriteBack(wb) = &msgs[0] else {
1654 panic!("expected a WriteBack message, got {:?}", msgs[0]);
1655 };
1656 assert_eq!(
1657 wb.callback.cb as *const () as usize,
1658 wb_do_nothing as *const () as usize
1659 );
1660 let mut payload = wb.refany.clone();
1662 assert_eq!(payload.downcast_ref::<usize>().map(|v| *v), Some(77_usize));
1663 }
1664
1665 #[test]
1666 fn quiet_worker_leaves_the_receive_queue_empty() {
1667 let t = Thread::create(
1668 RefAny::new(0_usize),
1669 RefAny::new(0_usize),
1670 worker_quiet as ThreadCallbackType,
1671 );
1672 join_worker(&t);
1673
1674 let mut guard = t.ptr.lock().expect("not poisoned");
1675 assert!(drain(&mut guard).is_empty());
1677 assert_eq!(guard.receiver_try_recv(), OptionThreadReceiveMsg::None);
1678 }
1679
1680 #[test]
1681 fn thread_destructor_is_idempotent() {
1682 let t = create_thread_libstd(
1683 RefAny::new(0_usize),
1684 RefAny::new(0_usize),
1685 ThreadCallback::new(worker_send_update),
1686 );
1687 join_worker(&t);
1690 join_worker(&t);
1691 drop(t);
1693 }
1694
1695 #[test]
1696 fn thread_send_message_reaches_the_worker_in_order() {
1697 let t = Thread::create(
1698 RefAny::new(0_usize),
1699 RefAny::new(0_usize),
1700 worker_wait_a as ThreadCallbackType,
1701 );
1702
1703 for _ in 0..3 {
1705 assert!(t.send_message(ThreadSendMsg::Tick));
1706 }
1707 assert!(t.send_message(ThreadSendMsg::Custom(RefAny::new(1_usize))));
1708
1709 join_worker(&t); assert!(WAIT_A_TERMINATED.load(AtomicOrd::SeqCst));
1712 assert_eq!(WAIT_A_TICKS.load(AtomicOrd::SeqCst), 4);
1713
1714 let mut guard = t.ptr.lock().expect("not poisoned");
1715 assert!(guard.is_finished());
1716 assert_eq!(drain(&mut guard).len(), 3);
1718 }
1719
1720 #[test]
1721 fn thread_send_message_returns_false_once_the_worker_is_gone() {
1722 let t = Thread::create(
1723 RefAny::new(0_usize),
1724 RefAny::new(0_usize),
1725 worker_wait_b as ThreadCallbackType,
1726 );
1727 assert!(t.send_message(ThreadSendMsg::Tick));
1728
1729 join_worker(&t); assert!(WAIT_B_TERMINATED.load(AtomicOrd::SeqCst));
1732 assert!(!t.send_message(ThreadSendMsg::Tick));
1734 assert!(!t.send_message(ThreadSendMsg::TerminateThread));
1735 }
1736
1737 #[test]
1738 fn thread_is_finished_is_false_while_the_worker_is_alive() {
1739 let t = Thread::create(
1740 RefAny::new(0_usize),
1741 RefAny::new(0_usize),
1742 worker_wait_c as ThreadCallbackType,
1743 );
1744 {
1745 let guard = t.ptr.lock().expect("not poisoned");
1748 assert!(!guard.is_finished());
1749 }
1750 join_worker(&t);
1751 assert!(t.ptr.lock().expect("not poisoned").is_finished());
1752 assert!(WAIT_C_TERMINATED.load(AtomicOrd::SeqCst));
1753 }
1754
1755 #[test]
1756 fn thread_clone_sender_shares_the_worker_channel() {
1757 let t = Thread::create(
1758 RefAny::new(0_usize),
1759 RefAny::new(0_usize),
1760 worker_wait_d as ThreadCallbackType,
1761 );
1762 let sender = t.clone_sender().expect("std build must hand back a Sender");
1763 assert!(sender.send(ThreadSendMsg::Tick).is_ok());
1766 assert!(t.send_message(ThreadSendMsg::Tick));
1767 drop(sender);
1768
1769 join_worker(&t);
1770 assert!(WAIT_D_TERMINATED.load(AtomicOrd::SeqCst));
1771 assert_eq!(WAIT_D_TICKS.load(AtomicOrd::SeqCst), 2);
1772 }
1773
1774 #[test]
1775 fn thread_send_message_and_clone_sender_survive_a_poisoned_mutex() {
1776 let t = Thread::create(
1777 RefAny::new(0_usize),
1778 RefAny::new(0_usize),
1779 worker_wait_e as ThreadCallbackType,
1780 );
1781 let arc = Arc::clone(&*t.ptr);
1782 let handle = std::thread::spawn(move || {
1783 let _guard = arc.lock().expect("mutex is fresh here");
1784 panic!("intentional poison");
1785 });
1786 assert!(handle.join().is_err(), "helper thread must have panicked");
1787
1788 assert!(!t.send_message(ThreadSendMsg::Tick));
1791 assert!(t.clone_sender().is_none());
1792
1793 drop(t);
1796 assert!(WAIT_E_TERMINATED.load(AtomicOrd::SeqCst));
1797 }
1798
1799 #[test]
1800 fn thread_clone_shares_the_same_inner_state() {
1801 let t = create_thread_libstd(
1802 RefAny::new(0_usize),
1803 RefAny::new(0_usize),
1804 ThreadCallback::new(worker_quiet),
1805 );
1806 let cloned = t.clone();
1807 assert_eq!(Arc::strong_count(&*t.ptr), 2, "clone must be shallow");
1808 assert!(cloned.run_destructor);
1809
1810 join_worker(&t);
1811 assert!(cloned.ptr.lock().expect("not poisoned").is_finished());
1813 drop(cloned);
1814 drop(t);
1815 }
1816
1817 #[test]
1818 fn option_thread_into_option_round_trips() {
1819 assert!(OptionThread::None.into_option().is_none());
1820
1821 let t = create_thread_libstd(
1822 RefAny::new(0_usize),
1823 RefAny::new(0_usize),
1824 ThreadCallback::new(worker_quiet),
1825 );
1826 let opt: OptionThread = Some(t).into();
1827 let recovered = opt.into_option().expect("Some must round-trip to Some");
1828 join_worker(&recovered);
1829 assert!(recovered.ptr.lock().expect("not poisoned").is_finished());
1830 }
1831
1832 #[test]
1837 fn thread_sleep_zero_returns_immediately_for_every_unit() {
1838 let start = StdInstant::now();
1839 assert_eq!(thread_sleep_ms(0), EmptyStruct::new());
1840 assert_eq!(thread_sleep_us(0), EmptyStruct::new());
1841 assert_eq!(thread_sleep_ns(0), EmptyStruct::new());
1842 assert!(start.elapsed() < core::time::Duration::from_secs(5));
1844 assert_eq!(EmptyStruct::new()._reserved, 0);
1845 }
1846
1847 #[test]
1848 fn thread_sleep_sleeps_at_least_the_requested_duration() {
1849 let start = StdInstant::now();
1851 let _slept = thread_sleep_ms(5);
1852 assert!(start.elapsed() >= core::time::Duration::from_millis(5));
1853
1854 let start = StdInstant::now();
1855 let _slept = thread_sleep_us(5_000);
1856 assert!(start.elapsed() >= core::time::Duration::from_micros(5_000));
1857
1858 let start = StdInstant::now();
1859 let _slept = thread_sleep_ns(5_000_000);
1860 assert!(start.elapsed() >= core::time::Duration::from_nanos(5_000_000));
1861 }
1862
1863 #[test]
1864 fn thread_sleep_one_unit_does_not_panic() {
1865 let _ms = thread_sleep_ms(1);
1867 let _us = thread_sleep_us(1);
1868 let _ns = thread_sleep_ns(1);
1869 }
1870
1871 static MAX_SLEEP_ENTERED: AtomicBool = AtomicBool::new(false);
1872 static MAX_SLEEP_PANICKED: AtomicBool = AtomicBool::new(false);
1873
1874 #[test]
1875 fn thread_sleep_max_converts_without_overflow() {
1876 let _d_ms = core::time::Duration::from_millis(u64::MAX);
1879 let _d_us = core::time::Duration::from_micros(u64::MAX);
1880 let _d_ns = core::time::Duration::from_nanos(u64::MAX);
1881
1882 let _detached = std::thread::spawn(|| {
1886 MAX_SLEEP_ENTERED.store(true, AtomicOrd::SeqCst);
1887 if std::panic::catch_unwind(|| {
1888 let _slept = thread_sleep_ms(u64::MAX);
1889 })
1890 .is_err()
1891 {
1892 MAX_SLEEP_PANICKED.store(true, AtomicOrd::SeqCst);
1893 }
1894 });
1895
1896 for _ in 0..200 {
1897 if MAX_SLEEP_ENTERED.load(AtomicOrd::SeqCst) {
1898 break;
1899 }
1900 let _slept = thread_sleep_ms(10);
1901 }
1902 assert!(
1903 MAX_SLEEP_ENTERED.load(AtomicOrd::SeqCst),
1904 "detached sleeper never started"
1905 );
1906 assert!(
1907 !MAX_SLEEP_PANICKED.load(AtomicOrd::SeqCst),
1908 "thread_sleep_ms(u64::MAX) must not panic"
1909 );
1910 }
1911}