summaryrefslogtreecommitdiff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/clock_device.rs225
-rw-r--r--src/devices/file_device.rs155
-rw-r--r--src/devices/file_device/bedrock_file_path.rs287
-rw-r--r--src/devices/file_device/bedrock_path_buffer.rs60
-rw-r--r--src/devices/file_device/buffered_file.rs144
-rw-r--r--src/devices/file_device/directory_listing.rs120
-rw-r--r--src/devices/file_device/entry.rs36
-rw-r--r--src/devices/file_device/operations.rs47
-rw-r--r--src/devices/input_device.rs202
-rw-r--r--src/devices/math_device.rs211
-rw-r--r--src/devices/memory_device.rs210
-rw-r--r--src/devices/mod.rs17
-rw-r--r--src/devices/remote_device.rs35
-rw-r--r--src/devices/screen_device.rs249
-rw-r--r--src/devices/stream_device.rs (renamed from src/devices/local_device.rs)174
-rw-r--r--src/devices/system_device.rs125
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)
+}