diff options
Diffstat (limited to 'src/devices')
-rw-r--r-- | src/devices/clock_device.rs | 161 | ||||
-rw-r--r-- | src/devices/file_device.rs | 376 | ||||
-rw-r--r-- | src/devices/input_device.rs | 254 | ||||
-rw-r--r-- | src/devices/math_device.rs | 199 | ||||
-rw-r--r-- | src/devices/memory_device.rs | 186 | ||||
-rw-r--r-- | src/devices/mod.rs | 17 | ||||
-rw-r--r-- | src/devices/screen_device.rs | 450 | ||||
-rw-r--r-- | src/devices/stream_device.rs | 239 | ||||
-rw-r--r-- | src/devices/system_device.rs | 119 |
9 files changed, 2001 insertions, 0 deletions
diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs new file mode 100644 index 0000000..d06a92c --- /dev/null +++ b/src/devices/clock_device.rs @@ -0,0 +1,161 @@ +use crate::*; + +use chrono::prelude::*; + + +pub struct ClockDevice { + pub epoch: Instant, + pub uptime_read: u16, + + pub t1: CountdownTimer, + pub t2: CountdownTimer, + pub t3: CountdownTimer, + pub t4: CountdownTimer, +} + + +impl Device for ClockDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => Local::now().year().saturating_sub(2000) as u8, + 0x1 => Local::now().month().saturating_sub(1) as u8, + 0x2 => Local::now().day().saturating_sub(1) as u8, + 0x3 => Local::now().hour() as u8, + 0x4 => Local::now().minute() as u8, + 0x5 => Local::now().second() as u8, + 0x6 => { self.uptime_read = self.uptime() as u16; + read_h!(self.uptime_read) }, + 0x7 => read_l!(self.uptime_read), + 0x8 => { self.t1.update(); read_h!(self.t1.read) }, + 0x9 => read_l!(self.t1.read), + 0xA => { self.t2.update(); read_h!(self.t2.read) }, + 0xB => read_l!(self.t2.read), + 0xC => { self.t3.update(); read_h!(self.t3.read) }, + 0xD => read_l!(self.t3.read), + 0xE => { self.t4.update(); read_h!(self.t4.read) }, + 0xF => read_l!(self.t4.read), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 0x5 => (), + 0x6 => (), + 0x7 => (), + 0x8 => write_h!(self.t1.write, value), + 0x9 => { write_l!(self.t1.write, value); self.t1.commit() }, + 0xA => write_h!(self.t2.write, value), + 0xB => { write_l!(self.t2.write, value); self.t2.commit() }, + 0xC => write_h!(self.t3.write, value), + 0xD => { write_l!(self.t3.write, value); self.t3.commit() }, + 0xE => write_h!(self.t4.write, value), + 0xF => { write_l!(self.t4.write, value); self.t4.commit() }, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + let t1 = self.t1.wake(); + let t2 = self.t2.wake(); + let t3 = self.t3.wake(); + let t4 = self.t4.wake(); + return t1 | t2 | t3 | t4; + } + + fn reset(&mut self) { + self.epoch = Instant::now(); + self.uptime_read = 0; + self.t1.reset(); + self.t2.reset(); + self.t3.reset(); + self.t4.reset(); + } +} + + +impl ClockDevice { + pub fn new() -> Self { + Self { + epoch: Instant::now(), + uptime_read: 0, + + t1: CountdownTimer::new(), + t2: CountdownTimer::new(), + t3: CountdownTimer::new(), + t4: CountdownTimer::new(), + } + } + + pub fn uptime(&self) -> u64 { + (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64 + } +} + + + +pub struct CountdownTimer { + pub end: Option<Instant>, + pub read: u16, + pub write: u16, + pub wake: bool, +} + +impl CountdownTimer { + pub fn new() -> Self { + Self { + end: None, + read: 0, + write: 0, + wake: false, + } + } + + pub fn reset(&mut self) { + self.end = None; + self.read = 0; + self.write = 0; + self.wake = false; + } + + pub fn wake(&mut self) -> bool { + if let Some(end) = self.end { + if end <= Instant::now() { + self.end = None; + self.wake = true; + } + } + std::mem::take(&mut self.wake) + } + + pub fn update(&mut self) { + if let Some(end) = self.end { + let now = Instant::now(); + if end > now { + let nanos = (end - now).as_nanos(); + self.read = (nanos * 256 / 1_000_000_000) as u16; + } else { + self.read = 0; + self.end = None; + self.wake = true; + } + } else { + self.read = 0; + } + } + + pub fn commit(&mut self) { + if self.write > 0 { + let nanos = (self.write as u64) * 1_000_000_000 / 256; + self.end = Some(Instant::now() + Duration::from_nanos(nanos)); + } else { + self.end = None; + } + } +} diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs new file mode 100644 index 0000000..7a64c8e --- /dev/null +++ b/src/devices/file_device.rs @@ -0,0 +1,376 @@ +use crate::*; + + +pub struct FileDevice { + pub base_path: PathBuf, + pub default_path: PathBuf, + + pub entry_buffer: BedrockPathBuffer, + pub action_buffer: BedrockPathBuffer, + pub path_buffer: BedrockPathBuffer, + + pub entry: Option<(Entry, BedrockFilePath)>, + pub cached_dir: Option<(Entry, BedrockFilePath)>, + + pub success: bool, + pub pointer_write: u32, + pub length_write: u32, + + pub enable_read: bool, + pub enable_write: bool, + pub enable_create: bool, + pub enable_move: bool, + pub enable_delete: bool, +} + + +impl Device for FileDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.entry.is_some()), + 0x1 => read_b!(self.success), + 0x2 => self.path_buffer.read(), + 0x3 => read_b!(self.entry_type()), + 0x4 => self.read_byte(), + 0x5 => self.read_byte(), + 0x6 => self.read_child_path(), + 0x7 => read_b!(self.child_type()), + 0x8 => read_hh!(self.pointer()), + 0x9 => read_hl!(self.pointer()), + 0xA => read_lh!(self.pointer()), + 0xB => read_ll!(self.pointer()), + 0xC => read_hh!(self.length()), + 0xD => read_hl!(self.length()), + 0xE => read_lh!(self.length()), + 0xF => read_ll!(self.length()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => self.write_to_entry_port(value), + 0x1 => self.write_to_action_port(value), + 0x2 => self.path_buffer.set_pointer(value), + 0x3 => self.ascend_to_parent(), + 0x4 => self.write_byte(value), + 0x5 => self.write_byte(value), + 0x6 => self.set_child_path(value), + 0x7 => self.descend_to_child(), + 0x8 => write_hh!(self.pointer_write, value), + 0x9 => write_hl!(self.pointer_write, value), + 0xA => write_lh!(self.pointer_write, value), + 0xB => {write_ll!(self.pointer_write, value); self.commit_pointer()}, + 0xC => write_hh!(self.length_write, value), + 0xD => write_hl!(self.length_write, value), + 0xE => write_lh!(self.length_write, value), + 0xF => {write_ll!(self.length_write, value); self.commit_length()}, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + todo!() + } +} + + +impl FileDevice { + pub fn new() -> Self { + #[cfg(target_family = "unix")] + let default_base: PathBuf = PathBuf::from("/"); + #[cfg(target_family = "windows")] + let default_base: PathBuf = PathBuf::from(""); + + // TODO: I'm not at all confident that the default path is correct + // when not being set as the current directory. + Self { + base_path: default_base, + default_path: match std::env::current_dir() { + Ok(dir) => PathBuf::from(dir), + Err(_) => PathBuf::from(""), + }, + + entry_buffer: BedrockPathBuffer::new(), + action_buffer: BedrockPathBuffer::new(), + path_buffer: BedrockPathBuffer::new(), + + entry: None, + cached_dir: None, + + success: false, + pointer_write: 0, + length_write: 0, + + enable_read: true, + enable_write: true, + enable_create: true, + enable_move: true, + enable_delete: false, + } + } + + /// Safely close the current entry, cleaning up entry variables. + pub fn close(&mut self) { + self.entry_buffer.clear(); + self.action_buffer.clear(); + self.path_buffer.clear(); + self.flush(); + + if let Some((Entry::Directory(mut dir), path)) = std::mem::take(&mut self.entry) { + // Prevent the selected child from persisting when loading from cache. + dir.deselect_child(); + self.cached_dir = Some((Entry::Directory(dir), path)); + } + } + + /// Open the entry at the given Bedrock path. + pub fn open(&mut self, path: BedrockFilePath) -> Result<(), ()> { + match path.entry_type() { + Some(EntryType::File) => { + let open_result = std::fs::OpenOptions::new() + .read(self.enable_read) + .write(self.enable_write) + .open(path.as_path()); + // Keep the current entry open if we can't open the new path. + if let Ok(file) = open_result { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::File(BufferedFile::new(file)), path)); + return Ok(()); + }; + } + Some(EntryType::Directory) => { + // Attempt to use the cached directory. + if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) { + if cached_path == path { + self.close(); + self.path_buffer.populate(cached_path.as_buffer()); + self.entry = Some((dir, cached_path)); + return Ok(()); + } + } + // Keep the current entry open if we can't open the new path. + if let Some(listing) = DirectoryListing::from_path(&path) { + self.close(); + self.path_buffer.populate(path.as_buffer()); + self.entry = Some((Entry::Directory(listing), path)); + return Ok(()); + }; + } + // The entry either doesn't exist or is not a file or directory. + None => (), + } + return Err(()); + } + + /// Process a byte received from the entry port. + pub fn write_to_entry_port(&mut self, byte: u8) { + if let Some(buffer) = self.entry_buffer.write(byte) { + self.close(); + match BedrockFilePath::from_buffer(buffer, &self.base_path) { + Some(path) => self.success = self.open(path).is_ok(), + None => self.success = false, + }; + } + } + + /// Process a byte received from the action port. + pub fn write_to_action_port(&mut self, byte: u8) { + if let Some(buffer) = self.action_buffer.write(byte) { + let destination_blank = buffer[0] == 0x00; + let destination = BedrockFilePath::from_buffer(buffer, &self.base_path); + self.success = false; + + if let Some((_, source)) = &self.entry { + if destination_blank { + if self.enable_delete { + self.success = delete_entry(&source.as_path()); + } + } else if let Some(dest) = destination { + if self.enable_move { + self.success = move_entry(&source.as_path(), &dest.as_path()); + } + } + } else if let Some(dest) = destination { + if self.enable_create { + self.success = create_file(&dest.as_path()); + } + } + self.close(); + } + } + + /// Attempt to open the parent directory of the current entry. + pub fn ascend_to_parent(&mut self) { + if let Some((_, path)) = &self.entry { + match path.parent() { + Some(parent) => self.success = self.open(parent).is_ok(), + None => self.success = false, + }; + } else { + match BedrockFilePath::from_path(&self.default_path, &self.base_path) { + Some(default) => self.success = self.open(default).is_ok(), + None => self.success = false, + }; + } + } + + /// Attempt to open the selected child of the current directory. + pub fn descend_to_child(&mut self) { + if let Some((Entry::Directory(dir), _)) = &self.entry { + match dir.child_path() { + Some(child) => self.success = self.open(child).is_ok(), + None => self.success = false, + }; + } else { + self.success = false; + } + } + + /// Return true if the current entry is a directory. + pub fn entry_type(&self) -> bool { + match self.entry { + Some((Entry::Directory(_), _)) => true, + _ => false, + } + } + + /// Read a byte from the path buffer of the selected child. + pub fn read_child_path(&mut self) -> u8 { + match &mut self.entry { + Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(), + _ => 0, + } + } + + pub fn set_child_path(&mut self, byte: u8) { + if let Some((Entry::Directory(dir), _)) = &mut self.entry { + dir.child_path_buffer().set_pointer(byte); + } + } + + /// Return true if the selected child is a directory. + pub fn child_type(&self) -> bool { + match &self.entry { + Some((Entry::Directory(dir), _)) => match dir.child_type() { + Some(EntryType::Directory) => true, + _ => false, + } + _ => false, + } + } + + /// Read a byte from the current file. + pub fn read_byte(&mut self) -> u8 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.read(), + _ => 0, + } + } + + /// Writes a byte to the currently-open file. + pub fn write_byte(&mut self, byte: u8) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.write(byte), + _ => (), + } + } + + pub fn pointer(&mut self) -> u32 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.pointer(), + Some((Entry::Directory(dir), _)) => dir.selected(), + _ => 0, + } + } + + pub fn commit_pointer(&mut self) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write), + Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write), + _ => (), + } + } + + pub fn length(&mut self) -> u32 { + match &mut self.entry { + Some((Entry::File(file), _)) => file.length(), + Some((Entry::Directory(dir), _)) => dir.length(), + _ => 0, + } + } + + pub fn commit_length(&mut self) { + match &mut self.entry { + Some((Entry::File(file), _)) => file.set_length(self.length_write), + _ => (), + } + } + + pub fn flush(&mut self) { + if let Some((Entry::File(buffered_file), _)) = &mut self.entry { + let _ = buffered_file; + } + } +} + + +impl Drop for FileDevice { + fn drop(&mut self) { + self.flush(); + } +} + + +/// Create a new file if it doesn't already exist, returning true if successful. +pub fn create_file(destination: &Path) -> bool { + if entry_exists(destination) { + false + } else { + if let Some(parent_path) = destination.parent() { + let _ = std::fs::create_dir_all(parent_path); + } + std::fs::OpenOptions::new().write(true).create_new(true) + .open(destination).is_ok() + } +} + +/// Move an entry from one location to another, returning true if successful. +pub fn move_entry(source: &Path, destination: &Path) -> bool { + if !entry_exists(source) || entry_exists(destination) { + return false; + } + std::fs::rename(source, destination).is_ok() +} + +/// Delete an entry, returning true if successful. +pub fn delete_entry(source: &Path) -> bool { + use std::fs::{remove_file, remove_dir_all}; + use std::io::ErrorKind; + + match remove_file(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + ErrorKind::IsADirectory => match remove_dir_all(source) { + Ok(_) => true, + Err(error) => match error.kind() { + ErrorKind::NotFound => true, + _ => false, + } + } + _ => false, + } + } +} + +/// Returns true if an entry already exists at the given path. +fn entry_exists(source: &Path) -> bool { + std::fs::metadata(source).is_ok() +} diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs new file mode 100644 index 0000000..83e8039 --- /dev/null +++ b/src/devices/input_device.rs @@ -0,0 +1,254 @@ +use crate::*; + +use std::collections::VecDeque; + + +pub struct InputDevice { + pub cursor: ScreenPosition, + pub x_read: u16, + pub y_read: u16, + pub h_scroll_read: i16, + pub v_scroll_read: i16, + pub h_scroll: f32, + pub v_scroll: f32, + pub pointer_buttons: u8, + pub pointer_active: bool, + + pub navigation: u8, + pub modifiers: u8, + pub characters: VecDeque<u8>, + pub gamepad_1: OwnedGamepad, + pub gamepad_2: OwnedGamepad, + pub gamepad_3: OwnedGamepad, + pub gamepad_4: OwnedGamepad, + + pub accessed: bool, + pub wake: bool, +} + + +impl Device for InputDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => { self.x_read = self.cursor.x; read_h!(self.x_read) }, + 0x1 => read_l!(self.cursor.x), + 0x2 => { self.y_read = self.cursor.y; read_h!(self.y_read) }, + 0x3 => read_l!(self.cursor.y), + 0x4 => { self.update_horizontal_scroll(); read_h!(self.h_scroll_read) }, + 0x5 => read_l!(self.h_scroll_read), + 0x6 => { self.update_vertical_scroll(); read_h!(self.v_scroll_read) }, + 0x7 => read_l!(self.v_scroll_read), + 0x8 => read_b!(self.pointer_active), + 0x9 => self.pointer_buttons, + 0xA => self.characters.pop_front().unwrap_or(0), + 0xB => self.modifiers, + 0xC => self.gamepad_1.state() | self.navigation, + 0xD => self.gamepad_2.state(), + 0xE => self.gamepad_3.state(), + 0xF => self.gamepad_4.state(), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { + let signal = if self.accessed { None } else { Some(Signal::Break) }; + self.accessed = true; + match port { + 0x0 => (), + 0x1 => (), + 0x2 => (), + 0x3 => (), + 0x4 => (), + 0x5 => (), + 0x6 => (), + 0x7 => (), + 0x8 => (), + 0x9 => (), + 0xA => self.characters.clear(), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), + _ => unreachable!(), + }; + return signal; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } + + fn reset(&mut self) { + self.cursor = ScreenPosition::ZERO; + self.x_read = 0; + self.y_read = 0; + self.h_scroll_read = 0; + self.v_scroll_read = 0; + self.h_scroll = 0.0; + self.v_scroll = 0.0; + self.pointer_active = false; + self.pointer_buttons = 0; + + self.navigation = 0; + self.modifiers = 0; + self.characters.clear(); + self.gamepad_1.reset(); + self.gamepad_2.reset(); + self.gamepad_3.reset(); + self.gamepad_4.reset(); + + self.accessed = false; + self.wake = false; + } +} + + +impl InputDevice { + pub fn new() -> Self { + Self { + cursor: ScreenPosition::ZERO, + x_read: 0, + y_read: 0, + h_scroll_read: 0, + v_scroll_read: 0, + h_scroll: 0.0, + v_scroll: 0.0, + pointer_active: false, + pointer_buttons: 0, + + navigation: 0, + modifiers: 0, + characters: VecDeque::new(), + gamepad_1: OwnedGamepad::new(1), + gamepad_2: OwnedGamepad::new(2), + gamepad_3: OwnedGamepad::new(3), + gamepad_4: OwnedGamepad::new(4), + + accessed: false, + wake: false, + } + } + + pub fn on_gamepad_event(&mut self, event: gilrs::Event) { + if let Some(g) = self.gamepad_1.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_2.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_3.register(event.id) { + self.wake |= g.process_event(&event); return; } + if let Some(g) = self.gamepad_4.register(event.id) { + self.wake |= g.process_event(&event); return; } + } + + pub fn on_cursor_enter(&mut self) { + self.pointer_active = true; + self.wake = true; + } + + pub fn on_cursor_exit(&mut self) { + self.pointer_active = false; + self.wake = true; + } + + pub fn on_cursor_move(&mut self, position: Position) { + self.pointer_active = true; + let cursor_position = ScreenPosition { + x: position.x as i16 as u16, + y: position.y as i16 as u16, + }; + if self.cursor != cursor_position { + self.cursor = cursor_position; + self.wake = true; + } + } + + pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) { + let mask = match button { + MouseButton::Left => 0x80, + MouseButton::Right => 0x40, + MouseButton::Middle => 0x20, + _ => return, + }; + let pointer_buttons = match action { + Action::Pressed => self.pointer_buttons | mask, + Action::Released => self.pointer_buttons & !mask, + }; + if self.pointer_buttons != pointer_buttons { + self.pointer_buttons = pointer_buttons; + self.wake = true; + } + } + + pub fn on_horizontal_scroll(&mut self, delta: f32) { + self.h_scroll += delta; + self.h_scroll = self.h_scroll.clamp(-32768.0, 32767.0); + } + + pub fn on_vertical_scroll(&mut self, delta: f32) { + self.v_scroll += delta; + self.v_scroll = self.v_scroll.clamp(i16::MIN as f32, i16::MAX as f32); + } + + pub fn update_horizontal_scroll(&mut self) { + self.h_scroll_read = self.h_scroll.trunc() as i16; + self.h_scroll -= self.h_scroll.trunc(); + } + + pub fn update_vertical_scroll(&mut self) { + self.v_scroll_read = self.v_scroll.trunc() as i16; + self.v_scroll -= self.v_scroll.trunc(); + } + + pub fn on_character(&mut self, character: char) { + let character = match character { + '\r' => '\n', + _ => character, + }; + let mut bytes = [0; 4]; + let string = character.encode_utf8(&mut bytes); + for byte in string.bytes() { + self.characters.push_back(byte); + } + self.wake = true; + } + + pub fn on_keypress(&mut self, key: KeyCode, action: Action) { + let shift = self.modifiers & 0x40 != 0; + let mask = match key { + KeyCode::ArrowUp => 0x80, // up + KeyCode::ArrowDown => 0x40, // down + KeyCode::ArrowLeft => 0x20, // left + KeyCode::ArrowRight => 0x10, // right + KeyCode::Enter => 0x08, // confirm + KeyCode::Escape => 0x04, // cancel + KeyCode::Tab => match shift { // shift + false => 0x02, // next + true => 0x01 // previous + }, + _ => return, + }; + let navigation = match action { + Action::Pressed => self.navigation | mask, + Action::Released => self.navigation & !mask, + }; + if self.navigation != navigation { + self.navigation = navigation; + self.wake = true; + } + } + + pub fn on_modifier(&mut self, state: ModifiersState) { + let mut modifiers = 0; + if state.control_key() { modifiers |= 0x80 } + if state.shift_key() { modifiers |= 0x40 } + if state.alt_key() { modifiers |= 0x20 } + if state.super_key() { modifiers |= 0x10 } + if self.modifiers != modifiers { + self.modifiers = modifiers; + self.wake = true; + } + } +} diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs new file mode 100644 index 0000000..e7043b9 --- /dev/null +++ b/src/devices/math_device.rs @@ -0,0 +1,199 @@ +use crate::*; + +const ANGLE_SCALE: f64 = 10430.378350470453; // 65536 / 2π + + +pub struct MathDevice { + pub x: u16, + pub y: u16, + pub r: u16, + pub t: u16, + pub x_read: Option<u16>, + pub y_read: Option<u16>, + pub r_read: Option<u16>, + pub t_read: Option<u16>, + pub prod: Option<(u16, u16)>, // (low, high) + pub quot: Option<u16>, + pub rem: Option<u16>, +} + + +impl Device for MathDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_h!(self.x()), + 0x1 => read_l!(self.x()), + 0x2 => read_h!(self.y()), + 0x3 => read_l!(self.y()), + 0x4 => read_h!(self.r()), + 0x5 => read_l!(self.r()), + 0x6 => read_h!(self.t()), + 0x7 => read_l!(self.t()), + 0x8 => read_h!(self.prod().1), + 0x9 => read_l!(self.prod().1), + 0xA => read_h!(self.prod().0), + 0xB => read_l!(self.prod().0), + 0xC => read_h!(self.quot()), + 0xD => read_l!(self.quot()), + 0xE => read_h!(self.rem()), + 0xF => read_l!(self.rem()), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => { write_h!(self.x, value); self.clear_polar(); }, + 0x1 => { write_l!(self.x, value); self.clear_polar(); }, + 0x2 => { write_h!(self.y, value); self.clear_polar(); }, + 0x3 => { write_l!(self.y, value); self.clear_polar(); }, + 0x4 => { write_h!(self.r, value); self.clear_cartesian(); }, + 0x5 => { write_l!(self.r, value); self.clear_cartesian(); }, + 0x6 => { write_h!(self.t, value); self.clear_cartesian(); }, + 0x7 => { write_l!(self.t, value); self.clear_cartesian(); }, + 0x8 => (), + 0x9 => (), + 0xA => (), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + self.x = 0; + self.y = 0; + self.r = 0; + self.t = 0; + self.clear_cartesian(); + self.clear_polar(); + } +} + + +impl MathDevice { + pub fn new() -> Self { + Self { + x: 0, + y: 0, + r: 0, + t: 0, + x_read: None, + y_read: None, + r_read: None, + t_read: None, + prod: None, + quot: None, + rem: None, + } + } + + pub fn clear_cartesian(&mut self) { + self.x_read = None; + self.y_read = None; + } + + pub fn clear_polar(&mut self) { + self.r_read = None; + self.t_read = None; + self.prod = None; + self.quot = None; + self.rem = None; + } + + pub fn x(&mut self) -> u16 { + match self.x_read { + Some(x) => x, + None => { + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let x = angle.cos() * r; + self.x_read = match x > (i16::MIN as f64) && x < (i16::MAX as f64) { + true => Some(x as i16 as u16), + false => Some(0), + }; + self.x_read.unwrap() + } + } + } + + pub fn y(&mut self) -> u16 { + match self.y_read { + Some(y) => y, + None => { + let r = self.r as f64; + let t = self.t as f64; + let angle = t / ANGLE_SCALE; + let y = angle.sin() * r; + self.y_read = match y > (i16::MIN as f64) && y < (i16::MAX as f64) { + true => Some(y as i16 as u16), + false => Some(0), + }; + self.y_read.unwrap() + } + } + } + + pub fn r(&mut self) -> u16 { + match self.r_read { + Some(r) => r, + None => { + let sum = (self.x as f64).powi(2) + (self.y as f64).powi(2); + self.r_read = Some(sum.sqrt() as u16); + self.r_read.unwrap() + } + } + } + + pub fn t(&mut self) -> u16 { + match self.t_read { + Some(t) => t, + None => { + let x = self.x as i16 as f64; + let y = self.x as i16 as f64; + let angle = f64::atan2(y, x) * ANGLE_SCALE; + self.t_read = Some(angle as i16 as u16); + self.t_read.unwrap() + } + } + } + + pub fn prod(&mut self) -> (u16, u16) { + match self.prod { + Some(prod) => prod, + None => { + self.prod = Some(self.x.widening_mul(self.y)); + self.prod.unwrap() + } + } + } + + pub fn quot(&mut self) -> u16 { + match self.quot { + Some(quot) => quot, + None => { + self.quot = Some(self.x.checked_div(self.y).unwrap_or(0)); + self.quot.unwrap() + } + } + } + + pub fn rem(&mut self) -> u16 { + match self.rem { + Some(rem) => rem, + None => { + self.rem = Some(self.x.checked_rem(self.y).unwrap_or(0)); + self.rem.unwrap() + } + } + } +} diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs new file mode 100644 index 0000000..d116ca7 --- /dev/null +++ b/src/devices/memory_device.rs @@ -0,0 +1,186 @@ +use crate::*; + +use std::cmp::min; + + +type Page = [u8; 256]; + + +pub struct MemoryDevice { + pub limit: u16, // maximum allocateable number of pages + pub pages: Vec<Page>, // all allocated pages + pub count_write: u16, // number of pages requested by program + pub count: usize, // number of pages allocated for use + pub copy_write: u16, + pub head_1: HeadAddress, + pub head_2: HeadAddress, +} + + +impl Device for MemoryDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_h!(self.count), + 0x1 => read_l!(self.count), + 0x2 => read_h!(self.head_1.page), + 0x3 => read_l!(self.head_1.page), + 0x4 => read_h!(self.head_1.address), + 0x5 => read_l!(self.head_1.address), + 0x6 => self.read_head_1(), + 0x7 => self.read_head_1(), + 0x8 => 0x00, + 0x9 => 0x00, + 0xA => read_h!(self.head_2.page), + 0xB => read_l!(self.head_2.page), + 0xC => read_h!(self.head_2.address), + 0xD => read_l!(self.head_2.address), + 0xE => self.read_head_2(), + 0xF => self.read_head_2(), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => write_h!(self.count_write, value), + 0x1 => { write_l!(self.count_write, value); self.allocate(); }, + 0x2 => write_h!(self.head_1.page, value), + 0x3 => write_l!(self.head_1.page, value), + 0x4 => write_h!(self.head_1.address, value), + 0x5 => write_l!(self.head_1.address, value), + 0x6 => self.write_head_1(value), + 0x7 => self.write_head_1(value), + 0x8 => write_h!(self.copy_write, value), + 0x9 => { write_l!(self.copy_write, value); self.copy(); }, + 0xA => write_h!(self.head_2.page, value), + 0xB => write_l!(self.head_2.page, value), + 0xC => write_h!(self.head_2.address, value), + 0xD => write_l!(self.head_2.address, value), + 0xE => self.write_head_2(value), + 0xF => self.write_head_2(value), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } + + fn reset(&mut self) { + self.pages.clear(); + self.count_write = 0; + self.count = 0; + self.copy_write = 0; + self.head_1.reset(); + self.head_2.reset(); + } +} + + +impl MemoryDevice { + pub fn new() -> Self { + Self { + limit: u16::MAX, + pages: Vec::new(), + count_write: 0, + count: 0, + copy_write: 0, + head_1: HeadAddress::new(), + head_2: HeadAddress::new(), + } + } + + pub fn read_head_1(&mut self) -> u8 { + let (page_i, byte_i) = self.head_1.get_indices(); + self.read_byte(page_i, byte_i) + } + + pub fn read_head_2(&mut self) -> u8 { + let (page_i, byte_i) = self.head_2.get_indices(); + self.read_byte(page_i, byte_i) + } + + fn read_byte(&self, page_i: usize, byte_i: usize) -> u8 { + match self.pages.get(page_i) { + Some(page) => page[byte_i], + None => 0, + } + } + + pub fn write_head_1(&mut self, value: u8) { + let (page_i, byte_i) = self.head_1.get_indices(); + self.write_byte(page_i, byte_i, value); + } + + pub fn write_head_2(&mut self, value: u8) { + let (page_i, byte_i) = self.head_2.get_indices(); + self.write_byte(page_i, byte_i, value); + } + + fn write_byte(&mut self, page_i: usize, byte_i: usize, value: u8) { + match self.pages.get_mut(page_i) { + Some(page) => page[byte_i] = value, + None => if page_i < self.count { + self.pages.resize(page_i + 1, [0; 256]); + self.pages[page_i][byte_i] = value; + } + } + } + + pub fn allocate(&mut self) { + self.count = min(self.count_write, self.limit) as usize; + // Defer allocation of new pages. + self.pages.truncate(self.count as usize); + } + + pub fn copy(&mut self) { + let src = self.head_2.page as usize; + let dest = self.head_1.page as usize; + let n = self.copy_write as usize; + + // Pre-allocate destination pages as needed. + let allocate = min(dest + n, self.count); + if allocate > self.pages.len() { + self.pages.resize(allocate, [0; 256]); + } + + for i in 0..n { + let src_page = match self.pages.get(src + i) { + Some(src_page) => src_page.to_owned(), + None => [0; 256], + }; + match self.pages.get_mut(dest + i) { + Some(dest) => *dest = src_page, + None => break, + }; + } + } +} + + +pub struct HeadAddress { + pub page: u16, + pub address: u16, +} + +impl HeadAddress { + pub fn new() -> Self { + Self { + page: 0, + address: 0, + } + } + + pub fn reset(&mut self) { + self.page = 0; + self.address = 0; + } + + pub fn get_indices(&mut self) -> (usize, usize) { + let page_i = (self.page + (self.address / 256)) as usize; + let byte_i = (self.address % 256) as usize; + self.address = self.address.wrapping_add(1); + (page_i, byte_i) + } +} diff --git a/src/devices/mod.rs b/src/devices/mod.rs new file mode 100644 index 0000000..aa98a49 --- /dev/null +++ b/src/devices/mod.rs @@ -0,0 +1,17 @@ +mod system_device; +mod memory_device; +mod math_device; +mod clock_device; +mod input_device; +mod screen_device; +mod stream_device; +mod file_device; + +pub use system_device::*; +pub use memory_device::*; +pub use math_device::*; +pub use clock_device::*; +pub use input_device::*; +pub use screen_device::*; +pub use stream_device::*; +pub use file_device::*; diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs new file mode 100644 index 0000000..483bcca --- /dev/null +++ b/src/devices/screen_device.rs @@ -0,0 +1,450 @@ +use crate::*; + +use geometry::*; +use phosphor::*; + + +pub type Sprite = [[u8; 8]; 8]; + +#[derive(Clone, Copy)] +pub enum Layer { Fg, Bg } + + +pub struct ScreenDevice { + /// Each byte represents a screen pixel, left-to-right and top-to-bottom. + // Only the bottom four bits of each byte are used. + pub fg: Vec<u8>, + pub bg: Vec<u8>, + pub dirty: bool, + + pub cursor: ScreenPosition, + pub vector: ScreenPosition, + + pub dimensions: ScreenDimensions, + pub dirty_dimensions: bool, + pub width_write: u16, + pub height_write: u16, + pub fixed_width: Option<u16>, + pub fixed_height: Option<u16>, + + pub palette_write: u16, + pub palette: [Colour; 16], + pub sprite_colours: u16, + pub sprite: SpriteBuffer, + + pub accessed: bool, + pub wake: bool, +} + + +impl HasDimensions<u16> for ScreenDevice { + fn dimensions(&self) -> ScreenDimensions { + self.dimensions + } +} + + +impl Device for ScreenDevice { + fn read(&mut self, port: u8) -> u8 { + self.accessed = true; + match port { + 0x0 => read_h!(self.cursor.x), + 0x1 => read_l!(self.cursor.x), + 0x2 => read_h!(self.cursor.y), + 0x3 => read_l!(self.cursor.y), + 0x4 => read_h!(self.dimensions.width), + 0x5 => read_l!(self.dimensions.width), + 0x6 => read_h!(self.dimensions.height), + 0x7 => read_l!(self.dimensions.height), + 0x8 => 0, + 0x9 => 0, + 0xA => 0, + 0xB => 0, + 0xC => 0, + 0xD => 0, + 0xE => 0, + 0xF => 0, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + let signal = if self.accessed { None } else { Some(Signal::Break) }; + self.accessed = true; + match port { + 0x0 => write_h!(self.cursor.x, value), + 0x1 => write_l!(self.cursor.x, value), + 0x2 => write_h!(self.cursor.y, value), + 0x3 => write_l!(self.cursor.y, value), + 0x4 => write_h!(self.width_write, value), + 0x5 => { write_l!(self.width_write, value); self.resize_width() } + 0x6 => write_h!(self.height_write, value), + 0x7 => { write_l!(self.height_write, value); self.resize_height() } + 0x8 => write_h!(self.palette_write, value), + 0x9 => { write_l!(self.palette_write, value); self.set_palette() } + 0xA => write_h!(self.sprite_colours, value), + 0xB => write_l!(self.sprite_colours, value), + 0xC => self.sprite.push_byte(value), + 0xD => self.sprite.push_byte(value), + 0xE => self.draw_dispatch(value), + 0xF => self.move_cursor(value), + _ => unreachable!(), + }; + return signal; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } + + fn reset(&mut self) { + self.fg.clear(); + self.bg.clear(); + self.dirty = false; + + self.cursor = ScreenPosition::ZERO; + self.vector = ScreenPosition::ZERO; + + self.dirty_dimensions = true; + self.width_write = 0; + self.height_write = 0; + self.fixed_width = None; + self.fixed_height = None; + + self.palette_write = 0; + self.palette = [ + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + Colour::grey(0x00), Colour::grey(0xFF), Colour::grey(0x55), Colour::grey(0xAA), + ]; + self.sprite_colours = 0; + self.sprite = SpriteBuffer::new(); + + self.accessed = false; + self.wake = false; + } +} + + +impl ScreenDevice { + pub fn new(config: &EmulatorConfig) -> Self { + let area = config.dimensions.area_usize(); + + Self { + fg: vec![0; area], + bg: vec![0; area], + dirty: false, + + cursor: ScreenPosition::ZERO, + vector: ScreenPosition::ZERO, + + dimensions: config.dimensions, + dirty_dimensions: true, + width_write: 0, + height_write: 0, + fixed_width: None, + fixed_height: None, + + palette_write: 0, + palette: [Colour::BLACK; 16], + sprite_colours: 0, + sprite: SpriteBuffer::new(), + + accessed: false, + wake: false, + } + } + + /// Resize screen to match window dimensions. + pub fn resize(&mut self, dimensions: phosphor::Dimensions) { + // Replace dimensions with fixed dimensions. + let screen_dimensions = ScreenDimensions { + width: match self.fixed_width { + Some(fixed_width) => fixed_width, + None => dimensions.width as u16, + }, + height: match self.fixed_height { + Some(fixed_height) => fixed_height, + None => dimensions.height as u16, + }, + }; + let old_dimensions = self.dimensions; + if self.dimensions != screen_dimensions { + self.dimensions = screen_dimensions; + self.resize_layers(old_dimensions); + self.wake = true; + } + } + + /// Internal resize. + fn resize_width(&mut self) { + self.fixed_width = Some(self.width_write); + self.dirty_dimensions = true; + let old_dimensions = self.dimensions; + if self.dimensions.width != self.width_write { + self.dimensions.width = self.width_write; + self.resize_layers(old_dimensions); + } + } + + /// Internal resize. + fn resize_height(&mut self) { + self.fixed_height = Some(self.height_write); + self.dirty_dimensions = true; + let old_dimensions = self.dimensions; + if self.dimensions.height != self.height_write { + self.dimensions.height = self.height_write; + self.resize_layers(old_dimensions); + } + } + + fn resize_layers(&mut self, old_dimensions: ScreenDimensions) { + use std::cmp::{min, Ordering}; + + let old_width = old_dimensions.width as usize; + let old_height = old_dimensions.height as usize; + let new_width = self.dimensions.width as usize; + let new_height = self.dimensions.height as usize; + let new_area = self.dimensions.area_usize(); + let y_range = 0..min(old_height, new_height); + let new_colour = match self.fg.last() { + None | Some(0) => *self.bg.last().unwrap_or(&0), + Some(colour) => *colour, + }; + + match new_width.cmp(&old_width) { + Ordering::Less => { + for y in y_range { + let src = y * old_width; + let dest = y * new_width; + let len = new_width; + self.fg.copy_within(src..src+len, dest); + self.bg.copy_within(src..src+len, dest); + } + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + }, + Ordering::Greater => { + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + for y in y_range.rev() { + let src = y * old_width; + let dest = y * new_width; + let len = old_width; + self.fg.copy_within(src..src+len, dest); + self.bg.copy_within(src..src+len, dest); + self.fg[dest+len..dest+new_width].fill(0); + self.bg[dest+len..dest+new_width].fill(new_colour); + } + }, + Ordering::Equal => { + self.fg.resize(new_area, 0); + self.bg.resize(new_area, new_colour); + }, + }; + + self.dirty = true; + } + + pub fn set_palette(&mut self) { + let i = (self.palette_write >> 12 ) as usize; + let r = (self.palette_write >> 8 & 0xF) as u8 * 17; + let g = (self.palette_write >> 4 & 0xF) as u8 * 17; + let b = (self.palette_write & 0xF) as u8 * 17; + let colour = Colour::from_rgb(r, g, b); + if self.palette[i] != colour { + self.palette[i] = colour; + self.dirty = true; + } + } + + pub fn draw_dispatch(&mut self, draw: u8) { + match draw >> 4 { + 0x0 => self.op_draw_pixel(Layer::Bg, draw), + 0x1 => self.op_draw_sprite(Layer::Bg, draw), + 0x2 => self.op_fill_layer(Layer::Bg, draw), + 0x3 => self.op_draw_sprite(Layer::Bg, draw), + 0x4 => self.op_draw_line(Layer::Bg, draw), + 0x5 => self.op_draw_line(Layer::Bg, draw), + 0x6 => self.op_draw_rect(Layer::Bg, draw), + 0x7 => self.op_draw_rect(Layer::Bg, draw), + 0x8 => self.op_draw_pixel(Layer::Fg, draw), + 0x9 => self.op_draw_sprite(Layer::Fg, draw), + 0xA => self.op_fill_layer(Layer::Fg, draw), + 0xB => self.op_draw_sprite(Layer::Fg, draw), + 0xC => self.op_draw_line(Layer::Fg, draw), + 0xD => self.op_draw_line(Layer::Fg, draw), + 0xE => self.op_draw_rect(Layer::Fg, draw), + 0xF => self.op_draw_rect(Layer::Fg, draw), + _ => unreachable!(), + } + self.vector = self.cursor; + self.dirty = true; + } + + pub fn move_cursor(&mut self, value: u8) { + let distance = (value & 0x3F) as u16; + match value >> 6 { + 0b00 => self.cursor.x = self.cursor.x.wrapping_add(distance), + 0b01 => self.cursor.y = self.cursor.y.wrapping_add(distance), + 0b10 => self.cursor.x = self.cursor.x.wrapping_sub(distance), + 0b11 => self.cursor.y = self.cursor.y.wrapping_sub(distance), + _ => unreachable!(), + }; + } + + /// Colour must already be masked by 0xF. + pub fn draw_pixel(&mut self, layer: Layer, x: u16, y: u16, colour: u8) { + if x < self.dimensions.width && y < self.dimensions.height { + let index = x as usize + (self.dimensions.width as usize * y as usize); + match layer { + Layer::Fg => self.fg[index] = colour, + Layer::Bg => self.bg[index] = colour, + }; + } + } + + fn op_draw_pixel(&mut self, layer: Layer, draw: u8) { + self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xF); + } + + fn op_fill_layer(&mut self, layer: Layer, draw: u8) { + match layer { + Layer::Fg => self.fg.fill(draw & 0xF), + Layer::Bg => self.bg.fill(draw & 0xF), + } + } + + fn op_draw_sprite(&mut self, layer: Layer, draw: u8) { + let sprite = match draw & 0x20 != 0 { + true => self.sprite.read_2bit_sprite(draw), + false => self.sprite.read_1bit_sprite(draw), + }; + let colours = [ + (self.sprite_colours >> 12 & 0x000F) as u8, + (self.sprite_colours >> 8 & 0x000F) as u8, + (self.sprite_colours >> 4 & 0x000F) as u8, + (self.sprite_colours & 0x000F) as u8, + ]; + let cx = self.cursor.x; + let cy = self.cursor.y; + + if draw & 0x08 != 0 { + // Draw sprite with transparent background + for y in 0..8 { + for x in 0..8 { + let index = sprite[y as usize][x as usize] as usize; + if index != 0 { + let px = cx.wrapping_add(x); + let py = cy.wrapping_add(y); + self.draw_pixel(layer, px, py, colours[index]); + } + } + } + } else { + // Draw sprite with opaque background + for y in 0..8 { + for x in 0..8 { + let index = sprite[y as usize][x as usize] as usize; + let px = cx.wrapping_add(x); + let py = cy.wrapping_add(y); + self.draw_pixel(layer, px, py, colours[index]); + } + } + } + } + + fn op_draw_line(&mut self, layer: Layer, draw: u8) { + let mut x: i16 = self.cursor.x as i16; + let mut y: i16 = self.cursor.y as i16; + let x_end: i16 = self.vector.x as i16; + let y_end: i16 = self.vector.y as i16; + + let dx: i32 = ((x_end as i32) - (x as i32)).abs(); + let dy: i32 = -((y_end as i32) - (y as i32)).abs(); + let sx: i16 = if x < x_end { 1 } else { -1 }; + let sy: i16 = if y < y_end { 1 } else { -1 }; + let mut e1: i32 = dx + dy; + + if draw & 0x10 != 0 { + // Draw 1-bit textured line. + let sprite = self.sprite.read_1bit_sprite(draw); + let c1 = (self.sprite_colours >> 8 & 0xF) as u8; + let c0 = (self.sprite_colours >> 12 & 0xF) as u8; + let opaque = draw & 0x08 == 0; + loop { + let sprite_pixel = sprite[(y as usize) % 8][(x as usize) % 8]; + if sprite_pixel != 0 { self.draw_pixel(layer, x as u16, y as u16, c1); } + else if opaque { self.draw_pixel(layer, x as u16, y as u16, c0); } + if x == x_end && y == y_end { break; } + let e2 = e1 << 1; + if e2 >= dy { e1 += dy; x += sx; } + if e2 <= dx { e1 += dx; y += sy; } + } + } else { + // Draw solid line. + let colour = draw & 0xF; + loop { + self.draw_pixel(layer, x as u16, y as u16, colour); + if x == x_end && y == y_end { break; } + let e2 = e1 << 1; + if e2 >= dy { e1 += dy; x += sx; } + if e2 <= dx { e1 += dx; y += sy; } + } + } + } + + fn op_draw_rect(&mut self, layer: Layer, draw: u8) { + macro_rules! clamp { + ($v:expr, $max:expr) => { + if $v > 0x7FFF { 0 } else if $v > $max { $max } else { $v } + }; + } + macro_rules! out_of_bounds { + ($axis:ident, $max:expr) => {{ + let c = self.cursor.$axis; + let v = self.vector.$axis; + c >= $max && v >= $max && (c >= 0x8000) == (v >= 0x8000) + }}; + } + + let out_of_bounds_x = out_of_bounds!(x, self.dimensions.width); + let out_of_bounds_y = out_of_bounds!(y, self.dimensions.height); + if out_of_bounds_x || out_of_bounds_y { return; } + + // Get bounding box. + let mut l = clamp!(self.vector.x, self.dimensions.width -1); + let mut r = clamp!(self.cursor.x, self.dimensions.width -1); + let mut t = clamp!(self.vector.y, self.dimensions.height -1); + let mut b = clamp!(self.cursor.y, self.dimensions.height -1); + if l > r { std::mem::swap(&mut l, &mut r) }; + if t > b { std::mem::swap(&mut t, &mut b) }; + + if draw & 0x10 != 0 { + // Draw 1-bit textured rectangle. + let sprite = self.sprite.read_1bit_sprite(draw); + let c1 = (self.sprite_colours >> 8 & 0xF) as u8; + let c0 = (self.sprite_colours >> 12 & 0xF) as u8; + let opaque = draw & 0x08 == 0; + for y in t..=b { + for x in l..=r { + let sprite_colour = sprite[(y as usize) % 8][(x as usize) % 8]; + if sprite_colour != 0 { self.draw_pixel(layer, x, y, c1); } + else if opaque { self.draw_pixel(layer, x, y, c0); } + } + } + } else { + // Draw solid rectangle. + let colour = draw & 0xF; + for y in t..=b { + for x in l..=r { + self.draw_pixel(layer, x, y, colour); + } + } + } + } +} + + diff --git a/src/devices/stream_device.rs b/src/devices/stream_device.rs new file mode 100644 index 0000000..1e67166 --- /dev/null +++ b/src/devices/stream_device.rs @@ -0,0 +1,239 @@ +use crate::*; + +use std::collections::VecDeque; +use std::io::{BufRead, Stdout, Write}; +use std::sync::mpsc::{self, TryRecvError}; + + +pub struct StreamDevice { + /// True if a source is connected to stdin. + stdin_connected: bool, + /// True if a transmission is in progress. + stdin_control: bool, + stdin_rx: mpsc::Receiver<Vec<u8>>, + /// Bytes received in the current transmission. + stdin_queue: VecDeque<u8>, + /// Bytes received since stdin end-of-transmission. + stdin_excess: VecDeque<u8>, + + stdout: Stdout, + /// True if a sink is connected to stdout. + stdout_connected: bool, + + /// True if stdin is transmission-encoded. + decode_stdin: bool, + /// True if stdout should be transmission-encoded. + encode_stdout: bool, + /// Half-byte buffer for decoding stdin. + decode_buffer: Option<u8>, + + wake: bool, +} + + +impl Device for StreamDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => read_b!(self.stdin_connected), + 0x1 => read_b!(self.stdout_connected), + 0x2 => read_b!(self.stdin_control), + 0x3 => read_b!(true), + 0x4 => self.stdin_length(), + 0x5 => read_b!(true), + 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_start_transmission(), + 0x3 => self.stdout_end_transmission(), + 0x4 => (), + 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) + } + + fn reset(&mut self) { + todo!() + } +} + + +impl StreamDevice { + pub fn new(config: &EmulatorConfig) -> Self { + // 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 { + stdin_connected: true, + stdin_control: false, + stdin_rx, + stdin_queue: VecDeque::new(), + stdin_excess: VecDeque::new(), + + stdout: std::io::stdout(), + stdout_connected: true, + + decode_stdin: config.decode_stdin, + encode_stdout: config.encode_stdout, + decode_buffer: None, + + wake: true, + } + } + + pub fn stdin_length(&mut self) -> u8 { + self.fetch_stdin_data(); + self.stdin_queue.len().try_into().unwrap_or(u8::MAX) + } + + /// Start a transmission on stdin. + pub fn stdin_start_transmission(&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. + } + } + } + + /// End the current transmission on stdout. + pub fn stdout_end_transmission(&mut self) { + self.stdout_write_raw(&[b'\n']); + } + + /// Fetch all pending data from stdin. + 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, + } + } + loop { + 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) => { + break; + } + Err(TryRecvError::Disconnected) => { + self.stdin_control = false; + if self.stdin_connected { + self.stdin_connected = false; + self.wake = true; // wake because stdin was disconnected. + } + break; + } + } + } + } + + 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 StreamDevice { + fn drop(&mut self) { + self.flush(); + } +} diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs new file mode 100644 index 0000000..097c616 --- /dev/null +++ b/src/devices/system_device.rs @@ -0,0 +1,119 @@ +use crate::*; + + +pub struct SystemDevice { + /// Name and version of this system. + pub name: StringBuffer, + /// Authors of this system. + pub authors: StringBuffer, + /// Mask of all devices permitted to wake from sleep. + pub wake_mask: u16, + /// Slot number of device that most recently woke the system. + pub wake_slot: u8, + /// True if the system has been put to sleep. + pub asleep: bool, + /// Mask of all connected devices. + pub connected_devices: u16, + /// Name of the first custom device. + pub custom1: StringBuffer, + /// Name of the second custom device. + pub custom2: StringBuffer, + /// Name of the third custom device. + pub custom3: StringBuffer, + /// Name of the fourth custom device. + pub custom4: StringBuffer, +} + + +impl Device for SystemDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => 0x00, + 0x1 => 0x00, + 0x2 => self.wake_slot, + 0x3 => 0x00, + 0x4 => self.custom1.read(), + 0x5 => self.custom2.read(), + 0x6 => self.custom3.read(), + 0x7 => self.custom4.read(), + 0x8 => self.name.read(), + 0x9 => self.authors.read(), + 0xA => 0x00, + 0xB => 0x00, + 0xC => 0x00, + 0xD => 0x00, + 0xE => read_h!(self.connected_devices), + 0xF => read_l!(self.connected_devices), + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => write_h!(self.wake_mask, value), + 0x1 => { + write_l!(self.wake_mask, value); + self.asleep = true; + return Some(Signal::Sleep); + } + 0x2 => (), + 0x3 => match value { + 0 => return Some(Signal::Reset), + _ => return Some(Signal::Fork), + } + 0x4 => self.custom1.restart(), + 0x5 => self.custom2.restart(), + 0x6 => self.custom3.restart(), + 0x7 => self.custom4.restart(), + 0x8 => self.name.restart(), + 0x9 => self.authors.restart(), + 0xA => (), + 0xB => (), + 0xC => (), + 0xD => (), + 0xE => (), + 0xF => (), + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + true + } + + fn reset(&mut self) { + self.wake_mask = 0; + self.wake_slot = 0; + self.custom1.restart(); + self.custom2.restart(); + self.custom3.restart(); + self.custom4.restart(); + self.name.restart(); + self.authors.restart(); + } +} + + +impl SystemDevice { + pub fn new(connected_devices: u16) -> Self { + let pkg_name = env!("CARGO_PKG_NAME"); + let pkg_version = env!("CARGO_PKG_VERSION"); + let pkg_authors = env!("CARGO_PKG_AUTHORS"); + let name = format!("{pkg_name}/{pkg_version}"); + let authors = pkg_authors.replace(':', "\n"); + + Self { + name: StringBuffer::from_str(&name), + authors: StringBuffer::from_str(&authors), + wake_mask: 0, + wake_slot: 0, + asleep: false, + connected_devices, + custom1: StringBuffer::new(), + custom2: StringBuffer::new(), + custom3: StringBuffer::new(), + custom4: StringBuffer::new(), + } + } +} |