summaryrefslogtreecommitdiff
path: root/crates/control/src/lib.rs
blob: f0226f9e06ccd622a175e525266b97422e97f4f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
//! 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.
#![deny(clippy::all, clippy::pedantic, clippy::nursery)]
#![allow(clippy::must_use_candidate, clippy::missing_errors_doc)]

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 {
    Todo,
    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
    /// # Panics
    /// If there is an error spawning the control thread
    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,
                Message::Todo => todo!(),
            }
        }
    }
}