use std::borrow::Cow; use std::marker::PhantomData; use std::panic::catch_unwind; use std::sync::mpsc::{channel as mpsc_channel, Sender as MpscSender}; use std::sync::{Arc, OnceLock}; use std::thread::{Builder as ThreadBuilder, JoinHandle as ThreadJoinHandle}; pub struct Work { pub func: fn(UserData), pub user_data: UserData, } #[derive(Debug)] pub struct WorkQueue { work_sender: MpscSender>, thread_panic: Arc>>, _thread: ThreadJoinHandle<()>, _pd: PhantomData, } impl WorkQueue { pub fn new(name: &str) -> Self { let (work_sender, work_receiver) = mpsc_channel::>(); let thread_panic = Arc::new(OnceLock::new()); let thread_panic_b = thread_panic.clone(); Self { work_sender, thread_panic: thread_panic, _thread: ThreadBuilder::new() .name(name.to_string()) .spawn(move || { if let Err(panic_err) = catch_unwind(|| { while let Ok(work) = work_receiver.recv() { (work.func)(work.user_data); } }) { let panic_message: Cow<'static, str> = if let Some(panic_message) = panic_err.downcast_ref::<&'static str>() { (*panic_message).into() } else if let Some(panic_message) = panic_err.downcast_ref::() { panic_message.clone().into() } else { "(unknown panic payload type)".into() }; let _ = thread_panic_b .set(panic_message.into_owned().into_boxed_str()); } }) .expect("Failed to create work queue thread"), _pd: PhantomData, } } pub fn get_thread_panic(&self) -> Option<&str> { self.thread_panic.get().map(|thread_panic| &**thread_panic) } pub fn add_work(&self, work: Work) { if self.work_sender.send(work).is_err() { tracing::error!("Cannot add work to work queue. Work queue thread is dead"); } } }