use parking_lot::Mutex;
use rustyline::ExternalPrinter;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PrintResult {
Sent,
NoPrinter,
Dropped,
}
struct NonBlockingPrinter {
tx: std::sync::mpsc::SyncSender<String>,
}
impl NonBlockingPrinter {
const CHANNEL_CAPACITY: usize = 500;
fn new(mut printer: Box<dyn ExternalPrinter + Send>) -> Self {
let (tx, rx) = std::sync::mpsc::sync_channel::<String>(Self::CHANNEL_CAPACITY);
if let Err(e) = std::thread::Builder::new()
.name("orcs-printer-drain".into())
.spawn(move || {
while let Ok(msg) = rx.recv() {
let _ = printer.print(msg);
}
})
{
eprintln!("warning: failed to spawn printer drain thread: {e}");
}
Self { tx }
}
fn try_print(&self, msg: String) -> bool {
self.tx.try_send(msg).is_ok()
}
}
#[derive(Clone)]
pub struct SharedPrinterSlot {
inner: Arc<Mutex<Option<NonBlockingPrinter>>>,
}
impl SharedPrinterSlot {
#[must_use]
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(None)),
}
}
pub fn set(&self, printer: Box<dyn ExternalPrinter + Send>) {
*self.inner.lock() = Some(NonBlockingPrinter::new(printer));
}
pub fn clear(&self) {
*self.inner.lock() = None;
}
pub fn try_print(&self, msg: String) -> PrintResult {
let guard = self.inner.lock();
if let Some(ref printer) = *guard {
if printer.try_print(msg) {
PrintResult::Sent
} else {
PrintResult::Dropped
}
} else {
PrintResult::NoPrinter
}
}
}
impl Default for SharedPrinterSlot {
fn default() -> Self {
Self::new()
}
}