use crate::poll::Subscribe;
use crate::stdio::StdinStream;
use crate::{HostInputStream, StreamError};
use bytes::{Bytes, BytesMut};
use std::io::{IsTerminal, Read};
use std::mem;
use std::sync::{Condvar, Mutex, OnceLock};
use tokio::sync::Notify;
#[derive(Default)]
struct GlobalStdin {
state: Mutex<StdinState>,
read_requested: Condvar,
read_completed: Notify,
}
#[derive(Default, Debug)]
enum StdinState {
#[default]
ReadNotRequested,
ReadRequested,
Data(BytesMut),
Error(std::io::Error),
Closed,
}
impl GlobalStdin {
fn get() -> &'static GlobalStdin {
static STDIN: OnceLock<GlobalStdin> = OnceLock::new();
STDIN.get_or_init(|| create())
}
}
fn create() -> GlobalStdin {
std::thread::spawn(|| {
let state = GlobalStdin::get();
loop {
let mut lock = state.state.lock().unwrap();
lock = state
.read_requested
.wait_while(lock, |state| !matches!(state, StdinState::ReadRequested))
.unwrap();
drop(lock);
let mut bytes = BytesMut::zeroed(1024);
let (new_state, done) = match std::io::stdin().read(&mut bytes) {
Ok(0) => (StdinState::Closed, true),
Ok(nbytes) => {
bytes.truncate(nbytes);
(StdinState::Data(bytes), false)
}
Err(e) => (StdinState::Error(e), true),
};
debug_assert!(matches!(
*state.state.lock().unwrap(),
StdinState::ReadRequested
));
*state.state.lock().unwrap() = new_state;
state.read_completed.notify_waiters();
if done {
break;
}
}
});
GlobalStdin::default()
}
#[derive(Clone)]
pub struct Stdin;
pub fn stdin() -> Stdin {
Stdin
}
impl StdinStream for Stdin {
fn stream(&self) -> Box<dyn HostInputStream> {
Box::new(Stdin)
}
fn isatty(&self) -> bool {
std::io::stdin().is_terminal()
}
}
#[async_trait::async_trait]
impl HostInputStream for Stdin {
fn read(&mut self, size: usize) -> Result<Bytes, StreamError> {
let g = GlobalStdin::get();
let mut locked = g.state.lock().unwrap();
match mem::replace(&mut *locked, StdinState::ReadRequested) {
StdinState::ReadNotRequested => {
g.read_requested.notify_one();
Ok(Bytes::new())
}
StdinState::ReadRequested => Ok(Bytes::new()),
StdinState::Data(mut data) => {
let size = data.len().min(size);
let bytes = data.split_to(size);
*locked = if data.is_empty() {
StdinState::ReadNotRequested
} else {
StdinState::Data(data)
};
Ok(bytes.freeze())
}
StdinState::Error(e) => {
*locked = StdinState::Closed;
Err(StreamError::LastOperationFailed(e.into()))
}
StdinState::Closed => {
*locked = StdinState::Closed;
Err(StreamError::Closed)
}
}
}
}
#[async_trait::async_trait]
impl Subscribe for Stdin {
async fn ready(&mut self) {
let g = GlobalStdin::get();
let notified = {
let mut locked = g.state.lock().unwrap();
match *locked {
StdinState::ReadNotRequested => {
g.read_requested.notify_one();
*locked = StdinState::ReadRequested;
g.read_completed.notified()
}
StdinState::ReadRequested => g.read_completed.notified(),
StdinState::Data(_) | StdinState::Closed | StdinState::Error(_) => return,
}
};
notified.await;
}
}