diff options
Diffstat (limited to 'src/devices')
-rw-r--r-- | src/devices/clock_device.rs | 225 | ||||
-rw-r--r-- | src/devices/file_device.rs | 155 | ||||
-rw-r--r-- | src/devices/file_device/bedrock_file_path.rs | 287 | ||||
-rw-r--r-- | src/devices/file_device/bedrock_path_buffer.rs | 60 | ||||
-rw-r--r-- | src/devices/file_device/buffered_file.rs | 144 | ||||
-rw-r--r-- | src/devices/file_device/directory_listing.rs | 120 | ||||
-rw-r--r-- | src/devices/file_device/entry.rs | 36 | ||||
-rw-r--r-- | src/devices/file_device/operations.rs | 47 | ||||
-rw-r--r-- | src/devices/input_device.rs | 202 | ||||
-rw-r--r-- | src/devices/math_device.rs | 211 | ||||
-rw-r--r-- | src/devices/memory_device.rs | 210 | ||||
-rw-r--r-- | src/devices/mod.rs | 17 | ||||
-rw-r--r-- | src/devices/remote_device.rs | 35 | ||||
-rw-r--r-- | src/devices/screen_device.rs | 249 | ||||
-rw-r--r-- | src/devices/stream_device.rs (renamed from src/devices/local_device.rs) | 174 | ||||
-rw-r--r-- | src/devices/system_device.rs | 125 |
16 files changed, 813 insertions, 1484 deletions
diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs index 7cc877e..b7c5414 100644 --- a/src/devices/clock_device.rs +++ b/src/devices/clock_device.rs @@ -1,114 +1,32 @@ -use bedrock_core::*; +use crate::*; use chrono::prelude::*; -use std::time::{Duration, Instant}; - - -macro_rules! fn_read_timer { - ($fn_name:ident($read:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$end > uptime { - self.$read = (self.$end.saturating_sub(uptime)) as u16; - } else { - if self.$end > 0 { - self.$end = 0; - self.wake = true; - } - self.$read = 0; - } - } - }; -} - -macro_rules! fn_set_timer { - ($fn_name:ident($write:ident, $end:ident)) => { - pub fn $fn_name(&mut self) { - let uptime = self.uptime(); - if self.$write > 0 { - self.$end = uptime.saturating_add(self.$write as u32); - } else { - self.$end = 0; - } - } - }; -} pub struct ClockDevice { - pub wake: bool, - - pub start: Instant, + pub epoch: Instant, pub uptime_read: u16, - pub t1_end: u32, - pub t2_end: u32, - pub t3_end: u32, - pub t4_end: u32, + // End time for each timer as ticks since epoch, zero if not set. + pub t1_end: u64, + pub t2_end: u64, + pub t3_end: u64, + pub t4_end: u64, + // Cached read value for each timer. pub t1_read: u16, pub t2_read: u16, pub t3_read: u16, pub t4_read: u16, + // Cached write value for each timer. pub t1_write: u16, pub t2_write: u16, pub t3_write: u16, pub t4_write: u16, -} - -impl ClockDevice { - pub fn new() -> Self { - Self { - start: Instant::now(), - uptime_read: 0, - wake: false, - - t1_end: 0, - t2_end: 0, - t3_end: 0, - t4_end: 0, - t1_read: 0, - t2_read: 0, - t3_read: 0, - t4_read: 0, - t1_write: 0, - t2_write: 0, - t3_write: 0, - t4_write: 0, - } - } - pub fn uptime(&self) -> u32 { - (self.start.elapsed().as_millis() / 4) as u32 - } - - pub fn read_uptime(&mut self) { - self.uptime_read = self.uptime() as u16; - } - - fn_read_timer!{ read_t1(t1_read, t1_end) } - fn_read_timer!{ read_t2(t2_read, t2_end) } - fn_read_timer!{ read_t3(t3_read, t3_end) } - fn_read_timer!{ read_t4(t4_read, t4_end) } - - fn_set_timer!{ set_t1(t1_write, t1_end) } - fn_set_timer!{ set_t2(t2_write, t2_end) } - fn_set_timer!{ set_t3(t3_write, t3_end) } - fn_set_timer!{ set_t4(t4_write, t4_end) } - - - pub fn time_remaining(&mut self) -> Option<Duration> { - use std::cmp::max; - let uptime = self.uptime(); - - let end = max(self.t1_end, max(self.t2_end, max(self.t3_end, self.t4_end))); - let remaining = end.saturating_sub(uptime); - match remaining > 0 { - true => Some(Duration::from_millis(remaining as u64) * 4), - false => None, - } - } + pub wake: bool, } + impl Device for ClockDevice { fn read(&mut self, port: u8) -> u8 { match port { @@ -118,16 +36,17 @@ impl Device for ClockDevice { 0x3 => Local::now().hour() as u8, 0x4 => Local::now().minute() as u8, 0x5 => Local::now().second() as u8, - 0x6 => { self.read_uptime(); read_h!(self.uptime_read) }, - 0x7 => read_l!(self.uptime_read), + 0x6 => { self.uptime_read = self.uptime() as u16; + read_h!(self.uptime_read) }, + 0x7 => read_l!(self.uptime_read), 0x8 => { self.read_t1(); read_h!(self.t1_read) }, - 0x9 => read_l!(self.t1_read), + 0x9 => read_l!(self.t1_read), 0xa => { self.read_t2(); read_h!(self.t2_read) }, - 0xb => read_l!(self.t2_read), + 0xb => read_l!(self.t2_read), 0xc => { self.read_t3(); read_h!(self.t3_read) }, - 0xd => read_l!(self.t3_read), + 0xd => read_l!(self.t3_read), 0xe => { self.read_t4(); read_h!(self.t4_read) }, - 0xf => read_l!(self.t4_read), + 0xf => read_l!(self.t4_read), _ => unreachable!(), } } @@ -142,13 +61,13 @@ impl Device for ClockDevice { 0x5 => (), 0x6 => (), 0x7 => (), - 0x8 => write_h!(self.t1_write, value), + 0x8 => write_h!(self.t1_write, value), 0x9 => { write_l!(self.t1_write, value); self.set_t1() }, - 0xa => write_h!(self.t2_write, value), + 0xa => write_h!(self.t2_write, value), 0xb => { write_l!(self.t2_write, value); self.set_t2() }, - 0xc => write_h!(self.t3_write, value), + 0xc => write_h!(self.t3_write, value), 0xd => { write_l!(self.t3_write, value); self.set_t3() }, - 0xe => write_h!(self.t4_write, value), + 0xe => write_h!(self.t4_write, value), 0xf => { write_l!(self.t4_write, value); self.set_t4() }, _ => unreachable!(), }; @@ -157,18 +76,100 @@ impl Device for ClockDevice { fn wake(&mut self) -> bool { let uptime = self.uptime(); - macro_rules! check_timer { - ($end:ident) => { - if self.$end > 0 && self.$end <= uptime { + + if self.t1_end > 0 && self.t1_end <= uptime { + self.t1_end = 0; self.wake = true; } + if self.t2_end > 0 && self.t2_end <= uptime { + self.t2_end = 0; self.wake = true; } + if self.t3_end > 0 && self.t3_end <= uptime { + self.t3_end = 0; self.wake = true; } + if self.t4_end > 0 && self.t4_end <= uptime { + self.t4_end = 0; self.wake = true; } + + return std::mem::take(&mut self.wake); + } +} + + +impl ClockDevice { + pub fn new() -> Self { + Self { + epoch: Instant::now(), + uptime_read: 0, + wake: false, + + t1_end: 0, + t2_end: 0, + t3_end: 0, + t4_end: 0, + t1_read: 0, + t2_read: 0, + t3_read: 0, + t4_read: 0, + t1_write: 0, + t2_write: 0, + t3_write: 0, + t4_write: 0, + } + } + + pub fn uptime(&self) -> u64 { + (self.epoch.elapsed().as_nanos() * 256 / 1_000_000_000) as u64 + } + + /// Duration remaining until the next timer expires, if any timer is set. + pub fn duration_remaining(&mut self) -> Option<Duration> { + let mut end = u64::MAX; + if self.t1_end > 0 { end = std::cmp::min(end, self.t1_end); } + if self.t2_end > 0 { end = std::cmp::min(end, self.t2_end); } + if self.t3_end > 0 { end = std::cmp::min(end, self.t3_end); } + if self.t4_end > 0 { end = std::cmp::min(end, self.t4_end); } + + if end != u64::MAX { + let remaining = end.saturating_sub(self.uptime()); + Some(Duration::from_nanos(remaining * 1_000_000_000 / 256)) + } else { + None + } + } +} + +macro_rules! fn_read_timer { + ($fn_name:ident($read:ident, $end:ident)) => { + pub fn $fn_name(&mut self) { + let uptime = self.uptime(); + if self.$end > uptime { + self.$read = (self.$end.saturating_sub(uptime)) as u16; + } else { + if self.$end > 0 { self.$end = 0; self.wake = true; } + self.$read = 0; + } + } + }; +} + +macro_rules! fn_set_timer { + ($fn_name:ident($write:ident, $end:ident)) => { + pub fn $fn_name(&mut self) { + match self.$write > 0 { + true => self.$end = self.uptime().saturating_add(self.$write as u64), + false => self.$end = 0, }; } - check_timer!(t1_end); - check_timer!(t2_end); - check_timer!(t3_end); - check_timer!(t4_end); - return std::mem::take(&mut self.wake); - } + }; +} + +impl ClockDevice { + fn_read_timer!{ read_t1(t1_read, t1_end) } + fn_read_timer!{ read_t2(t2_read, t2_end) } + fn_read_timer!{ read_t3(t3_read, t3_end) } + fn_read_timer!{ read_t4(t4_read, t4_end) } + + fn_set_timer!{ set_t1(t1_write, t1_end) } + fn_set_timer!{ set_t2(t2_write, t2_end) } + fn_set_timer!{ set_t3(t3_write, t3_end) } + fn_set_timer!{ set_t4(t4_write, t4_end) } } diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs index 61966b1..f8d8fa0 100644 --- a/src/devices/file_device.rs +++ b/src/devices/file_device.rs @@ -1,20 +1,4 @@ -mod bedrock_file_path; -mod bedrock_path_buffer; -mod buffered_file; -mod directory_listing; -mod entry; -mod operations; - -use buffered_file::BufferedFile; -use bedrock_file_path::BedrockFilePath; -use bedrock_path_buffer::BedrockPathBuffer; -use directory_listing::DirectoryListing; -use entry::{Entry, EntryType}; -use operations::{create_file, move_entry, delete_entry}; - -use bedrock_core::*; - -use std::path::{Component, Path, PathBuf}; +use crate::*; pub struct FileDevice { @@ -39,6 +23,59 @@ pub struct FileDevice { 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 + } +} + + impl FileDevice { pub fn new() -> Self { #[cfg(target_family = "unix")] @@ -279,59 +316,57 @@ impl FileDevice { } } + impl Drop for FileDevice { fn drop(&mut self) { self.flush(); } } -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!(), + +/// 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() } +} - 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; +/// 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() +} - fn wake(&mut self) -> bool { - false +/// 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/file_device/bedrock_file_path.rs b/src/devices/file_device/bedrock_file_path.rs deleted file mode 100644 index fdd8f79..0000000 --- a/src/devices/file_device/bedrock_file_path.rs +++ /dev/null @@ -1,287 +0,0 @@ -use super::*; - -use std::cmp::Ordering; -use std::ffi::OsString; - - -#[derive(Clone)] -pub struct BedrockFilePath { - /// Sandbox directory - base: PathBuf, - /// Path relative to sandbox directory - relative: PathBuf, - bytes: Vec<u8>, - entry_type: Option<EntryType>, -} - -impl BedrockFilePath { - pub fn from_buffer(buffer: [u8; 256], base: &Path) -> Option<Self> { - let base = base.to_path_buf(); - let relative = buffer_to_path(buffer)?; - let bytes = path_to_bytes(&relative)?; - let entry_type = get_entry_type(base.join(&relative)); - assert_path_is_safe(&relative, &base)?; - Some(Self { base, relative, bytes, entry_type }) - } - - /// Construct an instance from an absolute path and a prefix of that path. - pub fn from_path(path: &Path, base: &Path) -> Option<Self> { - let base = base.to_path_buf(); - let relative = path.strip_prefix(&base).ok()?.to_path_buf(); - let bytes = path_to_bytes(&relative)?; - let entry_type = get_entry_type(base.join(&relative)); - assert_path_is_safe(&relative, &base)?; - Some( Self { base, relative, bytes, entry_type } ) - } - - /// Get the base path used by this path. - pub fn base(&self) -> &Path { - &self.base - } - - /// Get this path as a Bedrock-style path, which can be passed to - /// a Bedrock program. - pub fn as_bytes(&self) -> &[u8] { - &self.bytes - } - - /// Get this path as a byte buffer, from which a CircularPathBuffer - /// can be populated. - pub fn as_buffer(&self) -> [u8; 256] { - let mut buffer: [u8; 256] = [0; 256]; - buffer[..self.bytes.len()].copy_from_slice(&self.bytes); - return buffer; - } - - /// Get this path as an absolute operating-system path, which can - /// be used to open a file or directory. - pub fn as_path(&self) -> PathBuf { - self.base.join(&self.relative) - } - - /// Get the entry type of this path. - pub fn entry_type(&self) -> Option<EntryType> { - self.entry_type - } - - /// Get a path which represents the parent of this path. - pub fn parent(&self) -> Option<Self> { - #[cfg(target_family = "unix")] { - Self::from_path(self.as_path().parent()?, &self.base) - } - #[cfg(target_family = "windows")] { - if self.base.components().count() != 0 { - // Sandboxed path, cannot ascend to a virtual root directory. - Self::from_path(self.as_path().parent()?, &self.base) - } else { - // Unsandboxed path, we can ascend to a virtual root directory. - match self.as_path().parent() { - // Ascend to concrete parent directory. - Some(parent) => Self::from_path(parent, &self.base), - // Ascend into a virtual root directory. - None => { - if self.relative.components().count() != 0 { - // Ascend from concrete path to virtual root. - let blank = PathBuf::from(""); - BedrockFilePath::from_path(&blank, &blank) - } else { - // Cannot ascend above the virtual root. - None - } - }, - } - } - } - } - - /// Returns true if the file would be hidden by the default file browser. - pub fn is_hidden(&self) -> bool { - #[cfg(target_family = "unix")] { - if let Some(stem) = self.relative.file_stem() { - if let Some(string) = stem.to_str() { - return string.starts_with('.'); - } - } - } - #[cfg(target_family = "windows")] { - use std::os::windows::fs::MetadataExt; - // See https://learn.microsoft.com/en-us/windows/win32/fileio/file-attribute-constants - // const FILE_ATTRIBUTE_HIDDEN: u32 = 0x00000002; - // const FILE_ATTRIBUTE_NOT_CONTENT_INDEXED: u32 = 0x00002000; - if let Ok(metadata) = std::fs::metadata(self.as_path()) { - return metadata.file_attributes() & 0x2002 != 0; - } - } - return false; - } -} - - -/// Converts the contents of a CircularPathBuffer to a relative path. -fn buffer_to_path(bytes: [u8; 256]) -> Option<PathBuf> { - // The buffer must be non-empty and slash-prefixed. - if bytes[0] != ('/' as u8) { - return None; - } - - // Find the index of the first null byte. - let mut null_i = None; - for (i, b) in bytes.iter().enumerate() { - if *b == 0x00 { - null_i = Some(i); - break; - } - } - // Take a slice, excluding the leading slash, up to the trailing null. - let slice = &bytes[1..null_i?]; - - #[cfg(target_family = "unix")] { - use std::os::unix::ffi::OsStringExt; - let vec = Vec::from(slice); - return Some(OsString::from_vec(vec).into()) - } - #[cfg(target_family = "windows")] { - use std::os::windows::ffi::OsStringExt; - let mut string = String::from_utf8_lossy(slice).to_string(); - // Convert drive-current-directory paths to drive-root paths. This is - // needed because the paths C: and C:/ point to separate directories, - // but trailing forward-slashes are optional in Bedrock. - if string.ends_with(':') { - string.push('/'); - } - let utf16: Vec<u16> = string.replace(r"/", r"\").encode_utf16().collect(); - return Some(OsString::from_wide(&utf16).into()) - } -} - -/// Convert an operating system path to a Bedrock-style byte path. -/// -/// A byte path contains at most 255 bytes, and is not null-terminated. -fn path_to_bytes(path: &Path) -> Option<Vec<u8>> { - #[cfg(target_family = "unix")] - let string = path.as_os_str().to_str()?.to_string(); - #[cfg(target_family = "windows")] - let string = path.as_os_str().to_str()?.replace(r"\", r"/"); - - // Remove any trailing forward-slash and add a leading forward-slash. - let mut prefixed_string = String::from("/"); - prefixed_string.push_str(string.trim_end_matches('/')); - let slice = prefixed_string.as_bytes(); - - // Error if bytes does not fit into a CircularPathBuffer. - if slice.len() > 255 { return None; } - - Some(Vec::from(slice)) -} - -/// Returns true if a relative path can be safely attached to a base without -/// breaking out of the sandbox. -fn assert_path_is_safe(relative: &Path, _base: &Path) -> Option<()> { - #[cfg(target_family = "unix")] { - // Error if path contains special components. - for component in relative.components() { - match component { - Component::Normal(_) => continue, - _ => return None, - } - } - } - #[cfg(target_family = "windows")] { - // If the base path is empty, the relative path needs to be able to - // contain the prefix and root element. If the base path is not - // empty, the relative path must not contain these elements else - // they will override the base path when joined. - if _base.components().count() != 0 { - for component in relative.components() { - match component { - Component::Normal(_) => continue, - _ => return None, - } - } - } - } - return Some(()); -} - -fn get_entry_type(absolute: PathBuf) -> Option<EntryType> { - #[cfg(target_family = "windows")] { - // If path is empty, this is a virtual root directory. - if absolute.components().count() == 0 { - return Some(EntryType::Directory) - } - } - let metadata = std::fs::metadata(absolute).ok()?; - if metadata.is_file() { - Some(EntryType::File) - } else if metadata.is_dir() { - Some(EntryType::Directory) - } else { - None - } -} - -impl std::fmt::Debug for BedrockFilePath { - fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { - self.as_path().fmt(f) - } -} - -// --------------------------------------------------------------------------- - -impl PartialEq for BedrockFilePath { - fn eq(&self, other: &Self) -> bool { - self.bytes == other.bytes && self.entry_type == other.entry_type - } -} - -impl Eq for BedrockFilePath {} - -impl PartialOrd for BedrockFilePath { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - Some(self.cmp(other)) - } -} - -impl Ord for BedrockFilePath { - fn cmp(&self, other: &Self) -> Ordering { - match self.entry_type.cmp(&other.entry_type) { - Ordering::Equal => compare_ascii_slices(&self.bytes, &other.bytes), - ordering => ordering, - } - } -} - -/// Compare two ASCII byte-slices in case-agnostic alphabetic order. -fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering { - let l = std::cmp::min(left.len(), right.len()); - let lhs = &left[..l]; - let rhs = &right[..l]; - - for i in 0..l { - let a = remap_ascii(lhs[i]); - let b = remap_ascii(rhs[i]); - match a.cmp(&b) { - Ordering::Equal => (), - non_eq => return non_eq, - } - } - - left.len().cmp(&right.len()) -} - -/// Remap ASCII values so that they sort in case-agnostic alphabetic order: -/// -/// ```text -/// !"#$%&'()*+,-./0123456789:;<=>? -/// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo -/// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_ -/// ``` -fn remap_ascii(c: u8) -> u8 { - if 0x40 <= c && c <= 0x5F { - (c - 0x40) * 2 + 0x40 - } else if 0x60 <= c && c <= 0x7F { - (c - 0x60) * 2 + 0x41 - } else { - c - } -} diff --git a/src/devices/file_device/bedrock_path_buffer.rs b/src/devices/file_device/bedrock_path_buffer.rs deleted file mode 100644 index d6a0861..0000000 --- a/src/devices/file_device/bedrock_path_buffer.rs +++ /dev/null @@ -1,60 +0,0 @@ -pub struct BedrockPathBuffer { - buffer: [u8; 256], - pointer: u8, -} - -impl BedrockPathBuffer { - pub fn new() -> Self { - Self { buffer: [0; 256] , pointer: 0 } - } - - /// Clear the buffer, returning the previous buffer contents. - pub fn clear(&mut self) -> [u8; 256] { - self.pointer = 0; - std::mem::replace(&mut self.buffer, [0; 256]) - } - - /// Reset the pointer and hot-swap the byte buffer. - pub fn populate(&mut self, buffer: [u8; 256]) { - self.pointer = 0; - self.buffer = buffer; - } - - /// Move internal pointer to the start of the path or file name. - /// - /// If value is non-zero, the pointer will be moved to the byte - /// directly following the final forward-slash. - pub fn set_pointer(&mut self, value: u8) { - self.pointer = 0; - // Set the pointer to the start of the filename if value is truthy. - if value != 0x00 { - for (i, c) in self.buffer.iter().enumerate() { - match c { - b'/' => self.pointer = (i as u8).saturating_add(1), - 0x00 => break, - _ => continue, - } - } - } - } - - /// Read a single byte from the buffer. - pub fn read(&mut self) -> u8 { - let pointer = self.pointer as usize; - self.pointer = self.pointer.wrapping_add(1); - self.buffer[pointer] - } - - /// Write a single byte to the buffer. - /// - /// If a null-byte is written, the buffer will be cleared and returned. - pub fn write(&mut self, byte: u8) -> Option<[u8; 256]> { - if byte == 0x00 { - Some(self.clear()) - } else { - self.buffer[self.pointer as usize] = byte; - self.pointer = self.pointer.saturating_add(1); - None - } - } -} diff --git a/src/devices/file_device/buffered_file.rs b/src/devices/file_device/buffered_file.rs deleted file mode 100644 index 29e1fa3..0000000 --- a/src/devices/file_device/buffered_file.rs +++ /dev/null @@ -1,144 +0,0 @@ -use std::fs::File; -use std::io::{BufReader, BufWriter}; -use std::io::{Read, Write}; -use std::io::{ErrorKind, Seek, SeekFrom}; - - -pub struct BufferedFile { - file: AccessMode, -} - -impl BufferedFile { - pub fn new(file: File) -> Self { - Self { - file: AccessMode::Read(BufReader::new(file)), - } - } - - pub fn close(&mut self) { - self.file = AccessMode::None; - } - - pub fn read(&mut self) -> u8 { - let mut buffer = [0u8; 1]; - - let read_result = match &mut self.file { - AccessMode::Read(reader) => reader.read_exact(&mut buffer), - AccessMode::Write(writer) => { - let address = writer.stream_position().unwrap(); - let file = std::mem::take(&mut self.file).unwrap(); - let mut reader = BufReader::new(file); - reader.seek(SeekFrom::Start(address)).unwrap(); - let read_result = reader.read_exact(&mut buffer); - self.file = AccessMode::Read(reader); - read_result - } - AccessMode::None => unreachable!(), - }; - - match read_result { - Ok(_) => buffer[0], - Err(error) => match error.kind() { - ErrorKind::UnexpectedEof => 0, - _ => { log::error!("BufferedFile::read: {error:?}"); 0 }, - } - } - } - - pub fn write(&mut self, byte: u8) { - let mut buffer = [byte; 1]; - - let write_result = match &mut self.file { - AccessMode::Write(writer) => writer.write_all(&mut buffer), - AccessMode::Read(reader) => { - let address = reader.stream_position().unwrap(); - let file = std::mem::take(&mut self.file).unwrap(); - let mut writer = BufWriter::new(file); - writer.seek(SeekFrom::Start(address)).unwrap(); - let write_result = writer.write_all(&mut buffer); - self.file = AccessMode::Write(writer); - write_result - } - AccessMode::None => unreachable!(), - }; - - write_result.unwrap(); - } - - pub fn pointer(&mut self) -> u32 { - let position = match &mut self.file { - AccessMode::Read(reader) => reader.stream_position(), - AccessMode::Write(writer) => writer.stream_position(), - AccessMode::None => unreachable!(), - }; - u32::try_from(position.unwrap()).unwrap_or(u32::MAX) - } - - pub fn set_pointer(&mut self, pointer: u32) { - let position = SeekFrom::Start(pointer as u64); - match &mut self.file { - AccessMode::Read(reader) => reader.seek(position).unwrap(), - AccessMode::Write(writer) => writer.seek(position).unwrap(), - AccessMode::None => unreachable!(), - }; - } - - pub fn length(&mut self) -> u32 { - let length = match &mut self.file { - AccessMode::Read(reader) => reader.stream_len(), - AccessMode::Write(writer) => writer.stream_len(), - AccessMode::None => unreachable!(), - }; - u32::try_from(length.unwrap()).unwrap_or(u32::MAX) - } - - pub fn set_length(&mut self, length: u32) { - match &mut self.file { - AccessMode::Read(_) => { - let file = std::mem::take(&mut self.file).unwrap(); - file.set_len(length as u64).unwrap(); - self.file = AccessMode::Read(BufReader::new(file)); - } - AccessMode::Write(_) => { - let file = std::mem::take(&mut self.file).unwrap(); - file.set_len(length as u64).unwrap(); - self.file = AccessMode::Read(BufReader::new(file)); - } - AccessMode::None => unreachable!(), - }; - } - - pub fn flush(&mut self) { - if let AccessMode::Write(writer) = &mut self.file { - let _ = writer.flush(); - } - } -} - -impl Drop for BufferedFile { - fn drop(&mut self) { - self.flush() - } -} - -enum AccessMode { - Read(BufReader<File>), - Write(BufWriter<File>), - None, -} - -impl AccessMode { - pub fn unwrap(self) -> File { - match self { - Self::Read(reader) => reader.into_inner(), - Self::Write(writer) => writer.into_inner().unwrap(), - Self::None => unreachable!(), - } - } -} - -impl Default for AccessMode { - fn default() -> Self { - Self::None - } -} diff --git a/src/devices/file_device/directory_listing.rs b/src/devices/file_device/directory_listing.rs deleted file mode 100644 index 465efc7..0000000 --- a/src/devices/file_device/directory_listing.rs +++ /dev/null @@ -1,120 +0,0 @@ -use super::*; - - -pub struct DirectoryListing { - children: Vec<BedrockFilePath>, - length: u32, - selected: Option<u32>, - child_path_buffer: BedrockPathBuffer, -} - - -impl DirectoryListing { - pub fn from_path(path: &BedrockFilePath) -> Option<Self> { - macro_rules! unres { - ($result:expr) => { match $result { Ok(v) => v, Err(_) => continue} }; - } - macro_rules! unopt { - ($option:expr) => { match $option { Some(v) => v, None => continue} }; - } - - #[cfg(target_family = "windows")] { - if path.as_path().components().count() == 0 { - return Some(Self::construct_virtual_root()) - } - } - - let mut children = Vec::new(); - if let Ok(dir_listing) = std::fs::read_dir(path.as_path()) { - for (i, entry_result) in dir_listing.enumerate() { - // Firebreak to prevent emulator from consuming an absurd amount - // of memory when opening too large of a directory. - if i == (u16::MAX as usize) { - break; - } - - let entry = unres!(entry_result); - let entry_path = unopt!(BedrockFilePath::from_path(&entry.path(), path.base())); - if entry_path.is_hidden() { - continue; - } - - children.push(entry_path); - } - } - - children.sort(); - let length = u32::try_from(children.len()).ok()?; - let selected = None; - let child_path_buffer = BedrockPathBuffer::new(); - Some( Self { children, length, selected, child_path_buffer } ) - } - - /// Generate entries for a virtual root directory. - #[cfg(target_family = "windows")] - fn construct_virtual_root() -> Self { - let mut children = Vec::new(); - let base = PathBuf::from(""); - let drive_bits = unsafe { - windows::Win32::Storage::FileSystem::GetLogicalDrives() - }; - for i in 0..26 { - if drive_bits & (0x1 << i) != 0 { - let letter: char = (b'A' + i).into(); - let path = PathBuf::from(format!("{letter}:/")); - if let Some(drive) = BedrockFilePath::from_path(&path, &base) { - children.push(drive); - } - } - } - - let length = children.len() as u32; - let selected = None; - let child_path_buffer = BedrockPathBuffer::new(); - Self { children, length, selected, child_path_buffer } - } - - /// Attempts to return a directory child by index. - pub fn get(&self, index: u32) -> Option<&BedrockFilePath> { - self.children.get(index as usize) - } - - pub fn length(&self) -> u32 { - self.length - } - - /// Returns the index of the selected child, or zero if no child is selected. - pub fn selected(&self) -> u32 { - self.selected.unwrap_or(0) - } - - /// Attempts to select a child by index. - pub fn set_selected(&mut self, index: u32) { - if let Some(child) = self.get(index) { - let buffer = child.as_buffer(); - self.child_path_buffer.populate(buffer); - self.selected = Some(index); - } else { - self.child_path_buffer.clear(); - self.selected = None; - } - } - - pub fn deselect_child(&mut self) { - self.child_path_buffer.clear(); - self.selected = None; - } - - pub fn child_path_buffer(&mut self) -> &mut BedrockPathBuffer { - &mut self.child_path_buffer - } - - pub fn child_type(&self) -> Option<EntryType> { - self.selected.and_then(|s| self.get(s).and_then(|i| i.entry_type())) - } - - pub fn child_path(&self) -> Option<BedrockFilePath> { - self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.clone()))) - } -} - diff --git a/src/devices/file_device/entry.rs b/src/devices/file_device/entry.rs deleted file mode 100644 index d604bb7..0000000 --- a/src/devices/file_device/entry.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::*; - -use std::cmp::Ordering; - -pub enum Entry { - File(BufferedFile), - Directory(DirectoryListing), -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub enum EntryType { - File, - Directory, -} - -impl PartialOrd for EntryType { - fn partial_cmp(&self, other: &Self) -> Option<Ordering> { - match (self, other) { - (EntryType::Directory, EntryType::Directory) => Some(Ordering::Equal ), - (EntryType::Directory, EntryType::File ) => Some(Ordering::Less ), - (EntryType::File, EntryType::Directory) => Some(Ordering::Greater), - (EntryType::File, EntryType::File ) => Some(Ordering::Equal ), - } - } -} - -impl Ord for EntryType { - fn cmp(&self, other: &Self) -> Ordering { - match (self, other) { - (EntryType::Directory, EntryType::Directory) => Ordering::Equal , - (EntryType::Directory, EntryType::File ) => Ordering::Less , - (EntryType::File, EntryType::Directory) => Ordering::Greater, - (EntryType::File, EntryType::File ) => Ordering::Equal , - } - } -} diff --git a/src/devices/file_device/operations.rs b/src/devices/file_device/operations.rs deleted file mode 100644 index 3a3f81b..0000000 --- a/src/devices/file_device/operations.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::io::ErrorKind; -use std::path::Path; - - -/// 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 { - match std::fs::remove_file(source) { - Ok(_) => true, - Err(e) => match e.kind() { - ErrorKind::NotFound => true, - ErrorKind::IsADirectory => match std::fs::remove_dir_all(source) { - Ok(_) => true, - Err(e) => match e.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 index 9b7038c..d2dd682 100644 --- a/src/devices/input_device.rs +++ b/src/devices/input_device.rs @@ -1,35 +1,10 @@ use crate::*; -use bedrock_core::*; -use phosphor::*; use std::collections::VecDeque; -macro_rules! fn_on_scroll { - ($fn_name:ident($value:ident, $delta:ident)) => { - pub fn $fn_name(&mut self, delta: f32) { - self.$delta += delta; - while self.$delta >= 1.0 { - self.$value = self.$value.saturating_add(1); - self.$delta -= 1.0; - self.wake = true; - } - while self.$delta <= -1.0 { - self.$value = self.$value.saturating_sub(1); - self.$delta += 1.0; - self.wake = true; - } - } - }; -} - pub struct InputDevice { - pub wake: bool, - pub accessed: bool, - - pub pointer_active: bool, - pub pointer_buttons: u8, - pub position: ScreenPosition, + pub cursor: ScreenPosition, pub x_read: u16, pub y_read: u16, @@ -38,27 +13,83 @@ pub struct InputDevice { pub h_scroll_delta: f32, pub v_scroll_delta: f32, - pub keyboard_active: bool, - pub characters: VecDeque<u8>, + pub pointer_buttons: u8, + pub pointer_active: bool, + pub navigation: u8, pub modifiers: u8, + pub characters: VecDeque<u8>, + pub keyboard_active: bool, pub gamepad_1: u8, pub gamepad_2: u8, pub gamepad_3: u8, pub gamepad_4: u8, + + 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.read_horizontal_scroll(), + 0x5 => self.read_vertical_scroll(), + 0x6 => self.pointer_buttons, + 0x7 => read_b!(self.pointer_active), + 0x8 => self.navigation, + 0x9 => self.modifiers, + 0xa => self.characters.pop_front().unwrap_or(0), + 0xb => read_b!(self.keyboard_active), + 0xc => self.gamepad_1, + 0xd => self.gamepad_2, + 0xe => self.gamepad_3, + 0xf => self.gamepad_4, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { + 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 None; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } +} + + impl InputDevice { pub fn new() -> Self { Self { - wake: false, - accessed: false, - - pointer_active: false, - pointer_buttons: 0, - - position: ScreenPosition::ZERO, + cursor: ScreenPosition::ZERO, x_read: 0, y_read: 0, @@ -67,15 +98,21 @@ impl InputDevice { h_scroll_delta: 0.0, v_scroll_delta: 0.0, - keyboard_active: true, - characters: VecDeque::new(), - modifiers: 0, + pointer_active: false, + pointer_buttons: 0, + navigation: 0, + modifiers: 0, + characters: VecDeque::new(), + keyboard_active: true, gamepad_1: 0, gamepad_2: 0, gamepad_3: 0, gamepad_4: 0, + + accessed: false, + wake: false, } } @@ -90,12 +127,12 @@ impl InputDevice { } pub fn on_cursor_move(&mut self, position: Position) { - let screen_position = ScreenPosition { + let cursor_position = ScreenPosition { x: position.x as i16 as u16, y: position.y as i16 as u16, }; - if self.position != screen_position { - self.position = screen_position; + if self.cursor != cursor_position { + self.cursor = cursor_position; self.wake = true; } } @@ -117,8 +154,33 @@ impl InputDevice { } } - fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta)); - fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta)); + pub fn on_horizontal_scroll(&mut self, delta: f32) { + self.h_scroll_delta += delta; + while self.h_scroll_delta >= 1.0 { + self.h_scroll = self.h_scroll.saturating_add(1); + self.h_scroll_delta -= 1.0; + self.wake = true; + } + while self.h_scroll_delta <= -1.0 { + self.h_scroll = self.h_scroll.saturating_sub(1); + self.h_scroll_delta += 1.0; + self.wake = true; + } + } + + pub fn on_vertical_scroll(&mut self, delta: f32) { + self.v_scroll_delta += delta; + while self.v_scroll_delta >= 1.0 { + self.v_scroll = self.v_scroll.saturating_add(1); + self.v_scroll_delta -= 1.0; + self.wake = true; + } + while self.v_scroll_delta <= -1.0 { + self.v_scroll = self.v_scroll.saturating_sub(1); + self.v_scroll_delta += 1.0; + self.wake = true; + } + } pub fn read_horizontal_scroll(&mut self) -> u8 { std::mem::take(&mut self.h_scroll) as u8 @@ -178,57 +240,3 @@ impl InputDevice { } } } - -impl Device for InputDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_b!(self.pointer_active), - 0x1 => self.pointer_buttons, - 0x2 => self.read_horizontal_scroll(), - 0x3 => self.read_vertical_scroll(), - 0x4 => { self.x_read = self.position.x as u16; read_h!(self.x_read) }, - 0x5 => read_l!(self.position.x), - 0x6 => { self.y_read = self.position.y as u16; read_h!(self.y_read) }, - 0x7 => read_l!(self.position.y), - 0x8 => read_b!(self.keyboard_active), - 0x9 => self.characters.pop_front().unwrap_or(0), - 0xa => self.navigation, - 0xb => self.modifiers, - 0xc => self.gamepad_1, - 0xd => self.gamepad_2, - 0xe => self.gamepad_3, - 0xf => self.gamepad_4, - _ => unreachable!(), - } - } - - fn write(&mut self, port: u8, _value: u8) -> Option<Signal> { - self.accessed = true; - match port { - 0x0 => (), - 0x1 => (), - 0x2 => (), - 0x3 => (), - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => self.characters.clear(), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs index 015545e..7944b48 100644 --- a/src/devices/math_device.rs +++ b/src/devices/math_device.rs @@ -1,59 +1,155 @@ -use bedrock_core::*; +use crate::*; +const ANGLE_SCALE: f64 = 10430.378350470453; // 65536 / 2Ï€ -pub struct MathDevice { - pub op1: u16, - pub op2: u16, - pub sqrt: Option<u16>, - pub atan: Option<u16>, - pub prod: Option<(u16, u16)>, // (low, high) +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>, + /// (low, high) + pub prod: Option<(u16, u16)>, 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 + } +} + + impl MathDevice { pub fn new() -> Self { Self { - op1: 0, - op2: 0, + x: 0, + y: 0, + r: 0, + t: 0, + x_read: None, + y_read: None, + r_read: None, + t_read: None, - sqrt: None, - atan: None, prod: None, quot: None, rem: None, } } - pub fn clear(&mut self) { - self.sqrt = None; - self.atan = 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; + 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 = Some(x as i16 as u16); + self.x_read.unwrap() + } + } } - pub fn atan(&mut self) -> u16 { - match self.atan { - Some(atan) => atan, + pub fn y(&mut self) -> u16 { + match self.y_read { + Some(y) => y, None => { - let x = self.op1 as i16 as f64; - let y = self.op2 as i16 as f64; - const SCALE: f64 = 10430.378350470453; // PI * 32768 - self.atan = Some((f64::atan2(x, y) * SCALE) as i16 as u16); - self.atan.unwrap() + 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 = Some(y as i16 as u16); + self.y_read.unwrap() } } } - pub fn sqrt(&mut self) -> u16 { - match self.sqrt { - Some(sqrt) => sqrt, + pub fn r(&mut self) -> u16 { + match self.r_read { + Some(r) => r, None => { - let input = ((self.op1 as u32) << 16) | (self.op2 as u32); - self.sqrt = Some((input as f64).sqrt() as u16); - self.sqrt.unwrap() + 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() } } } @@ -62,7 +158,7 @@ impl MathDevice { match self.prod { Some(prod) => prod, None => { - self.prod = Some(self.op1.widening_mul(self.op2)); + self.prod = Some(self.x.widening_mul(self.y)); self.prod.unwrap() } } @@ -72,7 +168,7 @@ impl MathDevice { match self.quot { Some(quot) => quot, None => { - self.quot = Some(self.op1.checked_div(self.op2).unwrap_or(0)); + self.quot = Some(self.x.checked_div(self.y).unwrap_or(0)); self.quot.unwrap() } } @@ -82,60 +178,9 @@ impl MathDevice { match self.rem { Some(rem) => rem, None => { - self.rem = Some(self.op1.checked_rem(self.op2).unwrap_or(0)); + self.rem = Some(self.x.checked_rem(self.y).unwrap_or(0)); self.rem.unwrap() } } } } - -impl Device for MathDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => read_h!(self.op1), - 0x1 => read_l!(self.op1), - 0x2 => read_h!(self.op2), - 0x3 => read_l!(self.op2), - 0x4 => read_h!(self.sqrt()), - 0x5 => read_l!(self.sqrt()), - 0x6 => read_h!(self.atan()), - 0x7 => read_l!(self.atan()), - 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.op1, value); self.clear(); }, - 0x1 => { write_l!(self.op1, value); self.clear(); }, - 0x2 => { write_h!(self.op2, value); self.clear(); }, - 0x3 => { write_l!(self.op2, value); self.clear(); }, - 0x4 => (), - 0x5 => (), - 0x6 => (), - 0x7 => (), - 0x8 => (), - 0x9 => (), - 0xa => (), - 0xb => (), - 0xc => (), - 0xd => (), - 0xe => (), - 0xf => (), - _ => unreachable!(), - }; - return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs index 0128d55..8efb12a 100644 --- a/src/devices/memory_device.rs +++ b/src/devices/memory_device.rs @@ -1,53 +1,73 @@ -use bedrock_core::*; +use crate::*; -type Page = [u8; 256]; - -macro_rules! fn_read_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self) -> u8 { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get(page_i) { - Some(page) => page[byte_i], - None => 0, - } - } - }; -} +use std::cmp::min; -macro_rules! fn_write_head { - ($fn_name:ident($offset:ident, $address:ident)) => { - pub fn $fn_name(&mut self, byte: u8) { - let page_i = (self.$offset + (self.$address / 256)) as usize; - let byte_i = (self.$address % 256) as usize; - self.$address = self.$address.wrapping_add(1); - match self.pages.get_mut(page_i) { - Some(page) => page[byte_i] = byte, - None => if page_i < self.provisioned { - self.pages.resize(page_i + 1, [0; 256]); - self.pages[page_i][byte_i] = byte; - } - } - } - }; -} +type Page = [u8; 256]; pub struct MemoryDevice { pub limit: u16, // maximum provisionable number of pages pub requested: u16, // number of pages requested by program pub provisioned: usize, // number of pages provisioned for use pub pages: Vec<Page>, // all allocated pages + pub head_1: HeadAddress, + pub head_2: HeadAddress, + pub copy_length: u16, +} - pub offset_1: u16, - pub address_1: u16, - pub offset_2: u16, - pub address_2: u16, - pub copy_length: u16, +impl Device for MemoryDevice { + fn read(&mut self, port: u8) -> u8 { + match port { + 0x0 => self.read_head_1(), + 0x1 => self.read_head_1(), + 0x2 => read_h!(self.head_1.offset), + 0x3 => read_l!(self.head_1.offset), + 0x4 => read_h!(self.head_1.address), + 0x5 => read_l!(self.head_1.address), + 0x6 => read_h!(self.provisioned), + 0x7 => read_l!(self.provisioned), + 0x8 => self.read_head_2(), + 0x9 => self.read_head_2(), + 0xa => read_h!(self.head_2.offset), + 0xb => read_l!(self.head_2.offset), + 0xc => read_h!(self.head_2.address), + 0xd => read_l!(self.head_2.address), + 0xe => 0x00, + 0xf => 0x00, + _ => unreachable!(), + } + } + + fn write(&mut self, port: u8, value: u8) -> Option<Signal> { + match port { + 0x0 => self.write_head_1(value), + 0x1 => self.write_head_1(value), + 0x2 => write_h!(self.head_1.offset, value), + 0x3 => write_l!(self.head_1.offset, value), + 0x4 => write_h!(self.head_1.address, value), + 0x5 => write_l!(self.head_1.address, value), + 0x6 => write_h!(self.requested, value), + 0x7 => { write_l!(self.requested, value); self.provision(); }, + 0x8 => self.write_head_2(value), + 0x9 => self.write_head_2(value), + 0xa => write_h!(self.head_2.offset, value), + 0xb => write_l!(self.head_2.offset, value), + 0xc => write_h!(self.head_2.address, value), + 0xd => write_l!(self.head_2.address, value), + 0xe => write_h!(self.copy_length, value), + 0xf => { write_l!(self.copy_length, value); self.copy(); }, + _ => unreachable!(), + }; + return None; + } + + fn wake(&mut self) -> bool { + false + } } + impl MemoryDevice { pub fn new() -> Self { Self { @@ -55,34 +75,63 @@ impl MemoryDevice { requested: 0, provisioned: 0, pages: Vec::new(), + head_1: HeadAddress::new(), + head_2: HeadAddress::new(), + copy_length: 0, + } + } + + pub fn read_head_1(&mut self) -> u8 { + let (page_i, byte_i) = self.head_1.get_page_address(); + self.read_byte(page_i, byte_i) + } - offset_1: 0, - address_1: 0, - offset_2: 0, - address_2: 0, + pub fn read_head_2(&mut self) -> u8 { + let (page_i, byte_i) = self.head_2.get_page_address(); + self.read_byte(page_i, byte_i) + } - copy_length: 0, + fn read_byte(&self, page_i: usize, byte_i: usize) -> u8 { + match self.pages.get(page_i) { + Some(page) => page[byte_i], + None => 0, } } - fn_read_head! { read_head_1( offset_1, address_1) } - fn_read_head! { read_head_2( offset_2, address_2) } - fn_write_head!{ write_head_1(offset_1, address_1) } - fn_write_head!{ write_head_2(offset_2, address_2) } + pub fn write_head_1(&mut self, value: u8) { + let (page_i, byte_i) = self.head_1.get_page_address(); + 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_page_address(); + self.write_byte(page_i, byte_i, value); + } + + // Write a byte to a page of memory. + 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.provisioned { + self.pages.resize(page_i + 1, [0; 256]); + self.pages[page_i][byte_i] = value; + } + } + } pub fn provision(&mut self) { - self.provisioned = std::cmp::min(self.requested, self.limit) as usize; + self.provisioned = min(self.requested, self.limit) as usize; // Defer allocation of new pages. self.pages.truncate(self.provisioned as usize); } pub fn copy(&mut self) { - let src = self.offset_2 as usize; - let dest = self.offset_1 as usize; + let src = self.head_2.offset as usize; + let dest = self.head_1.offset as usize; let count = self.copy_length as usize; // Pre-allocate destination pages as needed. - let pages_needed = std::cmp::min(dest + count, self.provisioned); + let pages_needed = min(dest + count, self.provisioned); if pages_needed > self.pages.len() { self.pages.resize(pages_needed, [0; 256]); } @@ -100,53 +149,24 @@ impl MemoryDevice { } } -impl Device for MemoryDevice { - fn read(&mut self, port: u8) -> u8 { - match port { - 0x0 => self.read_head_1(), - 0x1 => self.read_head_1(), - 0x2 => read_h!(self.offset_1), - 0x3 => read_l!(self.offset_1), - 0x4 => read_h!(self.address_1), - 0x5 => read_l!(self.address_1), - 0x6 => read_h!(self.provisioned), - 0x7 => read_l!(self.provisioned), - 0x8 => self.read_head_2(), - 0x9 => self.read_head_2(), - 0xa => read_h!(self.offset_2), - 0xb => read_l!(self.offset_2), - 0xc => read_h!(self.address_2), - 0xd => read_l!(self.address_2), - 0xe => 0x00, - 0xf => 0x00, - _ => unreachable!(), - } - } - fn write(&mut self, port: u8, value: u8) -> Option<Signal> { - match port { - 0x0 => self.write_head_1(value), - 0x1 => self.write_head_1(value), - 0x2 => write_h!(self.offset_1, value), - 0x3 => write_l!(self.offset_1, value), - 0x4 => write_h!(self.address_1, value), - 0x5 => write_l!(self.address_1, value), - 0x6 => write_h!(self.requested, value), - 0x7 => { write_l!(self.requested, value); self.provision(); }, - 0x8 => self.write_head_2(value), - 0x9 => self.write_head_2(value), - 0xa => write_h!(self.offset_2, value), - 0xb => write_l!(self.offset_2, value), - 0xc => write_h!(self.address_2, value), - 0xd => write_l!(self.address_2, value), - 0xe => write_h!(self.copy_length, value), - 0xf => { write_l!(self.copy_length, value); self.copy(); }, - _ => unreachable!(), - }; - return None; +pub struct HeadAddress { + pub offset: u16, + pub address: u16, +} + +impl HeadAddress { + pub fn new() -> Self { + Self { + offset: 0, + address: 0, + } } - fn wake(&mut self) -> bool { - false + fn get_page_address(&mut self) -> (usize, usize) { + let page_i = (self.offset + (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/remote_device.rs b/src/devices/remote_device.rs deleted file mode 100644 index f50ac7a..0000000 --- a/src/devices/remote_device.rs +++ /dev/null @@ -1,35 +0,0 @@ -use bedrock_core::*; - - -pub struct RemoteDevice { - -} - -impl RemoteDevice { - pub fn new() -> Self { - Self { - - } - } -} - -impl Device for RemoteDevice { - fn read(&mut self, _port: u8) -> u8 { - todo!() - // match port { - // _ => unreachable!(), - // } - } - - fn write(&mut self, _port: u8, _value: u8) -> Option<Signal> { - todo!() - // match port { - // _ => unreachable!(), - // }; - // return None; - } - - fn wake(&mut self) -> bool { - false - } -} diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs index a10ab20..4c3f5ab 100644 --- a/src/devices/screen_device.rs +++ b/src/devices/screen_device.rs @@ -1,21 +1,18 @@ use crate::*; -use bedrock_core::*; use geometry::*; use phosphor::*; -type Sprite = [[u8; 8]; 8]; + +pub type Sprite = [[u8; 8]; 8]; + #[derive(Clone, Copy)] pub enum Layer { Fg, Bg } pub struct ScreenDevice { - pub wake: bool, - pub accessed: bool, - /// Each byte represents a screen pixel, left-to-right and top-to-bottom. // Only the bottom four bits of each byte are used. - // TODO: Consider using the high bit of each pixel byte as a dirty bit. pub fg: Vec<u8>, pub bg: Vec<u8>, pub dirty: bool, @@ -32,18 +29,81 @@ pub struct ScreenDevice { pub palette_write: u16, pub palette: [Colour; 16], - pub colours: u16, + 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> { + 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 None; + } + + fn wake(&mut self) -> bool { + self.accessed = true; + std::mem::take(&mut self.wake) + } +} + + impl ScreenDevice { pub fn new(config: &EmulatorConfig) -> Self { - let area = config.dimensions.area_usize(); + let area = config.initial_dimensions.area_usize(); Self { - wake: false, - accessed: false, - fg: vec![0; area], bg: vec![0; area], dirty: false, @@ -51,7 +111,7 @@ impl ScreenDevice { cursor: ScreenPosition::ZERO, vector: ScreenPosition::ZERO, - dimensions: config.dimensions, + dimensions: config.initial_dimensions, dirty_dimensions: true, width_write: 0, height_write: 0, @@ -60,12 +120,15 @@ impl ScreenDevice { palette_write: 0, palette: [Colour::BLACK; 16], - colours: 0, + sprite_colours: 0, sprite: SpriteBuffer::new(), + + accessed: false, + wake: false, } } - /// External resize. + /// Resize screen to match window dimensions. pub fn resize(&mut self, dimensions: phosphor::Dimensions) { // Replace dimensions with fixed dimensions. let screen_dimensions = ScreenDimensions { @@ -231,10 +294,10 @@ impl ScreenDevice { false => self.sprite.read_1bit_sprite(draw), }; let colours = [ - (self.colours >> 12 & 0x000f) as u8, - (self.colours >> 8 & 0x000f) as u8, - (self.colours >> 4 & 0x000f) as u8, - (self.colours & 0x000f) as u8, + (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; @@ -279,8 +342,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured line. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + 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]; @@ -333,8 +396,8 @@ impl ScreenDevice { if draw & 0x10 != 0 { // Draw 1-bit textured rectangle. let sprite = self.sprite.read_1bit_sprite(draw); - let c1 = (self.colours >> 8 & 0xf) as u8; - let c0 = (self.colours >> 12 & 0xf) as u8; + 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 { @@ -355,146 +418,4 @@ impl ScreenDevice { } } -impl Device for ScreenDevice { - fn read(&mut self, port: u8) -> u8 { - self.accessed = true; - match port { - 0x0 => read_h!(self.dimensions.width), - 0x1 => read_l!(self.dimensions.width), - 0x2 => read_h!(self.dimensions.height), - 0x3 => read_l!(self.dimensions.height), - 0x4 => read_h!(self.cursor.x), - 0x5 => read_l!(self.cursor.x), - 0x6 => read_h!(self.cursor.y), - 0x7 => read_l!(self.cursor.y), - 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> { - self.accessed = true; - match port { - 0x0 => write_h!(self.width_write, value), - 0x1 => { write_l!(self.width_write, value); self.resize_width(); }, - 0x2 => write_h!(self.height_write, value), - 0x3 => { write_l!(self.height_write, value); self.resize_height(); }, - 0x4 => write_h!(self.cursor.x, value), - 0x5 => write_l!(self.cursor.x, value), - 0x6 => write_h!(self.cursor.y, value), - 0x7 => write_l!(self.cursor.y, value), - 0x8 => write_h!(self.palette_write, value), - 0x9 => { write_l!(self.palette_write, value); self.set_palette(); }, - 0xa => write_h!(self.colours, value), - 0xb => write_l!(self.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 None; - } - - fn wake(&mut self) -> bool { - self.accessed = true; - std::mem::take(&mut self.wake) - } -} - -impl HasDimensions<u16> for ScreenDevice { - fn dimensions(&self) -> ScreenDimensions { - self.dimensions - } -} - - -pub struct SpriteBuffer { - pub mem: [u8; 16], - pub pointer: usize, - pub cached: Option<(Sprite, u8)>, -} - -impl SpriteBuffer { - pub fn new() -> Self { - Self { - mem: [0; 16], - pointer: 0, - cached: None, - } - } - - pub fn push_byte(&mut self, byte: u8) { - self.mem[self.pointer] = byte; - self.pointer = (self.pointer + 1) % 16; - self.cached = None; - } - - pub fn read_1bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - match draw & 0x07 { - 0x0 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x1 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x2 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>(7-x) & 1; } } }, - 0x3 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[y][x] = l>>( x) & 1; } } }, - 0x4 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x5 => { for y in 0..8 { c!(l=mem[p++]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - 0x6 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>(7-x) & 1; } } }, - 0x7 => { for y in 0..8 { c!(l=mem[--p]); for x in 0..8 { sprite[x][y] = l>>( x) & 1; } } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } - pub fn read_2bit_sprite(&mut self, draw: u8) -> Sprite { - if let Some((sprite, transform)) = self.cached { - if transform == (draw & 0x77) { - return sprite; - } - } - macro_rules! c { - ($v:ident=mem[$p:ident++]) => { let $v = self.mem[$p % 16]; $p = $p.wrapping_add(1); }; - ($v:ident=mem[--$p:ident]) => { $p = $p.wrapping_sub(1); let $v = self.mem[$p % 16]; }; - } - let mut sprite = [[0; 8]; 8]; - let mut p = match draw & 0x02 != 0 { - true => self.pointer, - false => self.pointer + 8, - }; - let mut s = p + 8; - match draw & 0x07 { - 0x0 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x1 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x2 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x3 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[y][x] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x4 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x5 => for y in 0..8 { c!(l=mem[p++]); c!(h=mem[s++]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x6 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i=7-x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - 0x7 => for y in 0..8 { c!(l=mem[--p]); c!(h=mem[--s]); for x in 0..8 { let i= x; sprite[x][y] = (l>>i & 1) | (h>>i & 1) << 1; } }, - _ => unreachable!(), - } - self.cached = Some((sprite, draw & 0x77)); - return sprite; - } -} diff --git a/src/devices/local_device.rs b/src/devices/stream_device.rs index c6456de..e44ffb8 100644 --- a/src/devices/local_device.rs +++ b/src/devices/stream_device.rs @@ -1,36 +1,91 @@ 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, - +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, + 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 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) } + +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 => 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 => (), + 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) + } +} + + +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 { @@ -46,20 +101,20 @@ impl LocalDevice { }); Self { - wake: true, - stdin_connected: true, stdin_control: false, stdin_rx, - stdin_queue, + stdin_queue: VecDeque::new(), stdin_excess: VecDeque::new(), - stdout: std::io::stdout(), + stdout: std::io::stdout(), stdout_connected: true, decode_stdin: config.decode_stdin, encode_stdout: config.encode_stdout, decode_buffer: None, + + wake: true, } } @@ -108,6 +163,7 @@ impl LocalDevice { } } + /// Fetch all pending data from stdin. pub fn fetch_stdin_data(&mut self) { while self.stdin_control { match self.stdin_excess.pop_front() { @@ -115,21 +171,26 @@ impl LocalDevice { 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), + 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) => (), - Err(TryRecvError::Disconnected) => { - self.stdin_control = false; - if self.stdin_connected { - self.stdin_connected = false; - self.wake = true; // wake because stdin was disconnected. + 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; } } } @@ -167,61 +228,8 @@ impl LocalDevice { } -impl Drop for LocalDevice { +impl Drop for StreamDevice { 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) - } -} diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs index 383bb08..10ddad1 100644 --- a/src/devices/system_device.rs +++ b/src/devices/system_device.rs @@ -1,76 +1,69 @@ -use bedrock_core::*; +use crate::*; pub struct SystemDevice { + /// Name and version of this system. pub name: ReadBuffer, + /// Authors of this system. pub authors: ReadBuffer, - pub can_wake: u16, - pub wake_id: u8, + /// Mask of all devices waiting to wake from sleep. + pub wakers: u16, + /// Device that most recently woke the system. + pub waker: u8, + /// True if the system has been put to sleep. pub asleep: bool, + /// Mask of all available devices. + pub devices: u16, + /// Name of the first custom devices. + pub custom1: ReadBuffer, + /// Name of the second custom devices. + pub custom2: ReadBuffer, + /// Name of the third custom devices. + pub custom3: ReadBuffer, + /// Name of the fourth custom devices. + pub custom4: ReadBuffer, } -impl SystemDevice { - pub fn new() -> Self { - let pkg_version = env!("CARGO_PKG_VERSION"); - let pkg_name = env!("CARGO_PKG_NAME"); - let pkg_authors = env!("CARGO_PKG_AUTHORS"); - let name_str = format!("{pkg_name}/{pkg_version}"); - let authors_str = pkg_authors.replace(":", "\n"); - Self { - name: ReadBuffer::from_str(&name_str), - authors: ReadBuffer::from_str(&authors_str), - can_wake: 0, - wake_id: 0, - asleep: false, - } - } - - pub fn can_wake(&self, dev_id: u8) -> bool { - test_bit!(self.can_wake, 0x8000 >> dev_id) - } -} impl Device for SystemDevice { fn read(&mut self, port: u8) -> u8 { match port { - 0x0 => self.name.read(), - 0x1 => self.authors.read(), - 0x2 => 0x00, + 0x0 => 0x00, + 0x1 => 0x00, + 0x2 => self.waker, 0x3 => 0x00, 0x4 => 0x00, 0x5 => 0x00, - 0x6 => 0b1111_1100, - 0x7 => 0b0000_0000, // TODO: Update when fs and stream implemented - 0x8 => 0x00, - 0x9 => 0x00, - 0xa => self.wake_id, + 0x6 => 0x00, + 0x7 => 0x00, + 0x8 => self.name.read(), + 0x9 => self.authors.read(), + 0xa => 0x00, 0xb => 0x00, 0xc => 0x00, 0xd => 0x00, - 0xe => 0x00, - 0xf => 0x00, + 0xe => read_h!(self.devices), + 0xf => read_l!(self.devices), _ => unreachable!(), } } fn write(&mut self, port: u8, value: u8) -> Option<Signal> { match port { - 0x0 => self.name.pointer = 0, - 0x1 => self.authors.pointer = 0, + 0x0 => write_h!(self.wakers, value), + 0x1 => { write_l!(self.wakers, value); + self.asleep = true; + return Some(Signal::Sleep); }, 0x2 => (), - 0x3 => (), + 0x3 => return Some(Signal::Fork), 0x4 => (), 0x5 => (), 0x6 => (), 0x7 => (), - 0x8 => write_h!(self.can_wake, value), - 0x9 => { - write_l!(self.can_wake, value); - self.asleep = true; - return Some(Signal::Sleep); - }, + 0x8 => self.name.pointer = 0, + 0x9 => self.authors.pointer = 0, 0xa => (), - 0xb => return Some(Signal::Fork), + 0xb => (), 0xc => (), 0xd => (), 0xe => (), @@ -81,31 +74,41 @@ impl Device for SystemDevice { } fn wake(&mut self) -> bool { - true + false } } -pub struct ReadBuffer { - pub bytes: Vec<u8>, - pub pointer: usize, -} - -impl ReadBuffer { - pub fn from_str(text: &str) -> Self { +impl SystemDevice { + pub fn new(devices: u16) -> Self { Self { - bytes: text.bytes().collect(), - pointer: 0, + name: get_name(), + authors: get_authors(), + wakers: 0, + waker: 0, + asleep: false, + devices, + custom1: ReadBuffer::new(), + custom2: ReadBuffer::new(), + custom3: ReadBuffer::new(), + custom4: ReadBuffer::new(), } } +} - pub fn read(&mut self) -> u8 { - let pointer = self.pointer; - self.pointer += 1; - match self.bytes.get(pointer) { - Some(byte) => *byte, - None => 0, - } - } + +fn get_name() -> ReadBuffer { + let pkg_version = env!("CARGO_PKG_VERSION"); + let pkg_name = env!("CARGO_PKG_NAME"); + ReadBuffer::from_str(&format!("{pkg_name}/{pkg_version}")) } +fn get_authors() -> ReadBuffer { + let pkg_authors = env!("CARGO_PKG_AUTHORS"); + let mut authors_string = String::new(); + for author in pkg_authors.split(':') { + authors_string.push_str(author); + authors_string.push('\n'); + } + ReadBuffer::from_str(&authors_string) +} |