use crate::*;
use bedrock_core::*;
use std::collections::VecDeque;
use std::io::{BufRead, Stdout, Write};
use std::sync::mpsc::{self, TryRecvError};
pub struct LocalDevice {
wake: bool,
stdin_connected: bool,
stdin_control: bool,
stdin_rx: mpsc::Receiver<Vec<u8>>,
stdin_queue: VecDeque<u8>,
stdin_excess: VecDeque<u8>,
stdout: Stdout,
stdout_connected: bool,
decode_stdin: bool,
encode_stdout: bool,
decode_buffer: Option<u8>,
}
impl LocalDevice {
pub fn new(config: &EmulatorConfig) -> Self {
// Fill input queue with initial transmission.
let mut stdin_queue = VecDeque::new();
if let Some(bytes) = &config.initial_transmission {
for byte in bytes { stdin_queue.push_front(*byte) }
}
// Spawn a thread to enable non-blocking reads of stdin.
let (stdin_tx, stdin_rx) = std::sync::mpsc::channel();
std::thread::spawn(move || loop {
let mut stdin = std::io::stdin().lock();
match stdin.fill_buf() {
Ok(buf) if !buf.is_empty() => {
let length = buf.len();
stdin_tx.send(buf.to_vec()).unwrap();
stdin.consume(length);
}
_ => break,
};
});
Self {
wake: true,
stdin_connected: true,
stdin_control: false,
stdin_rx,
stdin_queue,
stdin_excess: VecDeque::new(),
stdout: std::io::stdout(),
stdout_connected: true,
decode_stdin: config.decode_stdin,
encode_stdout: config.encode_stdout,
decode_buffer: None,
}
}
pub fn stdin_length(&mut self) -> u8 {
self.fetch_stdin_data();
self.stdin_queue.len().try_into().unwrap_or(u8::MAX)
}
pub fn stdin_enable(&mut self) {
self.stdin_control = true;
}
pub fn stdin_read(&mut self) -> u8 {
self.fetch_stdin_data();
self.stdin_queue.pop_front().unwrap_or(0)
}
pub fn stdout_write(&mut self, value: u8) {
macro_rules! hex {
($value:expr) => { match $value {
0x0..=0x9 => $value + b'0',
0xa..=0xf => $value - 0x0a + b'a',
_ => unreachable!("Cannot encode value as hex digit: 0x{:02x}", $value),
} };
}
if self.encode_stdout {
let encoded = [hex!(value >> 4), hex!(value & 0xf), b' '];
self.stdout_write_raw(&encoded);
} else {
self.stdout_write_raw(&[value]);
};
}
fn stdout_write_raw(&mut self, bytes: &[u8]) {
if let Err(_) = self.stdout.write_all(bytes) {
if self.stdout_connected {
self.stdout_connected = false;
self.wake = true; // wake because stdout was disconnected.
}
}
}
pub fn stdout_disable(&mut self) {
if self.encode_stdout {
self.stdout_write_raw(&[b'\n']);
}
}
pub fn fetch_stdin_data(&mut self) {
while self.stdin_control {
match self.stdin_excess.pop_front() {
Some(byte) => self.fetch_byte(byte),
None => break,
}
}
match self.stdin_rx.try_recv() {
Ok(tx) => {
for byte in tx {
match self.stdin_control {
true => self.fetch_byte(byte),
false => self.stdin_excess.push_back(byte),
}
}
}
Err(TryRecvError::Empty) => (),
Err(TryRecvError::Disconnected) => {
self.stdin_control = false;
if self.stdin_connected {
self.stdin_connected = false;
self.wake = true; // wake because stdin was disconnected.
}
}
}
}
fn fetch_byte(&mut self, byte: u8) {
if self.decode_stdin {
let decoded = match byte {
b'0'..=b'9' => byte - b'0',
b'a'..=b'f' => byte - b'a' + 0x0a,
b'A'..=b'F' => byte - b'A' + 0x0a,
b'\n' => {
self.decode_buffer = None;
self.stdin_control = false;
self.wake = true; // wake because a transmission ended.
return;
},
_ => return,
};
if let Some(high) = std::mem::take(&mut self.decode_buffer) {
self.stdin_queue.push_back((high << 4) | decoded);
self.wake = true; // wake because a byte was received.
} else {
self.decode_buffer = Some(decoded);
}
} else {
self.stdin_queue.push_back(byte);
self.wake = true; // wake because a byte was received.
}
}
pub fn flush(&mut self) {
let _ = self.stdout.flush();
}
}
impl Drop for LocalDevice {
fn drop(&mut self) {
self.flush();
}
}
impl Device for LocalDevice {
fn read(&mut self, port: u8) -> u8 {
match port {
0x0 => read_b!(self.stdin_connected),
0x1 => 0xff,
0x2 => read_b!(self.stdin_control),
0x3 => 0xff,
0x4 => self.stdin_length(),
0x5 => 0xff,
0x6 => self.stdin_read(),
0x7 => self.stdin_read(),
0x8 => todo!(),
0x9 => todo!(),
0xa => todo!(),
0xb => todo!(),
0xc => todo!(),
0xd => todo!(),
0xe => todo!(),
0xf => todo!(),
_ => unreachable!(),
}
}
fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
match port {
0x0 => (),
0x1 => (),
0x2 => self.stdin_enable(),
0x3 => self.stdout_disable(),
0x4 => self.stdin_queue.clear(),
0x5 => (),
0x6 => self.stdout_write(value),
0x7 => self.stdout_write(value),
0x8 => todo!(),
0x9 => todo!(),
0xa => todo!(),
0xb => todo!(),
0xc => todo!(),
0xd => todo!(),
0xe => todo!(),
0xf => todo!(),
_ => unreachable!(),
};
return None;
}
fn wake(&mut self) -> bool {
self.fetch_stdin_data();
std::mem::take(&mut self.wake)
}
}