//! Printer control related code //! //! To ensure the volume of web requests doesn't mess with printing, communication with the printer is done //! in a separate thread, on its own async executor. //! //! The main server spawns this thread by creating a [`Handle`] and communicates by sending [messages](crate::Message) over an MPSC channel. use std::{ mem::ManuallyDrop, thread::{self, JoinHandle}, }; use tokio::{ runtime, sync::mpsc::{self, Receiver, Sender}, }; use tracing::info; /// The maximum number of messages to buffer before 'blocking' new ones /// This provides a form of backpressure const CHANNEL_CAPACITY: usize = 100; /// A control message sent from the API to the printer control thread pub enum Message { Quit, } /// A handle to the printer control thread, allowing for communication. pub struct Handle { cmd_send: Sender, thread_handle: ManuallyDrop>, } impl Handle { /// Spawn a printer control thread, returning a handle pub fn spawn() -> Self { let (send, recv) = mpsc::channel(CHANNEL_CAPACITY); let state = State { cmd_recv: recv }; let thread_handle = thread::spawn(move || { let rt = runtime::Builder::new_current_thread() .build() .expect("error spawning printer control thread"); rt.block_on(async move { state.entrypoint().await }); }); Self { cmd_send: send, thread_handle: ManuallyDrop::new(thread_handle), } } } impl Drop for Handle { fn drop(&mut self) { let _ = self.cmd_send.blocking_send(Message::Quit); unsafe { info!("waiting for printer thread to exit..."); ManuallyDrop::take(&mut self.thread_handle) .join() .expect("error joining printer thread") }; } } /// The state held by the printer control thread pub struct State { cmd_recv: Receiver, } impl State { /// The entrypoint of the printer control thread pub async fn entrypoint(mut self) { info!("listening for events"); while let Some(msg) = self.cmd_recv.recv().await { match msg { Message::Quit => break, } } } }