summaryrefslogtreecommitdiff
path: root/crates/control/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/control/src')
-rw-r--r--crates/control/src/lib.rs81
1 files changed, 81 insertions, 0 deletions
diff --git a/crates/control/src/lib.rs b/crates/control/src/lib.rs
new file mode 100644
index 0000000..5691cf2
--- /dev/null
+++ b/crates/control/src/lib.rs
@@ -0,0 +1,81 @@
+//! 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<Message>,
+ thread_handle: ManuallyDrop<JoinHandle<()>>,
+}
+
+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<Message>,
+}
+
+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,
+ }
+ }
+ }
+}