summaryrefslogtreecommitdiff
path: root/src/devices
diff options
context:
space:
mode:
Diffstat (limited to 'src/devices')
-rw-r--r--src/devices/clock.rs124
-rw-r--r--src/devices/clock_device.rs185
-rw-r--r--src/devices/file.rs288
-rw-r--r--src/devices/file_device.rs335
-rw-r--r--src/devices/file_device/bedrock_file_path.rs (renamed from src/devices/file/bedrock_file_path.rs)15
-rw-r--r--src/devices/file_device/bedrock_path_buffer.rs (renamed from src/devices/file/circular_path_buffer.rs)12
-rw-r--r--src/devices/file_device/buffered_file.rs (renamed from src/devices/file/buffered_file.rs)25
-rw-r--r--src/devices/file_device/directory_listing.rs (renamed from src/devices/file/directory_listing.rs)18
-rw-r--r--src/devices/file_device/entry.rs (renamed from src/devices/file/entry.rs)0
-rw-r--r--src/devices/file_device/operations.rs (renamed from src/devices/file/operations.rs)44
-rw-r--r--src/devices/input.rs164
-rw-r--r--src/devices/input_device.rs233
-rw-r--r--src/devices/local_device.rs230
-rw-r--r--src/devices/math.rs31
-rw-r--r--src/devices/math_device.rs141
-rw-r--r--src/devices/memory.rs91
-rw-r--r--src/devices/memory_device.rs152
-rw-r--r--src/devices/remote_device.rs35
-rw-r--r--src/devices/screen.rs258
-rw-r--r--src/devices/screen/draw_line.rs223
-rw-r--r--src/devices/screen/draw_rect.rs42
-rw-r--r--src/devices/screen/draw_sprite.rs25
-rw-r--r--src/devices/screen/sprite_data.rs109
-rw-r--r--src/devices/screen_device.rs485
-rw-r--r--src/devices/stream.rs53
-rw-r--r--src/devices/system.rs4
-rw-r--r--src/devices/system/read_only_text_buffer.rs23
-rw-r--r--src/devices/system_device.rs114
28 files changed, 1973 insertions, 1486 deletions
diff --git a/src/devices/clock.rs b/src/devices/clock.rs
deleted file mode 100644
index 60b1cad..0000000
--- a/src/devices/clock.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-use std::time::{Duration, Instant};
-
-macro_rules! to_ticks { ($dur:expr) => {($dur.as_millis() / 4) as u16}; }
-macro_rules! from_ticks { ($ticks:expr) => {Duration::from_millis(($ticks * 4).into())}; }
-macro_rules! now { () => {
- time::OffsetDateTime::now_local().unwrap_or_else(|_| time::OffsetDateTime::now_utc())};}
-
-/// Create a method to set the instant of a timer.
-macro_rules! generate_set_timer_method {
- ($i:tt) => { mini_paste::item!{
- pub fn [< set_timer_ $i >] (&mut self) {
- let ticks = &mut self. [< timer_ $i >];
- let instant = &mut self. [< timer_ $i _end >];
-
- *instant = Instant::now() + from_ticks!(*ticks);
- }
- }};
-}
-
-/// Create a method to update the value of a timer.
-macro_rules! generate_update_timer_method {
- ($i:tt) => { mini_paste::item!{
- pub fn [< update_timer_ $i >] (&mut self) -> u16 {
- let ticks = &mut self. [< timer_ $i >];
- let instant = &mut self. [< timer_ $i _end >];
-
- if *ticks > 0 {
- *ticks = to_ticks!(instant.duration_since(Instant::now()));
- if *ticks == 0 { self.wake_flag = true; }
- }
- return *ticks;
- }
- }};
-}
-
-pub struct ClockDevice {
- pub wake_flag: bool,
-
- pub program_start: Instant,
- pub uptime: u16,
-
- pub timer_1_end: Instant,
- pub timer_2_end: Instant,
- pub timer_3_end: Instant,
- pub timer_4_end: Instant,
- pub timer_1: u16,
- pub timer_2: u16,
- pub timer_3: u16,
- pub timer_4: u16,
-}
-
-impl ClockDevice {
- pub fn new() -> Self {
- Self {
- wake_flag: false,
-
- program_start: Instant::now(),
- uptime: 0,
-
- timer_1_end: Instant::now(),
- timer_2_end: Instant::now(),
- timer_3_end: Instant::now(),
- timer_4_end: Instant::now(),
- timer_1: 0,
- timer_2: 0,
- timer_3: 0,
- timer_4: 0,
- }
- }
-
- generate_set_timer_method!{1}
- generate_set_timer_method!{2}
- generate_set_timer_method!{3}
- generate_set_timer_method!{4}
-
- generate_update_timer_method!{1}
- generate_update_timer_method!{2}
- generate_update_timer_method!{3}
- generate_update_timer_method!{4}
-
- pub fn update_timers(&mut self) {
- self.update_timer_1();
- self.update_timer_2();
- self.update_timer_3();
- self.update_timer_4();
- }
-
- pub fn update_uptime(&mut self) -> u16 {
- self.uptime = to_ticks!(self.program_start.elapsed());
- return self.uptime;
- }
-
- pub fn year(&self) -> u8 {
- now!().year().saturating_sub(2000).try_into().unwrap_or(u8::MAX)
- }
-
- pub fn month(&self) -> u8 {
- now!().month() as u8 - 1
- }
-
- pub fn day(&self) -> u8 {
- now!().day() as u8 - 1
- }
-
- pub fn hour(&self) -> u8 {
- now!().hour()
- }
-
- pub fn minute(&self) -> u8 {
- now!().minute()
- }
-
- pub fn second(&self) -> u8 {
- now!().second()
- }
-
- pub fn time_to_next_wake(&self) -> Option<Duration> {
- [self.timer_1, self.timer_2, self.timer_3, self.timer_4]
- .iter()
- .filter(|t| **t > 0)
- .min()
- .and_then(|t| Some(from_ticks!(t)))
- }
-}
diff --git a/src/devices/clock_device.rs b/src/devices/clock_device.rs
new file mode 100644
index 0000000..494e0c7
--- /dev/null
+++ b/src/devices/clock_device.rs
@@ -0,0 +1,185 @@
+use bedrock_core::*;
+
+use std::time::{Duration, Instant};
+
+
+macro_rules! now { () => {
+ time::OffsetDateTime::now_local()
+ .unwrap_or_else(|_| time::OffsetDateTime::now_utc())
+}; }
+
+fn current_year() -> u8 { now!().year().saturating_sub(2000) as u8 }
+fn current_month() -> u8 { now!().month() as u8 - 1 }
+fn current_day() -> u8 { now!().day() as u8 - 1 }
+fn current_hour() -> u8 { now!().hour() }
+fn current_minute() -> u8 { now!().minute() }
+fn current_second() -> u8 { now!().second() }
+
+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 uptime_read: u16,
+
+ pub t1_end: u32,
+ pub t2_end: u32,
+ pub t3_end: u32,
+ pub t4_end: u32,
+ pub t1_read: u16,
+ pub t2_read: u16,
+ pub t3_read: u16,
+ pub t4_read: u16,
+ 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,
+ }
+ }
+}
+
+impl Device for ClockDevice {
+ fn read(&mut self, port: u8) -> u8 {
+ match port {
+ 0x0 => current_year(),
+ 0x1 => current_month(),
+ 0x2 => current_day(),
+ 0x3 => current_hour(),
+ 0x4 => current_minute(),
+ 0x5 => current_second(),
+ 0x6 => { self.read_uptime(); 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),
+ 0xa => { self.read_t2(); read_h!(self.t2_read) },
+ 0xb => read_l!(self.t2_read),
+ 0xc => { self.read_t3(); read_h!(self.t3_read) },
+ 0xd => read_l!(self.t3_read),
+ 0xe => { self.read_t4(); read_h!(self.t4_read) },
+ 0xf => read_l!(self.t4_read),
+ _ => unreachable!(),
+ }
+ }
+
+ fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ match port {
+ 0x0 => (),
+ 0x1 => (),
+ 0x2 => (),
+ 0x3 => (),
+ 0x4 => (),
+ 0x5 => (),
+ 0x6 => (),
+ 0x7 => (),
+ 0x8 => write_h!(self.t1_write, value),
+ 0x9 => { write_l!(self.t1_write, value); self.set_t1() },
+ 0xa => write_h!(self.t2_write, value),
+ 0xb => { write_l!(self.t2_write, value); self.set_t2() },
+ 0xc => write_h!(self.t3_write, value),
+ 0xd => { write_l!(self.t3_write, value); self.set_t3() },
+ 0xe => write_h!(self.t4_write, value),
+ 0xf => { write_l!(self.t4_write, value); self.set_t4() },
+ _ => unreachable!(),
+ };
+ return None;
+ }
+
+ fn wake(&mut self) -> bool {
+ let uptime = self.uptime();
+ macro_rules! check_timer {
+ ($end:ident) => {
+ if self.$end > 0 && self.$end <= uptime {
+ self.$end = 0;
+ self.wake = true;
+ }
+ };
+ }
+ check_timer!(t1_end);
+ check_timer!(t2_end);
+ check_timer!(t3_end);
+ check_timer!(t4_end);
+ return std::mem::take(&mut self.wake);
+ }
+}
diff --git a/src/devices/file.rs b/src/devices/file.rs
deleted file mode 100644
index 0bea2f4..0000000
--- a/src/devices/file.rs
+++ /dev/null
@@ -1,288 +0,0 @@
-mod bedrock_file_path;
-mod buffered_file;
-mod circular_path_buffer;
-mod directory_listing;
-mod entry;
-mod operations;
-
-pub use bedrock_file_path::*;
-pub use buffered_file::*;
-pub use circular_path_buffer::*;
-pub use directory_listing::*;
-pub use entry::*;
-use operations::*;
-
-use std::path::{Component, Path, PathBuf};
-use std::mem::take;
-
-
-pub struct FileDevice {
- pub base_path: PathBuf,
- pub default_path: PathBuf,
-
- pub open_buffer: CircularPathBuffer,
- pub move_buffer: CircularPathBuffer,
- pub name_buffer: CircularPathBuffer,
-
- pub entry: Option<(Entry, BedrockFilePath)>,
- pub cached_dir: Option<(Entry, BedrockFilePath)>,
-
- pub success: bool,
- pub pointer: u32,
- pub length: u32,
-
- pub enable_read: bool,
- pub enable_write: bool,
- pub enable_create: bool,
- pub enable_move: bool,
- pub enable_delete: bool,
-}
-
-impl FileDevice {
- pub fn new() -> Self {
- #[cfg(target_family = "unix")]
- let default_base: PathBuf = PathBuf::from("/");
- #[cfg(target_family = "windows")]
- let default_base: PathBuf = PathBuf::from("");
-
- Self {
- base_path: default_base,
- default_path: match std::env::current_dir() {
- Ok(dir) => PathBuf::from(dir),
- Err(_) => PathBuf::from(""),
- },
-
- open_buffer: CircularPathBuffer::new(),
- move_buffer: CircularPathBuffer::new(),
- name_buffer: CircularPathBuffer::new(),
-
- entry: None,
- cached_dir: None,
-
- success: false,
- pointer: 0,
- length: 0,
-
- enable_read: true,
- enable_write: true,
- enable_create: true,
- enable_move: true,
- enable_delete: false,
- }
- }
-
- /// Commit any pending writes to the currently-open file.
- pub fn flush_entry(&mut self) {
- if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
- buffered_file.flush();
- }
- }
-
- /// Safely close the currently-open entry, cleaning up entry variables.
- pub fn close_entry(&mut self) {
- self.open_buffer.clear();
- self.move_buffer.clear();
- self.name_buffer.clear();
- self.flush_entry();
- self.pointer = 0;
- self.length = 0;
- if let Some((Entry::Directory(dir), path)) = take(&mut self.entry) {
- self.cached_dir = Some((Entry::Directory(dir), path));
- }
- }
-
- /// Process a byte received from the OPEN port.
- pub fn write_to_open_port(&mut self, byte: u8) {
- if let Some(buffer) = self.open_buffer.push_byte(byte) {
- self.close_entry();
- if let Some(path) = BedrockFilePath::from_buffer(buffer, &self.base_path) {
- self.success = self.open_entry(path).is_ok();
- }
- }
- }
-
- /// Opens the entry at the given path.
- pub fn open_entry(&mut self, path: BedrockFilePath) -> Result<(), ()> {
- match path.entry_type() {
- Some(EntryType::File) => {
- let open_result = std::fs::OpenOptions::new()
- .read(self.enable_read)
- .write(self.enable_write)
- .open(path.as_path());
- // Keep the current entry open if we can't open the new path.
- if let Ok(file) = open_result {
- self.close_entry();
- self.name_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::File(BufferedFile::new(file)), path));
- return Ok(());
- };
- }
- Some(EntryType::Directory) => {
- // Attempt to use the cached directory.
- if let Some((dir, cached_path)) = take(&mut self.cached_dir) {
- if cached_path == path {
- self.close_entry();
- self.name_buffer.populate(cached_path.as_buffer());
- self.entry = Some((dir, cached_path));
- return Ok(());
- }
- }
- // Keep the current entry open if we can't open the new path.
- if let Some(listing) = DirectoryListing::from_path(&path) {
- self.close_entry();
- self.name_buffer.populate(path.as_buffer());
- self.entry = Some((Entry::Directory(listing), path));
- return Ok(());
- };
- }
- // The entry either doesn't exist or is not a file or directory.
- None => (),
- }
- return Err(());
- }
-
- pub fn write_to_move_port(&mut self, byte: u8) {
- if let Some(buffer) = self.move_buffer.push_byte(byte) {
- let blank_destination = buffer[0] == 0x00;
- let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
- self.success = false;
-
- if let Some((_, source)) = &self.entry {
- if blank_destination {
- if self.enable_delete {
- self.success = delete_entry(&source.as_path());
- }
- } else if let Some(dest) = destination {
- if self.enable_move {
- self.success = move_entry(&source.as_path(), &dest.as_path());
- }
- }
- } else if let Some(dest) = destination {
- if self.enable_create {
- self.success = create_file(&dest.as_path());
- }
- }
-
- self.close_entry();
- }
- }
-
- /// Attempt to open the parent directory of the current entry.
- pub fn ascend_to_parent(&mut self) {
- self.success = false;
- if let Some((_, path)) = &self.entry {
- if let Some(parent_path) = path.parent() {
- self.success = self.open_entry(parent_path).is_ok();
- }
- } else {
- if let Some(default) = BedrockFilePath::from_path(&self.default_path, &self.base_path) {
- self.success = self.open_entry(default).is_ok();
- }
- }
- }
-
- /// Attempt to open the currently-selected child of the current directory.
- pub fn descend_to_child(&mut self) {
- self.success = false;
- if let Some((Entry::Directory(listing), _)) = &self.entry {
- if let Some(child_path) = listing.child_path() {
- self.success = self.open_entry(child_path).is_ok();
- };
- }
- }
-
- pub fn set_name_pointer(&mut self, value: u8) {
- self.name_buffer.set_pointer(value);
- }
-
- /// Returns true if the currently-open entry is a directory.
- pub fn entry_type(&self) -> bool {
- match self.entry {
- Some((Entry::Directory(_), _)) => true,
- _ => false,
- }
- }
-
- /// Reads a byte from the name buffer of the currently-selected child.
- pub fn read_child_name(&mut self) -> u8 {
- if let Some((Entry::Directory(listing), _)) = &mut self.entry {
- listing.child_name().read_byte()
- } else {
- 0
- }
- }
-
- pub fn set_child_name_pointer(&mut self, byte: u8) {
- if let Some((Entry::Directory(listing), _)) = &mut self.entry {
- listing.child_name().set_pointer(byte);
- }
- }
-
- /// Returns true if the currently-selected child is a directory.
- pub fn child_type(&self) -> bool {
- if let Some((Entry::Directory(listing), _)) = &self.entry {
- match listing.child_type() {
- Some(EntryType::Directory) => true,
- Some(EntryType::File) => false,
- None => false,
- }
- } else {
- false
- }
- }
-
- /// Reads a byte from the currently-open file.
- pub fn read_byte(&mut self) -> u8 {
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.read_byte(),
- _ => 0,
- }
- }
-
- /// Writes a byte to the currently-open file.
- pub fn write_byte(&mut self, byte: u8) {
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.write_byte(byte),
- _ => (),
- }
- }
-
- pub fn pointer(&mut self) -> u32 {
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.pointer(),
- Some((Entry::Directory(listing), _)) => listing.selected(),
- _ => 0,
- }
- }
-
- pub fn commit_pointer(&mut self) {
- let pointer = take(&mut self.pointer);
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.set_pointer(pointer),
- Some((Entry::Directory(listing), _)) => listing.set_selected(pointer),
- _ => (),
- }
- }
-
- pub fn length(&mut self) -> u32 {
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.length(),
- Some((Entry::Directory(listing), _)) => listing.length(),
- _ => 0,
- }
- }
-
- pub fn commit_length(&mut self) {
- let length = take(&mut self.length);
- match &mut self.entry {
- Some((Entry::File(buffered_file), _)) => buffered_file.set_length(length),
- _ => (),
- }
- }
-}
-
-impl Drop for FileDevice {
- fn drop(&mut self) {
- self.close_entry();
- }
-}
diff --git a/src/devices/file_device.rs b/src/devices/file_device.rs
new file mode 100644
index 0000000..4859053
--- /dev/null
+++ b/src/devices/file_device.rs
@@ -0,0 +1,335 @@
+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};
+
+
+pub struct FileDevice {
+ pub base_path: PathBuf,
+ pub default_path: PathBuf,
+
+ pub entry_buffer: BedrockPathBuffer,
+ pub action_buffer: BedrockPathBuffer,
+ pub path_buffer: BedrockPathBuffer,
+
+ pub entry: Option<(Entry, BedrockFilePath)>,
+ pub cached_dir: Option<(Entry, BedrockFilePath)>,
+
+ pub success: bool,
+ pub pointer_write: u32,
+ pub length_write: u32,
+
+ pub enable_read: bool,
+ pub enable_write: bool,
+ pub enable_create: bool,
+ pub enable_move: bool,
+ pub enable_delete: bool,
+}
+
+impl FileDevice {
+ pub fn new() -> Self {
+ #[cfg(target_family = "unix")]
+ let default_base: PathBuf = PathBuf::from("/");
+ #[cfg(target_family = "windows")]
+ let default_base: PathBuf = PathBuf::from("");
+
+ // TODO: I'm not at all confident that the default path is correct
+ // when not being set as the current directory.
+ Self {
+ base_path: default_base,
+ default_path: match std::env::current_dir() {
+ Ok(dir) => PathBuf::from(dir),
+ Err(_) => PathBuf::from(""),
+ },
+
+ entry_buffer: BedrockPathBuffer::new(),
+ action_buffer: BedrockPathBuffer::new(),
+ path_buffer: BedrockPathBuffer::new(),
+
+ entry: None,
+ cached_dir: None,
+
+ success: false,
+ pointer_write: 0,
+ length_write: 0,
+
+ enable_read: true,
+ enable_write: true,
+ enable_create: true,
+ enable_move: true,
+ enable_delete: false,
+ }
+ }
+
+ /// Safely close the current entry, cleaning up entry variables.
+ pub fn close(&mut self) {
+ self.entry_buffer.clear();
+ self.action_buffer.clear();
+ self.path_buffer.clear();
+ self.flush();
+
+ if let Some((Entry::Directory(dir), path)) = std::mem::take(&mut self.entry) {
+ self.cached_dir = Some((Entry::Directory(dir), path));
+ }
+ }
+
+ /// Open the entry at the given Bedrock path.
+ pub fn open(&mut self, path: BedrockFilePath) -> Result<(), ()> {
+ match path.entry_type() {
+ Some(EntryType::File) => {
+ let open_result = std::fs::OpenOptions::new()
+ .read(self.enable_read)
+ .write(self.enable_write)
+ .open(path.as_path());
+ // Keep the current entry open if we can't open the new path.
+ if let Ok(file) = open_result {
+ self.close();
+ self.path_buffer.populate(path.as_buffer());
+ self.entry = Some((Entry::File(BufferedFile::new(file)), path));
+ return Ok(());
+ };
+ }
+ Some(EntryType::Directory) => {
+ // Attempt to use the cached directory.
+ if let Some((dir, cached_path)) = std::mem::take(&mut self.cached_dir) {
+ if cached_path == path {
+ self.close();
+ self.path_buffer.populate(cached_path.as_buffer());
+ self.entry = Some((dir, cached_path));
+ return Ok(());
+ }
+ }
+ // Keep the current entry open if we can't open the new path.
+ if let Some(listing) = DirectoryListing::from_path(&path) {
+ self.close();
+ self.path_buffer.populate(path.as_buffer());
+ self.entry = Some((Entry::Directory(listing), path));
+ return Ok(());
+ };
+ }
+ // The entry either doesn't exist or is not a file or directory.
+ None => (),
+ }
+ return Err(());
+ }
+
+ /// Process a byte received from the entry port.
+ pub fn write_to_entry_port(&mut self, byte: u8) {
+ if let Some(buffer) = self.entry_buffer.write(byte) {
+ self.close();
+ match BedrockFilePath::from_buffer(buffer, &self.base_path) {
+ Some(path) => self.success = self.open(path).is_ok(),
+ None => self.success = false,
+ };
+ }
+ }
+
+ /// Process a byte received from the action port.
+ pub fn write_to_action_port(&mut self, byte: u8) {
+ if let Some(buffer) = self.action_buffer.write(byte) {
+ let destination_blank = buffer[0] == 0x00;
+ let destination = BedrockFilePath::from_buffer(buffer, &self.base_path);
+ self.success = false;
+
+ if let Some((_, source)) = &self.entry {
+ if destination_blank {
+ if self.enable_delete {
+ self.success = delete_entry(&source.as_path());
+ }
+ } else if let Some(dest) = destination {
+ if self.enable_move {
+ self.success = move_entry(&source.as_path(), &dest.as_path());
+ }
+ }
+ } else if let Some(dest) = destination {
+ if self.enable_create {
+ self.success = create_file(&dest.as_path());
+ }
+ }
+ self.close();
+ }
+ }
+
+ /// Attempt to open the parent directory of the current entry.
+ pub fn ascend_to_parent(&mut self) {
+ if let Some((_, path)) = &self.entry {
+ match path.parent() {
+ Some(parent) => self.success = self.open(parent).is_ok(),
+ None => self.success = false,
+ };
+ } else {
+ match BedrockFilePath::from_path(&self.default_path, &self.base_path) {
+ Some(default) => self.success = self.open(default).is_ok(),
+ None => self.success = false,
+ };
+ }
+ }
+
+ /// Attempt to open the selected child of the current directory.
+ pub fn descend_to_child(&mut self) {
+ if let Some((Entry::Directory(dir), _)) = &self.entry {
+ match dir.child_path() {
+ Some(child) => self.success = self.open(child).is_ok(),
+ None => self.success = false,
+ };
+ } else {
+ self.success = false;
+ }
+ }
+
+ /// Return true if the current entry is a directory.
+ pub fn entry_type(&self) -> bool {
+ match self.entry {
+ Some((Entry::Directory(_), _)) => true,
+ _ => false,
+ }
+ }
+
+ /// Read a byte from the path buffer of the selected child.
+ pub fn read_child_path(&mut self) -> u8 {
+ match &mut self.entry {
+ Some((Entry::Directory(dir), _)) => dir.child_path_buffer().read(),
+ _ => 0,
+ }
+ }
+
+ pub fn set_child_path(&mut self, byte: u8) {
+ if let Some((Entry::Directory(dir), _)) = &mut self.entry {
+ dir.child_path_buffer().set_pointer(byte);
+ }
+ }
+
+ /// Return true if the selected child is a directory.
+ pub fn child_type(&self) -> bool {
+ match &self.entry {
+ Some((Entry::Directory(dir), _)) => match dir.child_type() {
+ Some(EntryType::Directory) => true,
+ _ => false,
+ }
+ _ => false,
+ }
+ }
+
+ /// Read a byte from the current file.
+ pub fn read_byte(&mut self) -> u8 {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.read(),
+ _ => 0,
+ }
+ }
+
+ /// Writes a byte to the currently-open file.
+ pub fn write_byte(&mut self, byte: u8) {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.write(byte),
+ _ => (),
+ }
+ }
+
+ pub fn pointer(&mut self) -> u32 {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.pointer(),
+ Some((Entry::Directory(dir), _)) => dir.selected(),
+ _ => 0,
+ }
+ }
+
+ pub fn commit_pointer(&mut self) {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.set_pointer(self.pointer_write),
+ Some((Entry::Directory(dir), _)) => dir.set_selected(self.pointer_write),
+ _ => (),
+ }
+ }
+
+ pub fn length(&mut self) -> u32 {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.length(),
+ Some((Entry::Directory(dir), _)) => dir.length(),
+ _ => 0,
+ }
+ }
+
+ pub fn commit_length(&mut self) {
+ match &mut self.entry {
+ Some((Entry::File(file), _)) => file.set_length(self.length_write),
+ _ => (),
+ }
+ }
+
+ pub fn flush(&mut self) {
+ if let Some((Entry::File(buffered_file), _)) = &mut self.entry {
+ buffered_file.flush();
+ }
+ }
+}
+
+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!(),
+ }
+ }
+
+ 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),
+ 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),
+ _ => unreachable!(),
+ };
+ return None;
+ }
+
+ fn wake(&mut self) -> bool {
+ false
+ }
+}
diff --git a/src/devices/file/bedrock_file_path.rs b/src/devices/file_device/bedrock_file_path.rs
index 30aa803..fdd8f79 100644
--- a/src/devices/file/bedrock_file_path.rs
+++ b/src/devices/file_device/bedrock_file_path.rs
@@ -6,7 +6,9 @@ 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>,
@@ -249,7 +251,7 @@ impl Ord for BedrockFilePath {
}
}
-// Compare two ASCII byte-slices in case-agnostic alphabetic order.
+/// 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];
@@ -267,10 +269,13 @@ fn compare_ascii_slices(left: &[u8], right: &[u8]) -> Ordering {
left.len().cmp(&right.len())
}
-// Remap ASCII values so that they sort in case-agnostic alphabetic order:
-// !"#$%&'()*+,-./0123456789:;<=>?
-// @`AaBbCcDdEeFfGgHhIiJjKkLlMmNnOo
-// PpQqRrSsTtUuVvWwXxYyZz[{\|]}^~_
+/// 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
diff --git a/src/devices/file/circular_path_buffer.rs b/src/devices/file_device/bedrock_path_buffer.rs
index 9d1dea6..d6a0861 100644
--- a/src/devices/file/circular_path_buffer.rs
+++ b/src/devices/file_device/bedrock_path_buffer.rs
@@ -1,9 +1,9 @@
-pub struct CircularPathBuffer {
+pub struct BedrockPathBuffer {
buffer: [u8; 256],
pointer: u8,
}
-impl CircularPathBuffer {
+impl BedrockPathBuffer {
pub fn new() -> Self {
Self { buffer: [0; 256] , pointer: 0 }
}
@@ -20,7 +20,7 @@ impl CircularPathBuffer {
self.buffer = buffer;
}
- /// Move internal pointer to either the start of the path or the file name.
+ /// 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.
@@ -39,7 +39,7 @@ impl CircularPathBuffer {
}
/// Read a single byte from the buffer.
- pub fn read_byte(&mut self) -> u8 {
+ pub fn read(&mut self) -> u8 {
let pointer = self.pointer as usize;
self.pointer = self.pointer.wrapping_add(1);
self.buffer[pointer]
@@ -48,12 +48,12 @@ impl CircularPathBuffer {
/// Write a single byte to the buffer.
///
/// If a null-byte is written, the buffer will be cleared and returned.
- pub fn push_byte(&mut self, byte: u8) -> Option<[u8; 256]> {
+ 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.wrapping_add(1);
+ self.pointer = self.pointer.saturating_add(1);
None
}
}
diff --git a/src/devices/file/buffered_file.rs b/src/devices/file_device/buffered_file.rs
index 73d3536..3487d54 100644
--- a/src/devices/file/buffered_file.rs
+++ b/src/devices/file_device/buffered_file.rs
@@ -3,6 +3,8 @@ use std::io::{BufReader, BufWriter};
use std::io::{Read, Write};
use std::io::{ErrorKind, Seek, SeekFrom};
+use crate::*;
+
pub struct BufferedFile {
file: AccessMode,
@@ -15,17 +17,11 @@ impl BufferedFile {
}
}
- pub fn flush(&mut self) {
- if let AccessMode::Write(writer) = &mut self.file {
- writer.flush().unwrap();
- }
- }
-
pub fn close(&mut self) {
self.file = AccessMode::None;
}
- pub fn read_byte(&mut self) -> u8 {
+ pub fn read(&mut self) -> u8 {
let mut buffer = [0u8; 1];
let read_result = match &mut self.file {
@@ -46,12 +42,12 @@ impl BufferedFile {
Ok(_) => buffer[0],
Err(error) => match error.kind() {
ErrorKind::UnexpectedEof => 0,
- _ => panic!("{error:?}"),
+ _ => { error!("BufferedFile::read", "{error:?}"); 0 },
}
}
}
- pub fn write_byte(&mut self, byte: u8) {
+ pub fn write(&mut self, byte: u8) {
let mut buffer = [byte; 1];
let write_result = match &mut self.file {
@@ -113,8 +109,19 @@ impl BufferedFile {
AccessMode::None => unreachable!(),
};
}
+
+ pub fn flush(&mut self) {
+ if let AccessMode::Write(writer) = &mut self.file {
+ writer.flush().unwrap();
+ }
+ }
}
+impl Drop for BufferedFile {
+ fn drop(&mut self) {
+ self.flush()
+ }
+}
enum AccessMode {
Read(BufReader<File>),
diff --git a/src/devices/file/directory_listing.rs b/src/devices/file_device/directory_listing.rs
index febc5c2..1d7ddd2 100644
--- a/src/devices/file/directory_listing.rs
+++ b/src/devices/file_device/directory_listing.rs
@@ -5,7 +5,7 @@ pub struct DirectoryListing {
children: Vec<BedrockFilePath>,
length: u32,
selected: Option<u32>,
- name_buffer: CircularPathBuffer,
+ child_path_buffer: BedrockPathBuffer,
}
@@ -46,8 +46,8 @@ impl DirectoryListing {
children.sort();
let length = u32::try_from(children.len()).ok()?;
let selected = None;
- let name_buffer = CircularPathBuffer::new();
- Some( Self { children, length, selected, name_buffer } )
+ let child_path_buffer = BedrockPathBuffer::new();
+ Some( Self { children, length, selected, child_path_buffer } )
}
/// Generate entries for a virtual root directory.
@@ -70,8 +70,8 @@ impl DirectoryListing {
let length = children.len() as u32;
let selected = None;
- let name_buffer = CircularPathBuffer::new();
- Self { children, length, selected, name_buffer }
+ let child_path_buffer = BedrockPathBuffer::new();
+ Self { children, length, selected, child_path_buffer }
}
/// Attempts to return a directory child by index.
@@ -92,16 +92,16 @@ impl DirectoryListing {
pub fn set_selected(&mut self, index: u32) {
if let Some(child) = self.get(index) {
let buffer = child.as_buffer();
- self.name_buffer.populate(buffer);
+ self.child_path_buffer.populate(buffer);
self.selected = Some(index);
} else {
- self.name_buffer.clear();
+ self.child_path_buffer.clear();
self.selected = None;
}
}
- pub fn child_name(&mut self) -> &mut CircularPathBuffer {
- &mut self.name_buffer
+ pub fn child_path_buffer(&mut self) -> &mut BedrockPathBuffer {
+ &mut self.child_path_buffer
}
pub fn child_type(&self) -> Option<EntryType> {
diff --git a/src/devices/file/entry.rs b/src/devices/file_device/entry.rs
index d604bb7..d604bb7 100644
--- a/src/devices/file/entry.rs
+++ b/src/devices/file_device/entry.rs
diff --git a/src/devices/file/operations.rs b/src/devices/file_device/operations.rs
index 0593ac8..3a3f81b 100644
--- a/src/devices/file/operations.rs
+++ b/src/devices/file_device/operations.rs
@@ -2,9 +2,25 @@ use std::io::ErrorKind;
use std::path::Path;
-/// Returns true if an entry already exists at the given path.
-pub fn entry_exists(source: &Path) -> bool {
- std::fs::metadata(source).is_ok()
+/// 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.
@@ -25,23 +41,7 @@ pub fn delete_entry(source: &Path) -> bool {
}
}
-/// 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()
-}
-
-/// 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()
- }
+/// 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.rs b/src/devices/input.rs
deleted file mode 100644
index 462f569..0000000
--- a/src/devices/input.rs
+++ /dev/null
@@ -1,164 +0,0 @@
-use crate::*;
-use phosphor::*;
-use std::collections::VecDeque;
-
-const CONTROL: u8 = 0x80;
-const ALT: u8 = 0x40;
-const SHIFT: u8 = 0x20;
-
-const UP: u8 = 0x80;
-const DOWN: u8 = 0x40;
-const LEFT: u8 = 0x20;
-const RIGHT: u8 = 0x10;
-const CONFIRM: u8 = 0x08;
-const CANCEL: u8 = 0x04;
-const NEXT: u8 = 0x02;
-const PREVIOUS: u8 = 0x01;
-
-macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; }
-
-
-pub struct InputDevice {
- pub wake_flag: bool,
-
- pub pointer_position: ScreenPosition,
- pub pointer_buttons: u8,
- pub pointer_active: bool,
-
- pub horizontal_scroll: i8,
- pub vertical_scroll: i8,
- pub horizontal_scroll_delta: f64,
- pub vertical_scroll_delta: f64,
-
- pub text_queue: VecDeque<u8>,
- pub modifiers: u8,
- pub navigation: u8,
-
- pub controller_1: u8,
- pub controller_2: u8,
- pub controller_3: u8,
- pub controller_4: u8,
-}
-
-impl InputDevice {
- pub fn new() -> Self {
- Self {
- wake_flag: false,
-
- pointer_position: ScreenPosition::ZERO,
- pointer_buttons: 0x00,
- pointer_active: false,
-
- horizontal_scroll: 0x00,
- vertical_scroll: 0x00,
- horizontal_scroll_delta: 0.0,
- vertical_scroll_delta: 0.0,
-
- text_queue: VecDeque::new(),
- modifiers: 0x00,
- navigation: 0x00,
-
- controller_1: 0x00,
- controller_2: 0x00,
- controller_3: 0x00,
- controller_4: 0x00,
- }
- }
-
- pub fn read_horizontal_scroll(&mut self) -> u8 {
- std::mem::take(&mut self.horizontal_scroll) as u8
- }
-
- pub fn read_vertical_scroll(&mut self) -> u8 {
- std::mem::take(&mut self.vertical_scroll) as u8
- }
-
- pub fn on_pointer_button(&mut self, mask: u8, action: Action) {
- let new_buttons = match action {
- Action::Pressed => self.pointer_buttons | mask,
- Action::Released => self.pointer_buttons & !mask,
- };
- if new_buttons != self.pointer_buttons {
- self.pointer_buttons = new_buttons;
- self.wake_flag = true;
- }
- }
-
- pub fn on_pointer_move(&mut self, position: ScreenPosition) {
- if position != self.pointer_position {
- self.pointer_position = position;
- self.wake_flag = true;
- }
- }
-
- pub fn on_scroll_horizontal(&mut self, delta: f64) {
- self.horizontal_scroll_delta += delta;
- while self.horizontal_scroll_delta >= 1.0 {
- self.horizontal_scroll = self.horizontal_scroll.saturating_add(1);
- self.horizontal_scroll_delta -= 1.0;
- self.wake_flag = true;
- }
- while self.horizontal_scroll_delta <= -1.0 {
- self.horizontal_scroll = self.horizontal_scroll.saturating_sub(1);
- self.horizontal_scroll_delta += 1.0;
- self.wake_flag = true;
- }
- }
-
- pub fn on_scroll_vertical(&mut self, delta: f64) {
- self.vertical_scroll_delta += delta;
- while self.vertical_scroll_delta >= 1.0 {
- self.vertical_scroll = self.vertical_scroll.saturating_add(1);
- self.vertical_scroll_delta -= 1.0;
- self.wake_flag = true;
- }
- while self.vertical_scroll_delta <= -1.0 {
- self.vertical_scroll = self.vertical_scroll.saturating_sub(1);
- self.vertical_scroll_delta += 1.0;
- self.wake_flag = true;
- }
- }
-
- pub fn on_character_input(&mut self, input: char) {
- if let Ok(byte) = u8::try_from(u32::from(input)) {
- self.text_queue.push_back(byte);
- self.wake_flag = true;
- }
- }
-
- pub fn on_keyboard_input(&mut self, input: KeyboardInput) {
- let mask = match input.key {
- KeyCode::Up => UP,
- KeyCode::Down => DOWN,
- KeyCode::Left => LEFT,
- KeyCode::Right => RIGHT,
- KeyCode::Return => CONFIRM,
- KeyCode::Escape => CANCEL,
- KeyCode::Tab => match test!(self.modifiers, SHIFT) {
- false => NEXT,
- true => PREVIOUS
- },
- _ => return,
- };
- let navigation = match input.action {
- Action::Pressed => self.navigation | mask,
- Action::Released => self.navigation & !mask,
- };
- if navigation != self.navigation {
- self.navigation = navigation;
- self.wake_flag = true;
- }
- }
-
- pub fn on_modifier_change(&mut self, modifiers: ModifiersState) {
- let mut new_modifiers = 0x00;
- if modifiers.ctrl() { new_modifiers |= CONTROL }
- if modifiers.alt() { new_modifiers |= ALT }
- if modifiers.shift() { new_modifiers |= SHIFT }
- if new_modifiers != self.modifiers {
- self.modifiers = new_modifiers;
- self.wake_flag = true;
- }
-
- }
-}
diff --git a/src/devices/input_device.rs b/src/devices/input_device.rs
new file mode 100644
index 0000000..638c277
--- /dev/null
+++ b/src/devices/input_device.rs
@@ -0,0 +1,233 @@
+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 x_read: u16,
+ pub y_read: u16,
+
+ pub h_scroll: i8,
+ pub v_scroll: i8,
+ pub h_scroll_delta: f32,
+ pub v_scroll_delta: f32,
+
+ pub keyboard_active: bool,
+ pub characters: VecDeque<u8>,
+ pub navigation: u8,
+ pub modifiers: u8,
+
+ pub gamepad_1: u8,
+ pub gamepad_2: u8,
+ pub gamepad_3: u8,
+ pub gamepad_4: u8,
+}
+
+impl InputDevice {
+ pub fn new() -> Self {
+ Self {
+ wake: false,
+ accessed: false,
+
+ pointer_active: false,
+ pointer_buttons: 0,
+
+ position: ScreenPosition::ZERO,
+ x_read: 0,
+ y_read: 0,
+
+ h_scroll: 0,
+ v_scroll: 0,
+ h_scroll_delta: 0.0,
+ v_scroll_delta: 0.0,
+
+ keyboard_active: true,
+ characters: VecDeque::new(),
+ modifiers: 0,
+ navigation: 0,
+
+ gamepad_1: 0,
+ gamepad_2: 0,
+ gamepad_3: 0,
+ gamepad_4: 0,
+ }
+ }
+
+ pub fn on_cursor_enter(&mut self) {
+ self.pointer_active = true;
+ self.wake = true;
+ }
+
+ pub fn on_cursor_exit(&mut self) {
+ self.pointer_active = false;
+ self.wake = true;
+ }
+
+ pub fn on_cursor_move(&mut self, position: Position) {
+ let screen_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;
+ self.wake = true;
+ }
+ }
+
+ pub fn on_mouse_button(&mut self, button: MouseButton, action: Action) {
+ let mask = match button {
+ MouseButton::Left => 0x80,
+ MouseButton::Middle => 0x40,
+ MouseButton::Right => 0x20,
+ };
+ let pointer_buttons = match action {
+ Action::Pressed => self.pointer_buttons | mask,
+ Action::Released => self.pointer_buttons & !mask,
+ };
+ if self.pointer_buttons != pointer_buttons {
+ self.pointer_buttons = pointer_buttons;
+ self.wake = true;
+ }
+ }
+
+ fn_on_scroll!(on_horizontal_scroll(h_scroll, h_scroll_delta));
+ fn_on_scroll!(on_vertical_scroll(v_scroll, v_scroll_delta));
+
+ pub fn read_horizontal_scroll(&mut self) -> u8 {
+ std::mem::take(&mut self.h_scroll) as u8
+ }
+
+ pub fn read_vertical_scroll(&mut self) -> u8 {
+ std::mem::take(&mut self.v_scroll) as u8
+ }
+
+ pub fn on_character(&mut self, character: char) {
+ let character = match character {
+ '\r' => '\n',
+ _ => character,
+ };
+ let mut bytes = [0; 4];
+ let string = character.encode_utf8(&mut bytes);
+ for byte in string.bytes() {
+ self.characters.push_back(byte);
+ }
+ self.wake = true;
+ }
+
+ pub fn on_keypress(&mut self, key: KeyCode, action: Action) {
+ let shift = self.modifiers & 0x40 != 0;
+ let mask = match key {
+ KeyCode::ArrowUp => 0x80, // up
+ KeyCode::ArrowDown => 0x40, // down
+ KeyCode::ArrowLeft => 0x20, // left
+ KeyCode::ArrowRight => 0x10, // right
+ KeyCode::Enter => 0x08, // confirm
+ KeyCode::Escape => 0x04, // cancel
+ KeyCode::Tab => match shift { // shift
+ false => 0x02, // next
+ true => 0x01 // previous
+ },
+ _ => return,
+ };
+ let navigation = match action {
+ Action::Pressed => self.navigation | mask,
+ Action::Released => self.navigation & !mask,
+ };
+ if self.navigation != navigation {
+ self.navigation = navigation;
+ self.wake = true;
+ }
+ }
+
+ pub fn on_modifier(&mut self, state: ModifiersState) {
+ let mut modifiers = 0;
+ if state.control_key() { modifiers |= 0x80 }
+ if state.shift_key() { modifiers |= 0x40 }
+ if state.alt_key() { modifiers |= 0x20 }
+ if state.super_key() { modifiers |= 0x10 }
+ if self.modifiers != modifiers {
+ self.modifiers = modifiers;
+ self.wake = true;
+ }
+ }
+}
+
+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/local_device.rs b/src/devices/local_device.rs
new file mode 100644
index 0000000..14a8f71
--- /dev/null
+++ b/src/devices/local_device.rs
@@ -0,0 +1,230 @@
+use crate::*;
+
+use bedrock_core::*;
+
+use std::collections::VecDeque;
+use std::io::{BufRead, Stdout, Write};
+use std::sync::mpsc::{self, TryRecvError};
+
+
+pub struct LocalDevice {
+ wake: bool,
+
+ stdin_connected: bool,
+ stdin_control: bool,
+ stdin_rx: mpsc::Receiver<Vec<u8>>,
+ stdin_queue: VecDeque<u8>,
+ stdin_excess: VecDeque<u8>,
+ stdout: Stdout,
+
+ stdout_connected: bool,
+
+ decode_stdin: bool,
+ encode_stdout: bool,
+ decode_buffer: Option<u8>,
+}
+
+impl LocalDevice {
+ pub fn new(config: &EmulatorConfig) -> Self {
+ // Fill input queue with initial transmission.
+ let mut stdin_queue = VecDeque::new();
+ if let Some(bytes) = &config.initial_transmission {
+ for byte in bytes { stdin_queue.push_front(*byte) }
+ }
+ // Spawn a thread to enable non-blocking reads of stdin.
+ let (stdin_tx, stdin_rx) = std::sync::mpsc::channel();
+ std::thread::spawn(move || loop {
+ let mut stdin = std::io::stdin().lock();
+ match stdin.fill_buf() {
+ Ok(buf) if !buf.is_empty() => {
+ let length = buf.len();
+ stdin_tx.send(buf.to_vec()).unwrap();
+ stdin.consume(length);
+ }
+ _ => break,
+ };
+ });
+
+ Self {
+ wake: true,
+
+ stdin_connected: true,
+ stdin_control: false,
+ stdin_rx,
+ stdin_queue,
+ stdin_excess: VecDeque::new(),
+ stdout: std::io::stdout(),
+
+ stdout_connected: true,
+
+ decode_stdin: config.encode_stdin,
+ encode_stdout: config.encode_stdout,
+ decode_buffer: None,
+ }
+ }
+
+ pub fn stdin_length(&mut self) -> u8 {
+ self.fetch_stdin_data();
+ self.stdin_queue.len().try_into().unwrap_or(u8::MAX)
+ }
+
+ pub fn stdin_enable(&mut self) {
+ if !self.stdin_control {
+ self.stdin_queue.clear();
+ self.stdin_control = true;
+ }
+ }
+
+ pub fn stdin_read(&mut self) -> u8 {
+ self.fetch_stdin_data();
+ self.stdin_queue.pop_front().unwrap_or(0)
+ }
+
+ pub fn stdout_write(&mut self, value: u8) {
+ macro_rules! hex {
+ ($value:expr) => { match $value {
+ 0x0..=0x9 => $value + b'0',
+ 0xa..=0xf => $value - 0x0a + b'a',
+ _ => unreachable!("Cannot encode value as hex digit: 0x{:02x}", $value),
+ } };
+ }
+ if self.encode_stdout {
+ let encoded = [hex!(value >> 4), hex!(value & 0xf), b' '];
+ self.stdout_write_raw(&encoded);
+ } else {
+ self.stdout_write_raw(&[value]);
+ };
+ }
+
+ fn stdout_write_raw(&mut self, bytes: &[u8]) {
+ if let Err(_) = self.stdout.write_all(bytes) {
+ if self.stdout_connected {
+ self.stdout_connected = false;
+ self.wake = true; // wake because stdout was disconnected.
+ }
+ }
+ }
+
+ pub fn stdout_disable(&mut self) {
+ if self.encode_stdout {
+ self.stdout_write_raw(&[b'\n']);
+ }
+ }
+
+ pub fn fetch_stdin_data(&mut self) {
+ while self.stdin_control {
+ match self.stdin_excess.pop_front() {
+ Some(byte) => self.fetch_byte(byte),
+ None => break,
+ }
+ }
+ match self.stdin_rx.try_recv() {
+ Ok(tx) => {
+ for byte in tx {
+ match self.stdin_control {
+ true => self.fetch_byte(byte),
+ false => self.stdin_excess.push_back(byte),
+ }
+ }
+ }
+ Err(TryRecvError::Empty) => (),
+ Err(TryRecvError::Disconnected) => {
+ self.stdin_control = false;
+ if self.stdin_connected {
+ self.stdin_connected = false;
+ self.wake = true; // wake because stdin was disconnected.
+ }
+ }
+ }
+ }
+
+ fn fetch_byte(&mut self, byte: u8) {
+ if self.decode_stdin {
+ let decoded = match byte {
+ b'0'..=b'9' => byte - b'0',
+ b'a'..=b'f' => byte - b'a' + 0x0a,
+ b'A'..=b'F' => byte - b'A' + 0x0a,
+ b'\n' => {
+ self.decode_buffer = None;
+ self.stdin_control = false;
+ self.wake = true; // wake because a transmission ended.
+ return;
+ },
+ _ => return,
+ };
+ if let Some(high) = std::mem::take(&mut self.decode_buffer) {
+ self.stdin_queue.push_back((high << 4) | decoded);
+ self.wake = true; // wake because a byte was received.
+ } else {
+ self.decode_buffer = Some(decoded);
+ }
+ } else {
+ self.stdin_queue.push_back(byte);
+ self.wake = true; // wake because a byte was received.
+ }
+ }
+
+ pub fn flush(&mut self) {
+ self.stdout.flush().unwrap();
+ }
+}
+
+
+impl Drop for LocalDevice {
+ fn drop(&mut self) {
+ self.flush();
+ }
+}
+
+
+impl Device for LocalDevice {
+ fn read(&mut self, port: u8) -> u8 {
+ match port {
+ 0x0 => read_b!(self.stdin_connected),
+ 0x1 => 0xff,
+ 0x2 => read_b!(self.stdin_control),
+ 0x3 => 0xff,
+ 0x4 => self.stdin_length(),
+ 0x5 => 0xff,
+ 0x6 => self.stdin_read(),
+ 0x7 => self.stdin_read(),
+ 0x8 => todo!(),
+ 0x9 => todo!(),
+ 0xa => todo!(),
+ 0xb => todo!(),
+ 0xc => todo!(),
+ 0xd => todo!(),
+ 0xe => todo!(),
+ 0xf => todo!(),
+ _ => unreachable!(),
+ }
+ }
+
+ fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ match port {
+ 0x0 => (),
+ 0x1 => (),
+ 0x2 => self.stdin_enable(),
+ 0x3 => self.stdout_disable(),
+ 0x4 => self.stdin_queue.clear(),
+ 0x5 => (),
+ 0x6 => self.stdout_write(value),
+ 0x7 => self.stdout_write(value),
+ 0x8 => todo!(),
+ 0x9 => todo!(),
+ 0xa => todo!(),
+ 0xb => todo!(),
+ 0xc => todo!(),
+ 0xd => todo!(),
+ 0xe => todo!(),
+ 0xf => todo!(),
+ _ => unreachable!(),
+ };
+ return None;
+ }
+
+ fn wake(&mut self) -> bool {
+ self.fetch_stdin_data();
+ std::mem::take(&mut self.wake)
+ }
+}
diff --git a/src/devices/math.rs b/src/devices/math.rs
deleted file mode 100644
index cefb572..0000000
--- a/src/devices/math.rs
+++ /dev/null
@@ -1,31 +0,0 @@
-pub struct MathDevice {
- pub operand_1: u16,
- pub operand_2: u16,
-}
-
-impl MathDevice {
- pub fn new() -> Self {
- Self {
- operand_1: 0x0000,
- operand_2: 0x0000,
- }
- }
-
- pub fn multiply_high(&mut self) -> u16 {
- let (_, high) = self.operand_1.widening_mul(self.operand_2);
- return high;
- }
-
- pub fn multiply_low(&mut self) -> u16 {
- let (low, _) = self.operand_1.widening_mul(self.operand_2);
- return low;
- }
-
- pub fn divide(&mut self) -> u16 {
- self.operand_1.checked_div(self.operand_2).unwrap_or(0)
- }
-
- pub fn modulo(&mut self) -> u16 {
- self.operand_1.checked_rem(self.operand_2).unwrap_or(0)
- }
-}
diff --git a/src/devices/math_device.rs b/src/devices/math_device.rs
new file mode 100644
index 0000000..015545e
--- /dev/null
+++ b/src/devices/math_device.rs
@@ -0,0 +1,141 @@
+use bedrock_core::*;
+
+
+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 quot: Option<u16>,
+ pub rem: Option<u16>,
+}
+
+impl MathDevice {
+ pub fn new() -> Self {
+ Self {
+ op1: 0,
+ op2: 0,
+
+ sqrt: None,
+ atan: None,
+ prod: None,
+ quot: None,
+ rem: None,
+ }
+ }
+
+ pub fn clear(&mut self) {
+ self.sqrt = None;
+ self.atan = None;
+ self.prod = None;
+ self.quot = None;
+ self.rem = None;
+ }
+
+ pub fn atan(&mut self) -> u16 {
+ match self.atan {
+ Some(atan) => atan,
+ 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()
+ }
+ }
+ }
+
+ pub fn sqrt(&mut self) -> u16 {
+ match self.sqrt {
+ Some(sqrt) => sqrt,
+ None => {
+ let input = ((self.op1 as u32) << 16) | (self.op2 as u32);
+ self.sqrt = Some((input as f64).sqrt() as u16);
+ self.sqrt.unwrap()
+ }
+ }
+ }
+
+ pub fn prod(&mut self) -> (u16, u16) {
+ match self.prod {
+ Some(prod) => prod,
+ None => {
+ self.prod = Some(self.op1.widening_mul(self.op2));
+ self.prod.unwrap()
+ }
+ }
+ }
+
+ pub fn quot(&mut self) -> u16 {
+ match self.quot {
+ Some(quot) => quot,
+ None => {
+ self.quot = Some(self.op1.checked_div(self.op2).unwrap_or(0));
+ self.quot.unwrap()
+ }
+ }
+ }
+
+ pub fn rem(&mut self) -> u16 {
+ match self.rem {
+ Some(rem) => rem,
+ None => {
+ self.rem = Some(self.op1.checked_rem(self.op2).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.rs b/src/devices/memory.rs
deleted file mode 100644
index 56125d2..0000000
--- a/src/devices/memory.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-macro_rules! then_inc { ($v:expr) => {{ $v = $v.wrapping_add(1); $v as usize }}; }
-
-type Page = [u8; 65536];
-fn new_blank_page() -> [u8; 65536] { [0; 65536] }
-
-
-pub struct MemoryDevice {
- // Hard limit on the number of pages which can be allocated
- pub page_limit: u16,
- // Pages which have actually been allocated
- pub pages: Vec<Page>,
-
- pub page_1: u16,
- pub address_1: u16,
- pub page_2: u16,
- pub address_2: u16,
- pub copy_length: u16,
-}
-
-impl MemoryDevice {
- pub fn new() -> Self {
- Self {
- page_limit: 0,
- pages: Vec::new(),
-
- page_1: 0,
- address_1: 0,
- page_2: 0,
- address_2: 0,
- copy_length: 0,
- }
- }
-
- pub fn copy(&mut self) {
- let count = std::cmp::max(self.page_1, self.page_2) as usize + 1;
- if count <= self.page_limit as usize {
- if self.pages.len() < count {
- self.pages.resize_with(count, new_blank_page);
- }
- let p1 = self.page_1 as usize;
- let p2 = self.page_2 as usize;
- let a1 = self.address_1;
- let a2 = self.address_2;
-
- for i in 0..=self.copy_length {
- let byte = self.pages[p2][a2.wrapping_add(i) as usize];
- self.pages[p1][a1.wrapping_add(i) as usize] = byte;
- }
- }
- }
-
- pub fn read_from_head_1(&mut self) -> u8 {
- let address = then_inc!(self.address_1);
- if let Some(page) = self.pages.get(self.page_1 as usize) {
- page[address]
- } else {
- 0x00
- }
- }
-
- pub fn read_from_head_2(&mut self) -> u8 {
- let address = then_inc!(self.address_2);
- if let Some(page) = self.pages.get(self.page_2 as usize) {
- page[address]
- } else {
- 0x00
- }
- }
-
- pub fn write_to_head_1(&mut self, byte: u8) {
- let address = then_inc!(self.address_1);
- let page = self.page_1 as usize;
- if self.page_limit as usize > page {
- if self.pages.len() <= page {
- self.pages.resize_with(page + 1, new_blank_page);
- }
- self.pages[page][address] = byte;
- }
- }
-
- pub fn write_to_head_2(&mut self, byte: u8) {
- let address = then_inc!(self.address_1);
- let page = self.page_2 as usize;
- if self.page_limit as usize > page {
- if self.pages.len() <= page {
- self.pages.resize_with(page + 1, new_blank_page);
- }
- self.pages[page][address] = byte;
- }
- }
-}
diff --git a/src/devices/memory_device.rs b/src/devices/memory_device.rs
new file mode 100644
index 0000000..7c1aeda
--- /dev/null
+++ b/src/devices/memory_device.rs
@@ -0,0 +1,152 @@
+use bedrock_core::*;
+
+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,
+ }
+ }
+ };
+}
+
+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;
+ }
+ }
+ }
+ };
+}
+
+
+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 offset_1: u16,
+ pub address_1: u16,
+ pub offset_2: u16,
+ pub address_2: u16,
+
+ pub copy_length: u16,
+}
+
+impl MemoryDevice {
+ pub fn new() -> Self {
+ Self {
+ limit: 0,
+ requested: 0,
+ provisioned: 0,
+ pages: Vec::new(),
+
+ offset_1: 0,
+ address_1: 0,
+ offset_2: 0,
+ address_2: 0,
+
+ copy_length: 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 provision(&mut self) {
+ self.provisioned = std::cmp::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 count = self.copy_length as usize;
+
+ // Pre-allocate destination pages as needed.
+ let pages_needed = std::cmp::min(dest + count, self.provisioned);
+ if pages_needed > self.pages.len() {
+ self.pages.resize(pages_needed, [0; 256]);
+ }
+
+ for i in 0..count {
+ let src_page = match self.pages.get(src + i) {
+ Some(src_page) => src_page.to_owned(),
+ None => [0; 256],
+ };
+ match self.pages.get_mut(dest + i) {
+ Some(dest) => *dest = src_page,
+ None => break,
+ };
+ }
+ }
+}
+
+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;
+ }
+
+ fn wake(&mut self) -> bool {
+ false
+ }
+}
diff --git a/src/devices/remote_device.rs b/src/devices/remote_device.rs
new file mode 100644
index 0000000..f50ac7a
--- /dev/null
+++ b/src/devices/remote_device.rs
@@ -0,0 +1,35 @@
+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.rs b/src/devices/screen.rs
deleted file mode 100644
index a61d8b3..0000000
--- a/src/devices/screen.rs
+++ /dev/null
@@ -1,258 +0,0 @@
-mod sprite_data;
-mod draw_line;
-mod draw_rect;
-mod draw_sprite;
-
-pub use sprite_data::*;
-use geometry::HasDimensions;
-use phosphor::*;
-use std::cmp::{min, max, Ordering};
-use std::iter::zip;
-
-pub type ScreenDimensions = geometry::Dimensions<u16>;
-pub type ScreenPosition = geometry::Point<u16>;
-pub type Plane = [u8; 8];
-pub type Sprite = [[u8; 8]; 8];
-
-const TRANSPARENT: u8 = 0x08;
-const FLIP_DIAGONAL: u8 = 0x04;
-const FLIP_VERTICAL: u8 = 0x02;
-const FLIP_HORIZONTAL: u8 = 0x01;
-const NEGATIVE: u8 = 0x80;
-const VERTICAL: u8 = 0x40;
-
-
-#[derive(Copy, Clone)]
-pub enum ScreenLayer { Background, Foreground }
-
-macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; }
-
-
-pub struct ScreenDevice {
- pub wake_flag: bool,
-
- /// Each byte represents a screen pixel, left-to-right and top-to-bottom.
- // Only the bottom four bits of each byte are used.
- pub foreground: Vec<u8>,
- pub background: Vec<u8>,
- pub dirty: bool,
- pub resizable: bool,
-
- pub cursor: ScreenPosition,
- pub vector: ScreenPosition,
- pub dimensions: ScreenDimensions,
-
- pub palette_high: u8,
- pub palette: [Colour; 16],
- pub sprite_buffer: SpriteBuffer,
-}
-
-impl ScreenDevice {
- pub fn new() -> Self {
- Self {
- wake_flag: false,
-
- foreground: Vec::new(),
- background: Vec::new(),
- dirty: false,
- resizable: true,
-
- cursor: ScreenPosition::ZERO,
- vector: ScreenPosition::ZERO,
- dimensions: ScreenDimensions::ZERO,
-
- palette_high: 0,
- palette: [Colour::BLACK; 16],
- sprite_buffer: SpriteBuffer::new(),
- }
- }
-
- pub fn set_size(&mut self) {
- self.resizable = false;
- self.resize(self.dimensions);
- }
-
- // Resize the screen buffers while preserving the current content.
- pub fn resize(&mut self, dimensions: ScreenDimensions) {
- let old_width = self.dimensions.width as usize;
- let old_height = self.dimensions.height as usize;
- let new_width = dimensions.width as usize;
- let new_height = dimensions.height as usize;
- let new_area = dimensions.area_usize();
- let y_range = 0..min(old_height, new_height);
-
- match new_width.cmp(&old_width) {
- Ordering::Less => {
- for y in y_range {
- let from = y * old_width;
- let to = y * new_width;
- let len = new_width;
- self.foreground.copy_within(from..from+len, to);
- self.background.copy_within(from..from+len, to);
- }
- self.foreground.resize(new_area, 0);
- self.background.resize(new_area, 0);
- },
- Ordering::Greater => {
- self.foreground.resize(new_area, 0);
- self.background.resize(new_area, 0);
- for y in y_range.rev() {
- let from = y * old_width;
- let to = y * new_width;
- let len = old_width;
- self.foreground.copy_within(from..from+len, to);
- self.background.copy_within(from..from+len, to);
- self.foreground[to+len..to+new_width].fill(0);
- self.background[to+len..to+new_width].fill(0);
- }
- },
- Ordering::Equal => {
- self.foreground.resize(new_area, 0);
- self.background.resize(new_area, 0);
- },
- };
-
- self.dimensions = dimensions;
- self.dirty = true;
- self.wake_flag = true;
- }
-
- pub fn render(&mut self, buffer: &mut Buffer) {
- // Pre-calculate a lookup table for the colour palette
- let mut lookup = [Colour::BLACK; 256];
- for (i, c) in lookup.iter_mut().enumerate() {
- match i > 0x0f {
- true => *c = self.palette[i >> 4],
- false => *c = self.palette[i & 0x0f],
- }
- };
- // Prepare values
- let b_width = buffer.width() as usize;
- let b_height = buffer.height() as usize;
- let s_width = self.dimensions.width() as usize;
- let s_height = self.dimensions.height() as usize;
-
- // Write colours to the buffer
- if b_width == s_width && b_height == s_height {
- let screen_iter = zip(&self.background, &self.foreground);
- let buffer_iter = buffer.as_mut_slice();
- for (b, (bg, fg)) in zip(buffer_iter, screen_iter) {
- *b = lookup[(fg << 4 | bg) as usize];
- }
- // Write colours to the buffer when the size of the buffer is wrong
- } else {
- let width = min(b_width, s_width);
- let height = min(b_height, s_height);
- let width_excess = b_width.saturating_sub(width);
- let b_slice = &mut buffer.as_mut_slice();
- let mut bi = 0;
- let mut si = 0;
- for _ in 0..height {
- let b_iter = &mut b_slice[bi..bi+width];
- let s_iter = zip(
- &self.background[si..si+width],
- &self.foreground[si..si+width],
- );
- for (b, (bg, fg)) in zip(b_iter, s_iter) {
- *b = lookup[(fg << 4 | bg) as usize];
- }
- b_slice[bi+width..bi+width+width_excess].fill(lookup[0]);
- bi += b_width;
- si += s_width;
- }
- b_slice[bi..].fill(lookup[0]);
- }
- self.dirty = false;
- }
-
- pub fn set_palette_high(&mut self, val: u8) {
- self.palette_high = val;
- }
-
- pub fn set_palette_low(&mut self, val: u8) {
- let index = (self.palette_high >> 4) as usize;
- let red = (self.palette_high & 0x0f) * 17;
- let green = (val >> 4) * 17;
- let blue = (val & 0x0f) * 17;
- self.palette[index] = Colour::from_rgb(red, green, blue);
- self.dirty = true;
- }
-
- pub fn shunt(&mut self, val: u8) {
- let negative = test!(val, NEGATIVE);
- let vertical = test!(val, VERTICAL);
- let dist = (val & 0x3f) as u16;
- match (negative, vertical) {
- (false, false) => self.cursor.x = self.cursor.x.wrapping_add(dist),
- (false, true) => self.cursor.y = self.cursor.y.wrapping_add(dist),
- ( true, false) => self.cursor.x = self.cursor.x.wrapping_sub(dist),
- ( true, true) => self.cursor.y = self.cursor.y.wrapping_sub(dist),
- };
- }
-
- pub fn draw(&mut self, val: u8) {
- let operation = val & 0x70;
- let parameters = val & 0x0f;
- let layer = match val & 0x80 != 0 {
- true => ScreenLayer::Foreground,
- false => ScreenLayer::Background
- };
- match operation {
- 0x00 => self.draw_pixel(parameters, layer, self.cursor),
- 0x10 => self.draw_sprite_1bit(parameters, layer),
- 0x20 => self.fill_layer(parameters, layer),
- 0x30 => self.draw_sprite_2bit(parameters, layer),
- 0x40 => self.draw_line(parameters, layer),
- 0x50 => self.draw_line_1bit(parameters, layer),
- 0x60 => self.draw_rect(parameters, layer),
- 0x70 => self.draw_rect_1bit(parameters, layer),
- _ => unreachable!(),
- };
-
- self.dirty = true;
- self.vector = self.cursor;
- }
-
- // Draw a single pixel of a single colour
- fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) {
- let dim = self.dimensions;
- if !dim.contains_point(point) || colour > 0xf { return }
- let index = point.x as usize + ((dim.width as usize) * (point.y as usize));
- match layer {
- ScreenLayer::Background => self.background[index] = colour,
- ScreenLayer::Foreground => self.foreground[index] = colour,
- };
- }
-
- // Fill an entire screen layer with a single colour
- fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) {
- match layer {
- ScreenLayer::Background => self.background.fill(colour),
- ScreenLayer::Foreground => self.foreground.fill(colour),
- }
- }
-
- /// Returns [x0, y0, x1, y1], ensuring that x0 <= x1 and y0 <= y1
- fn find_vector_bounding_box(&self) -> Option<[usize; 4]> {
- macro_rules! raise {($v:expr) => {$v.wrapping_add(0x8000)};}
- macro_rules! lower {($v:expr) => {$v.wrapping_sub(0x8000)};}
-
- let [p0, p1] = [self.cursor, self.vector];
- let [p0x, p0y] = [ raise!(p0.x), raise!(p0.y) ];
- let [p1x, p1y] = [ raise!(p1.x), raise!(p1.y) ];
- let [x0, y0] = [ min(p0x, p1x), min(p0y, p1y) ];
- let [x1, y1] = [ max(p0x, p1x), max(p0y, p1y) ];
- let right = self.dimensions.width.saturating_sub(1);
- let bottom = self.dimensions.height.saturating_sub(1);
- if x0 > raise!(right) || y0 > raise!(bottom) || x1 < 0x8000 || y1 < 0x8000 {
- None
- } else {
- Some([
- if x0 < 0x8000 { 0 } else { min(lower!(x0), right) } as usize,
- if y0 < 0x8000 { 0 } else { min(lower!(y0), bottom) } as usize,
- if x1 < 0x8000 { 0 } else { min(lower!(x1), right) } as usize,
- if y1 < 0x8000 { 0 } else { min(lower!(y1), bottom) } as usize,
- ])
- }
- }
-}
diff --git a/src/devices/screen/draw_line.rs b/src/devices/screen/draw_line.rs
deleted file mode 100644
index 94066f4..0000000
--- a/src/devices/screen/draw_line.rs
+++ /dev/null
@@ -1,223 +0,0 @@
-use super::*;
-
-impl ScreenDevice {
- pub fn draw_line(&mut self, colour: u8, layer: ScreenLayer) {
- let [p0, p1] = [self.cursor, self.vector];
- match (p0.x == p1.x, p0.y == p1.y) {
- (false, false) => self.draw_diagonal_line(colour, layer),
- (false, true) => self.draw_horizontal_line(colour, layer),
- ( true, false) => self.draw_vertical_line(colour, layer),
- ( true, true) => self.draw_pixel(colour, layer, p0),
- };
- }
-
- pub fn draw_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
- let [p0, p1] = [self.cursor, self.vector];
- match (p0.x == p1.x, p0.y == p1.y) {
- (false, false) => self.draw_diagonal_line_1bit(params, layer),
- (false, true) => self.draw_horizontal_line_1bit(params, layer),
- ( true, false) => self.draw_vertical_line_1bit(params, layer),
- ( true, true) => self.draw_pixel_1bit(params, layer, p0),
- };
- }
-
- pub fn draw_pixel_1bit(&mut self, params: u8, layer: ScreenLayer, point: ScreenPosition) {
- let dim = self.dimensions;
- let sprite = self.sprite_buffer.get_1bit_sprite(params);
- let colour = sprite[point.y as usize % 8][point.x as usize % 8];
- if !dim.contains_point(point) || colour == 0xff { return }
- let index = point.x as usize + ((dim.width as usize) * (point.y as usize));
- match layer {
- ScreenLayer::Background => self.background[index] = colour,
- ScreenLayer::Foreground => self.foreground[index] = colour,
- };
- }
-
- fn draw_horizontal_line(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let i = screen_width * y;
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- buffer[i+x0..=i+x1].fill(colour);
- }
- }
-
- fn draw_horizontal_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
- if let Some([x0, y, x1, _]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let i = screen_width * y;
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- let sprite = self.sprite_buffer.get_1bit_sprite(params);
- let row = sprite[y % 8];
- for x in x0..=x1 {
- let colour = row[x % 8];
- if colour != 0xff { buffer[i+x] = colour };
- }
- }
- }
-
- fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let mut i = (screen_width * y0) + x;
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- for _ in y0..=y1 {
- buffer[i] = colour;
- i += screen_width;
- }
- }
- }
-
- fn draw_vertical_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
- if let Some([x, y0, _, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let mut i = (screen_width * y0) + x;
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- let sprite = self.sprite_buffer.get_1bit_sprite(params);
- let mut column = [0u8; 8];
- for y in 0..8 { column[y] = sprite[y][x % 8] }
- for y in y0..=y1 {
- let colour = column[y % 8];
- if colour != 0xff { buffer[i] = colour };
- i += screen_width;
- }
- }
- }
-
- fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer) {
- fn abs_diff(v0: u16, v1: u16) -> u16 {
- let v = v1.wrapping_sub(v0);
- if v > 0x8000 { !v + 1 } else { v }
- }
- let [p0, p1] = [self.cursor, self.vector];
-
- // If the slope of the line is greater than 1.
- if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
- // Swap points 0 and 1 such that y0 is always smaller than y1.
- let (x0, y0, x1, y1) = match p0.y > p1.y {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
- let dy = y1 - y0;
- let (dx, xi) = match x0 > x1 {
- true => (x0 - x1, 0xffff),
- false => (x1 - x0, 0x0001),
- };
- let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
- let dx2 = dx * 2;
- let mut d = dx2.wrapping_sub(dy);
- let mut x = x0;
-
- for y in y0..=y1 {
- self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2);
- } else {
- d = d.wrapping_add(dx2);
- }
- }
- // If the slope of the line is less than or equal to 1.
- } else {
- // Swap points 0 and 1 so that x0 is always smaller than x1.
- let (x0, y0, x1, y1) = match p0.x > p1.x {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
- let dx = x1 - x0;
- let (dy, yi) = match y0 > y1 {
- true => (y0 - y1, 0xffff),
- false => (y1 - y0, 0x0001),
- };
- let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
- let dy2 = dy * 2;
- let mut d = dy2.wrapping_sub(dx);
- let mut y = y0;
-
- for x in x0..=x1 {
- self.draw_pixel(colour, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- y = y.wrapping_add(yi);
- d = d.wrapping_add(dydx2);
- } else {
- d = d.wrapping_add(dy2);
- }
- }
- }
- }
-
- fn draw_diagonal_line_1bit(&mut self, params: u8, layer: ScreenLayer) {
- fn abs_diff(v0: u16, v1: u16) -> u16 {
- let v = v1.wrapping_sub(v0);
- if v > 0x8000 { !v + 1 } else { v }
- }
- let [p0, p1] = [self.cursor, self.vector];
-
- // If the slope of the line is greater than 1.
- if abs_diff(p0.y, p1.y) > abs_diff(p0.x, p1.x) {
- // Swap points 0 and 1 such that y0 is always smaller than y1.
- let (x0, y0, x1, y1) = match p0.y > p1.y {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
- let dy = y1 - y0;
- let (dx, xi) = match x0 > x1 {
- true => (x0 - x1, 0xffff),
- false => (x1 - x0, 0x0001),
- };
- let dxdy2 = (dx.wrapping_sub(dy)).wrapping_mul(2);
- let dx2 = dx * 2;
- let mut d = dx2.wrapping_sub(dy);
- let mut x = x0;
-
- for y in y0..=y1 {
- self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- x = x.wrapping_add(xi); d = d.wrapping_add(dxdy2);
- } else {
- d = d.wrapping_add(dx2);
- }
- }
- // If the slope of the line is less than or equal to 1.
- } else {
- // Swap points 0 and 1 so that x0 is always smaller than x1.
- let (x0, y0, x1, y1) = match p0.x > p1.x {
- true => (p1.x, p1.y, p0.x, p0.y),
- false => (p0.x, p0.y, p1.x, p1.y),
- };
- let dx = x1 - x0;
- let (dy, yi) = match y0 > y1 {
- true => (y0 - y1, 0xffff),
- false => (y1 - y0, 0x0001),
- };
- let dydx2 = (dy.wrapping_sub(dx)).wrapping_mul(2);
- let dy2 = dy * 2;
- let mut d = dy2.wrapping_sub(dx);
- let mut y = y0;
-
- for x in x0..=x1 {
- self.draw_pixel_1bit(params, layer, ScreenPosition::new(x, y));
- if d < 0x8000 {
- y = y.wrapping_add(yi);
- d = d.wrapping_add(dydx2);
- } else {
- d = d.wrapping_add(dy2);
- }
- }
- }
- }
-
-
-
-}
diff --git a/src/devices/screen/draw_rect.rs b/src/devices/screen/draw_rect.rs
deleted file mode 100644
index 265a87f..0000000
--- a/src/devices/screen/draw_rect.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use super::*;
-
-impl ScreenDevice {
- pub fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) {
- if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let rect_width = x1 - x0 + 1;
- let mut i = x0 + (screen_width * y0);
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
- for _ in y0..=y1 {
- buffer[i..i+rect_width].fill(colour);
- i += screen_width;
- }
- }
- }
-
- pub fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) {
- if let Some([x0, y0, x1, y1]) = self.find_vector_bounding_box() {
- let screen_width = self.dimensions.width as usize;
- let rect_width = x1 - x0 + 1;
- let mut i = x0 + (screen_width * y0);
- let sprite = self.sprite_buffer.get_1bit_sprite(params);
- let buffer = match layer {
- ScreenLayer::Background => &mut self.background,
- ScreenLayer::Foreground => &mut self.foreground,
- };
-
- for y in y0..=y1 {
- let row = sprite[y % 8];
- for x in x0..=x1 {
- let colour = row[x % 8];
- if colour != 0xff { buffer[i] = colour }
- i += 1;
- }
- i += screen_width - rect_width;
- }
- };
- }
-}
diff --git a/src/devices/screen/draw_sprite.rs b/src/devices/screen/draw_sprite.rs
deleted file mode 100644
index 5676335..0000000
--- a/src/devices/screen/draw_sprite.rs
+++ /dev/null
@@ -1,25 +0,0 @@
-use super::*;
-
-impl ScreenDevice {
- pub fn draw_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) {
- let sprite = self.sprite_buffer.get_1bit_sprite(params);
- self.draw_sprite(sprite, layer);
- }
-
- pub fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) {
- let sprite = self.sprite_buffer.get_2bit_sprite(params);
- self.draw_sprite(sprite, layer);
- }
-
- fn draw_sprite(&mut self, sprite: Sprite, layer: ScreenLayer) {
- let mut pos = self.cursor;
- for row in sprite {
- for colour in row {
- self.draw_pixel(colour, layer, pos);
- pos.x = pos.x.wrapping_add(1);
- }
- pos.x = pos.x.wrapping_sub(8);
- pos.y = pos.y.wrapping_add(1);
- }
- }
-}
diff --git a/src/devices/screen/sprite_data.rs b/src/devices/screen/sprite_data.rs
deleted file mode 100644
index 0a1d3c2..0000000
--- a/src/devices/screen/sprite_data.rs
+++ /dev/null
@@ -1,109 +0,0 @@
-use super::*;
-
-macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; }
-
-
-pub struct SpriteBuffer {
- data: [u8; 16],
- pointer: usize,
- colours: [u8; 4],
-}
-
-impl SpriteBuffer {
- pub fn new() -> Self {
- Self { data: [0; 16], pointer: 0, colours: [0; 4] }
- }
-
- pub fn push(&mut self, val: u8) {
- self.data[self.pointer] = val;
- self.pointer = (self.pointer + 1) % 16;
- }
-
- pub fn set_colour_high(&mut self, val: u8) {
- self.colours[0] = val >> 4;
- self.colours[1] = val & 0x0f;
- }
-
- pub fn set_colour_low(&mut self, val: u8) {
- self.colours[2] = val >> 4;
- self.colours[3] = val & 0x0f;
- }
-
- // Return the 64 transformed pixels of the current 1-bit sprite.
- // Each pixel is the palette index of that pixel, or 0xff if transparent.
- pub fn get_1bit_sprite(&self, params: u8) -> Sprite {
- let mut sprite = [[0u8; 8]; 8];
- let plane = self.get_low_plane(params);
- let colours = self.get_colours(params);
- for (y, row) in plane.into_iter().enumerate() {
- for x in (0..8).rev() {
- sprite[y][7-x] = colours[(row >> x & 0x1) as usize];
- }
- }
- return sprite;
- }
-
- // Return the 64 transformed pixels of the current 2-bit sprite.
- // Each pixel is the palette index of that pixel, or 0xff if transparent.
- pub fn get_2bit_sprite(&self, params: u8) -> Sprite {
- let mut sprite = [[0u8; 8]; 8];
- let high_plane = self.get_high_plane(params);
- let low_plane = self.get_low_plane(params);
- let colours = self.get_colours(params);
- for (y, (row_h, row_l)) in zip(high_plane, low_plane).enumerate() {
- for x in (0..8).rev() {
- let bit_h = (row_h >> x) & 0x1;
- let bit_l = (row_l >> x) & 0x1;
- sprite[y][7-x] = colours[(bit_h << 1 | bit_l) as usize];
- }
- }
- return sprite;
- }
-
- fn get_high_plane(&self, params: u8) -> Plane {
- let mut plane = [0u8; 8];
- for (i, row) in plane.iter_mut().enumerate() {
- *row = self.data[(self.pointer + i) % 16]
- }
- transform_plane(plane, params)
- }
-
- fn get_low_plane(&self, params: u8) -> Plane {
- let mut plane = [0u8; 8];
- for (i, row) in plane.iter_mut().enumerate() {
- *row = self.data[(self.pointer + i + 8) % 16]
- }
- transform_plane(plane, params)
- }
-
- fn get_colours(&self, params: u8) -> [u8; 4] {
- let mut colours = self.colours;
- if test!(params, TRANSPARENT) {
- colours[0] = 0xff;
- }
- return colours;
- }
-}
-
-fn transform_plane(mut plane: Plane, params: u8) -> Plane {
- if test!(params, FLIP_DIAGONAL) {
- let mut flipped = [0u8; 8];
- for mut row in plane {
- for y in 0..8 {
- flipped[y] = flipped[y] << 1 | row >> 7;
- row <<= 1;
- }
- }
- plane = flipped;
- }
- if test!(params, FLIP_VERTICAL) {
- plane.reverse();
- }
- if test!(params, FLIP_HORIZONTAL) {
- for row in plane.iter_mut() {
- *row = row.reverse_bits();
- }
- }
- return plane;
-}
-
diff --git a/src/devices/screen_device.rs b/src/devices/screen_device.rs
new file mode 100644
index 0000000..64f3815
--- /dev/null
+++ b/src/devices/screen_device.rs
@@ -0,0 +1,485 @@
+use crate::*;
+
+use bedrock_core::*;
+use geometry::*;
+use phosphor::*;
+
+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,
+
+ pub cursor: ScreenPosition,
+ pub vector: ScreenPosition,
+
+ pub dimensions: ScreenDimensions,
+ pub dirty_dimensions: bool,
+ pub width_write: u16,
+ pub height_write: u16,
+ pub fixed_width: Option<u16>,
+ pub fixed_height: Option<u16>,
+
+ pub palette_write: u16,
+ pub palette: [Colour; 16],
+ pub colours: u16,
+ pub sprite: SpriteBuffer,
+}
+
+impl ScreenDevice {
+ pub fn new(config: &EmulatorConfig) -> Self {
+ let area = config.dimensions.area_usize();
+
+ Self {
+ wake: false,
+ accessed: false,
+
+ fg: vec![0; area],
+ bg: vec![0; area],
+ dirty: false,
+
+ cursor: ScreenPosition::ZERO,
+ vector: ScreenPosition::ZERO,
+
+ dimensions: config.dimensions,
+ dirty_dimensions: true,
+ width_write: 0,
+ height_write: 0,
+ fixed_width: None,
+ fixed_height: None,
+
+ palette_write: 0,
+ palette: [Colour::BLACK; 16],
+ colours: 0,
+ sprite: SpriteBuffer::new(),
+ }
+ }
+
+ /// External resize.
+ pub fn resize(&mut self, dimensions: phosphor::Dimensions) {
+ // Replace dimensions with fixed dimensions.
+ let screen_dimensions = ScreenDimensions {
+ width: match self.fixed_width {
+ Some(fixed_width) => fixed_width,
+ None => dimensions.width as u16,
+ },
+ height: match self.fixed_height {
+ Some(fixed_height) => fixed_height,
+ None => dimensions.height as u16,
+ },
+ };
+ let old_dimensions = self.dimensions;
+ if self.dimensions != screen_dimensions {
+ self.dimensions = screen_dimensions;
+ self.resize_layers(old_dimensions);
+ self.wake = true;
+ }
+ }
+
+ /// Internal resize.
+ fn resize_width(&mut self) {
+ self.fixed_width = Some(self.width_write);
+ self.dirty_dimensions = true;
+ let old_dimensions = self.dimensions;
+ if self.dimensions.width != self.width_write {
+ self.dimensions.width = self.width_write;
+ self.resize_layers(old_dimensions);
+ }
+ }
+
+ /// Internal resize.
+ fn resize_height(&mut self) {
+ self.fixed_height = Some(self.height_write);
+ self.dirty_dimensions = true;
+ let old_dimensions = self.dimensions;
+ if self.dimensions.height != self.height_write {
+ self.dimensions.height = self.height_write;
+ self.resize_layers(old_dimensions);
+ }
+ }
+
+ fn resize_layers(&mut self, old_dimensions: ScreenDimensions) {
+ use std::cmp::{min, Ordering};
+
+ let old_width = old_dimensions.width as usize;
+ let old_height = old_dimensions.height as usize;
+ let new_width = self.dimensions.width as usize;
+ let new_height = self.dimensions.height as usize;
+ let new_area = self.dimensions.area_usize();
+ let y_range = 0..min(old_height, new_height);
+ let new_colour = match self.fg.last() {
+ None | Some(0) => *self.bg.last().unwrap_or(&0),
+ Some(colour) => *colour,
+ };
+
+ match new_width.cmp(&old_width) {
+ Ordering::Less => {
+ for y in y_range {
+ let src = y * old_width;
+ let dest = y * new_width;
+ let len = new_width;
+ self.fg.copy_within(src..src+len, dest);
+ self.bg.copy_within(src..src+len, dest);
+ }
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ },
+ Ordering::Greater => {
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ for y in y_range.rev() {
+ let src = y * old_width;
+ let dest = y * new_width;
+ let len = old_width;
+ self.fg.copy_within(src..src+len, dest);
+ self.bg.copy_within(src..src+len, dest);
+ self.fg[dest+len..dest+new_width].fill(0);
+ self.bg[dest+len..dest+new_width].fill(new_colour);
+ }
+ },
+ Ordering::Equal => {
+ self.fg.resize(new_area, 0);
+ self.bg.resize(new_area, new_colour);
+ },
+ };
+
+ self.dirty = true;
+ }
+
+ pub fn set_palette(&mut self) {
+ let i = (self.palette_write >> 12 ) as usize;
+ let r = (self.palette_write >> 8 & 0xf) as u8 * 17;
+ let g = (self.palette_write >> 4 & 0xf) as u8 * 17;
+ let b = (self.palette_write & 0xf) as u8 * 17;
+ let colour = Colour::from_rgb(r, g, b);
+ if self.palette[i] != colour {
+ self.palette[i] = colour;
+ self.dirty = true;
+ }
+ }
+
+ pub fn draw_dispatch(&mut self, draw: u8) {
+ match draw >> 4 {
+ 0x0 => self.op_draw_pixel(Layer::Bg, draw),
+ 0x1 => self.op_draw_sprite(Layer::Bg, draw),
+ 0x2 => self.op_fill_layer(Layer::Bg, draw),
+ 0x3 => self.op_draw_sprite(Layer::Bg, draw),
+ 0x4 => self.op_draw_line(Layer::Bg, draw),
+ 0x5 => self.op_draw_line(Layer::Bg, draw),
+ 0x6 => self.op_draw_rect(Layer::Bg, draw),
+ 0x7 => self.op_draw_rect(Layer::Bg, draw),
+ 0x8 => self.op_draw_pixel(Layer::Fg, draw),
+ 0x9 => self.op_draw_sprite(Layer::Fg, draw),
+ 0xA => self.op_fill_layer(Layer::Fg, draw),
+ 0xB => self.op_draw_sprite(Layer::Fg, draw),
+ 0xC => self.op_draw_line(Layer::Fg, draw),
+ 0xD => self.op_draw_line(Layer::Fg, draw),
+ 0xE => self.op_draw_rect(Layer::Fg, draw),
+ 0xF => self.op_draw_rect(Layer::Fg, draw),
+ _ => unreachable!(),
+ }
+ self.vector = self.cursor;
+ self.dirty = true;
+ }
+
+ pub fn move_cursor(&mut self, value: u8) {
+ let distance = (value & 0x3f) as u16;
+ match value >> 6 {
+ 0b00 => self.cursor.x = self.cursor.x.wrapping_add(distance),
+ 0b01 => self.cursor.y = self.cursor.y.wrapping_add(distance),
+ 0b10 => self.cursor.x = self.cursor.x.wrapping_sub(distance),
+ 0b11 => self.cursor.y = self.cursor.y.wrapping_sub(distance),
+ _ => unreachable!(),
+ };
+ }
+
+ /// Colour must already be masked by 0xf.
+ pub fn draw_pixel(&mut self, layer: Layer, x: u16, y: u16, colour: u8) {
+ if x < self.dimensions.width && y < self.dimensions.height {
+ let index = x as usize + (self.dimensions.width as usize * y as usize);
+ match layer {
+ Layer::Fg => self.fg[index] = colour,
+ Layer::Bg => self.bg[index] = colour,
+ };
+ }
+ }
+
+ fn op_draw_pixel(&mut self, layer: Layer, draw: u8) {
+ self.draw_pixel(layer, self.cursor.x, self.cursor.y, draw & 0xf);
+ }
+
+ fn op_fill_layer(&mut self, layer: Layer, draw: u8) {
+ match layer {
+ Layer::Fg => self.fg.fill(draw & 0xf),
+ Layer::Bg => self.bg.fill(draw & 0xf),
+ }
+ }
+
+ fn op_draw_sprite(&mut self, layer: Layer, draw: u8) {
+ let sprite = match draw & 0x20 != 0 {
+ true => self.sprite.read_2bit_sprite(draw),
+ false => self.sprite.read_1bit_sprite(draw),
+ };
+ let colours = [
+ (self.colours >> 12 & 0x000f) as u8,
+ (self.colours >> 8 & 0x000f) as u8,
+ (self.colours >> 4 & 0x000f) as u8,
+ (self.colours & 0x000f) as u8,
+ ];
+ let cx = self.cursor.x;
+ let cy = self.cursor.y;
+
+ if draw & 0x08 != 0 {
+ // Draw sprite with transparent background
+ for y in 0..8 {
+ for x in 0..8 {
+ let index = sprite[y as usize][x as usize] as usize;
+ if index != 0 {
+ let px = cx.wrapping_add(x);
+ let py = cy.wrapping_add(y);
+ self.draw_pixel(layer, px, py, colours[index]);
+ }
+ }
+ }
+ } else {
+ // Draw sprite with opaque background
+ for y in 0..8 {
+ for x in 0..8 {
+ let index = sprite[y as usize][x as usize] as usize;
+ let px = cx.wrapping_add(x);
+ let py = cy.wrapping_add(y);
+ self.draw_pixel(layer, px, py, colours[index]);
+ }
+ }
+ }
+ }
+
+ fn op_draw_line(&mut self, layer: Layer, draw: u8) {
+ let mut x: i16 = self.cursor.x as i16;
+ let mut y: i16 = self.cursor.y as i16;
+ let x_end: i16 = self.vector.x as i16;
+ let y_end: i16 = self.vector.y as i16;
+
+ let dx: i16 = (x_end - x).abs();
+ let dy: i16 = -(y_end - y).abs();
+ let sx: i16 = if x < x_end { 1 } else { -1 };
+ let sy: i16 = if y < y_end { 1 } else { -1 };
+ let mut e1: i16 = dx + dy;
+
+ 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 opaque = draw & 0x08 == 0;
+ loop {
+ let sprite_pixel = sprite[(y % 8) as usize][(x % 8) as usize];
+ if sprite_pixel != 0 { self.draw_pixel(layer, x as u16, y as u16, c1); }
+ else if opaque { self.draw_pixel(layer, x as u16, y as u16, c0); }
+ if x == x_end && y == y_end { break; }
+ let e2 = e1 << 1;
+ if e2 >= dy { e1 += dy; x += sx; }
+ if e2 <= dx { e1 += dx; y += sy; }
+ }
+ } else {
+ // Draw solid line.
+ let colour = draw & 0xf;
+ loop {
+ self.draw_pixel(layer, x as u16, y as u16, colour);
+ if x == x_end && y == y_end { break; }
+ let e2 = e1 << 1;
+ if e2 >= dy { e1 += dy; x += sx; }
+ if e2 <= dx { e1 += dx; y += sy; }
+ }
+ }
+ }
+
+ fn op_draw_rect(&mut self, layer: Layer, draw: u8) {
+ macro_rules! clamp {
+ ($v:expr, $max:expr) => {
+ if $v > 0x7fff { 0 } else if $v > $max { $max } else { $v }
+ };
+ }
+ macro_rules! out_of_bounds {
+ ($axis:ident, $max:expr) => {{
+ let c = self.cursor.$axis;
+ let v = self.vector.$axis;
+ c >= $max && v >= $max && (c >= 0x8000) == (v >= 0x8000)
+ }};
+ }
+
+ let out_of_bounds_x = out_of_bounds!(x, self.dimensions.width);
+ let out_of_bounds_y = out_of_bounds!(y, self.dimensions.height);
+ if out_of_bounds_x || out_of_bounds_y { return; }
+
+ // Get bounding box.
+ let mut l = clamp!(self.vector.x, self.dimensions.width -1);
+ let mut r = clamp!(self.cursor.x, self.dimensions.width -1);
+ let mut t = clamp!(self.vector.y, self.dimensions.height -1);
+ let mut b = clamp!(self.cursor.y, self.dimensions.height -1);
+ if l > r { std::mem::swap(&mut l, &mut r) };
+ if t > b { std::mem::swap(&mut t, &mut b) };
+
+ if draw & 0x10 != 0 {
+ // Draw 1-bit textured rectangle.
+ let sprite = self.sprite.read_1bit_sprite(draw);
+ let c1 = (self.colours >> 8 & 0xf) as u8;
+ let c0 = (self.colours >> 12 & 0xf) as u8;
+ let opaque = draw & 0x08 == 0;
+ for y in t..=b {
+ for x in l..=r {
+ let sprite_colour = sprite[(y % 8) as usize][(x % 8) as usize];
+ if sprite_colour != 0 { self.draw_pixel(layer, x, y, c1); }
+ else if opaque { self.draw_pixel(layer, x, y, c0); }
+ }
+ }
+ } else {
+ // Draw solid rectangle.
+ let colour = draw & 0xf;
+ for y in t..=b {
+ for x in l..=r {
+ self.draw_pixel(layer, x, y, colour);
+ }
+ }
+ }
+ }
+}
+
+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,
+}
+
+impl SpriteBuffer {
+ pub fn new() -> Self {
+ Self {
+ mem: [0; 16],
+ pointer: 0,
+ }
+ }
+
+ pub fn push_byte(&mut self, byte: u8) {
+ self.mem[self.pointer] = byte;
+ self.pointer = (self.pointer + 1) % 16;
+ }
+
+ pub fn read_1bit_sprite(&self, draw: u8) -> 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!(),
+ }
+ return sprite;
+ }
+
+ pub fn read_2bit_sprite(&self, draw: u8) -> 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!(),
+ }
+ return sprite;
+ }
+}
diff --git a/src/devices/stream.rs b/src/devices/stream.rs
deleted file mode 100644
index 532df58..0000000
--- a/src/devices/stream.rs
+++ /dev/null
@@ -1,53 +0,0 @@
-use std::io::{Read, Write};
-use std::io::{BufReader, BufWriter};
-use std::io::{Stdin, Stdout};
-
-pub struct StreamDevice {
- pub wake_flag: bool,
-
- pub input_control: bool,
- pub output_control: bool,
-
- pub stdin: BufReader<Stdin>,
- pub stdout: BufWriter<Stdout>,
-}
-
-impl StreamDevice {
- pub fn new() -> Self {
- Self {
- wake_flag: false,
-
- input_control: true,
- output_control: true,
-
- stdin: BufReader::new(std::io::stdin()),
- stdout: BufWriter::new(std::io::stdout()),
- }
- }
-
- pub fn flush_local(&mut self) {
- self.stdout.flush().unwrap();
- }
-
- pub fn read_queue_len(&self) -> usize {
- self.stdin.buffer().len()
- }
-
- pub fn read_stdin(&mut self) -> u8 {
- let mut buffer = [0; 1];
- match self.stdin.read_exact(&mut buffer) {
- Ok(_) => buffer[0],
- Err(_) => 0,
- }
- }
-
- pub fn write_stdout(&mut self, val: u8) {
- self.stdout.write_all(&[val]).unwrap();
- }
-}
-
-impl Drop for StreamDevice {
- fn drop(&mut self) {
- self.flush_local();
- }
-}
diff --git a/src/devices/system.rs b/src/devices/system.rs
deleted file mode 100644
index 6eb9510..0000000
--- a/src/devices/system.rs
+++ /dev/null
@@ -1,4 +0,0 @@
-mod read_only_text_buffer;
-
-pub use read_only_text_buffer::ReadOnlyTextBuffer;
-
diff --git a/src/devices/system/read_only_text_buffer.rs b/src/devices/system/read_only_text_buffer.rs
deleted file mode 100644
index dae1024..0000000
--- a/src/devices/system/read_only_text_buffer.rs
+++ /dev/null
@@ -1,23 +0,0 @@
-pub struct ReadOnlyTextBuffer {
- chars: Vec<u8>,
- pointer: usize,
-}
-
-impl ReadOnlyTextBuffer {
- pub fn from_text(text: &str) -> Self {
- Self {
- chars: text.bytes().collect(),
- pointer: 0,
- }
- }
-
- pub fn read_byte(&mut self) -> u8 {
- let option = self.chars.get(self.pointer);
- self.pointer += 1;
- *option.unwrap_or(&0)
- }
-
- pub fn reset_pointer(&mut self) {
- self.pointer = 0;
- }
-}
diff --git a/src/devices/system_device.rs b/src/devices/system_device.rs
new file mode 100644
index 0000000..bcffb86
--- /dev/null
+++ b/src/devices/system_device.rs
@@ -0,0 +1,114 @@
+use bedrock_core::*;
+
+
+pub struct SystemDevice {
+ pub name: ReadBuffer,
+ pub authors: ReadBuffer,
+ pub can_wake: u16,
+ pub wake_id: u8,
+ pub asleep: bool,
+}
+
+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 mut authors_str = String::new();
+ for author in pkg_authors.split(":") {
+ authors_str.push_str(&format!("{author}, 2024\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,
+ 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,
+ 0xb => 0x00,
+ 0xc => 0x00,
+ 0xd => 0x00,
+ 0xe => 0x00,
+ 0xf => 0x00,
+ _ => unreachable!(),
+ }
+ }
+
+ fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
+ match port {
+ 0x0 => self.name.pointer = 0,
+ 0x1 => self.authors.pointer = 0,
+ 0x2 => (),
+ 0x3 => (),
+ 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);
+ },
+ 0xa => (),
+ 0xb => return Some(Signal::Fork),
+ 0xc => (),
+ 0xd => (),
+ 0xe => (),
+ 0xf => (),
+ _ => unreachable!(),
+ };
+ return None;
+ }
+
+ fn wake(&mut self) -> bool {
+ true
+ }
+}
+
+
+pub struct ReadBuffer {
+ pub bytes: Vec<u8>,
+ pub pointer: usize,
+}
+
+impl ReadBuffer {
+ pub fn from_str(text: &str) -> Self {
+ Self {
+ bytes: text.bytes().collect(),
+ pointer: 0,
+ }
+ }
+
+ pub fn read(&mut self) -> u8 {
+ let pointer = self.pointer;
+ self.pointer += 1;
+ match self.bytes.get(pointer) {
+ Some(byte) => *byte,
+ None => 0,
+ }
+ }
+}
+