diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 22:19:11 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2023-12-24 22:19:11 +1300 |
commit | 77067f6e244a9cf4a6ef59df2e3d735b4f172c35 (patch) | |
tree | 74eb1a226da5b367f65e2f9012dc8d440e1ccce5 /src | |
download | bedrock-pc-77067f6e244a9cf4a6ef59df2e3d735b4f172c35.zip |
First commitv0.1.0
Diffstat (limited to 'src')
-rw-r--r-- | src/devices.rs | 339 | ||||
-rw-r--r-- | src/devices/clock.rs | 130 | ||||
-rw-r--r-- | src/devices/file.rs | 90 | ||||
-rw-r--r-- | src/devices/input.rs | 128 | ||||
-rw-r--r-- | src/devices/math.rs | 35 | ||||
-rw-r--r-- | src/devices/scratch.rs | 58 | ||||
-rw-r--r-- | src/devices/screen.rs | 469 | ||||
-rw-r--r-- | src/devices/screen/sprite_data.rs | 33 | ||||
-rw-r--r-- | src/devices/screen/vector_points.rs | 28 | ||||
-rw-r--r-- | src/devices/stream.rs | 46 | ||||
-rw-r--r-- | src/emulator.rs | 194 | ||||
-rw-r--r-- | src/main.rs | 22 |
12 files changed, 1572 insertions, 0 deletions
diff --git a/src/devices.rs b/src/devices.rs new file mode 100644 index 0000000..562b664 --- /dev/null +++ b/src/devices.rs @@ -0,0 +1,339 @@ +use bedrock_core::*; + +mod math; +mod clock; +mod input; +mod screen; +mod scratch; +mod stream; +mod file; + +pub use math::*; +pub use clock::*; +pub use input::*; +pub use screen::*; +pub use scratch::*; +pub use stream::*; +pub use file::*; + +pub struct StandardDevices { + pub math: MathDevice, + pub clock: ClockDevice, + pub input: InputDevice, + pub screen: ScreenDevice, + pub scratch: ScratchDevice, + pub stream: StreamDevice, + pub file: FileDevice, + + pub wake_mask: u16, + pub wake_device: u8, +} + +impl StandardDevices { + pub fn new() -> Self { + let mut screen = ScreenDevice::new(); + screen.resize(ScreenDimensions::new(256, 192)); + + Self { + math: MathDevice::new(), + clock: ClockDevice::new(), + input: InputDevice::new(), + screen, + scratch: ScratchDevice::new(), + stream: StreamDevice::new(), + file: FileDevice::new(), + + wake_mask: 0x0000, + wake_device: 0x00, + } + } + + pub fn can_wake(&mut self) -> bool { + macro_rules! test_wake { + ($flag:expr, $mask:expr, $index:expr) => { + if $flag && self.wake_mask & $mask != 0 { + $flag = false; self.wake_device = $index; return true; + } + }; + } + self.clock.update_timer_1(); + self.clock.update_timer_2(); + self.clock.update_timer_3(); + self.clock.update_timer_4(); + test_wake!(self.clock.wake_flag, 0x1000, 0x03); + test_wake!(self.input.wake_flag, 0x0800, 0x04); + test_wake!(self.screen.wake_flag, 0x0400, 0x05); + test_wake!(self.stream.wake_flag, 0x0040, 0x09); + return false; + } +} + +impl DeviceBus for StandardDevices { + fn read_u8(&mut self, port: u8) -> u8 { + macro_rules! read_hh { ($v:expr) => { ($v>>24) as u8 }; } + macro_rules! read_hl { ($v:expr) => { ($v>>16) as u8 }; } + macro_rules! read_lh { ($v:expr) => { ($v>>8) as u8 }; } + macro_rules! read_ll { ($v:expr) => { $v as u8 }; } + macro_rules! read_h { ($v:expr) => { ($v>>8) as u8 }; } + macro_rules! read_l { ($v:expr) => { $v as u8 }; } + macro_rules! read_b { ($b:expr) => { if $b { 0xff } else { 0x00 } }; } + + macro_rules! no_read { () => { 0x00 }; } + + match port { + // System + 0x00 => no_read!(), + 0x01 => no_read!(), + 0x02 => self.wake_device, + 0x03 => no_read!(), + 0x04 => no_read!(), + 0x05 => no_read!(), + 0x06 => no_read!(), + 0x07 => no_read!(), + 0x08 => 0xFF, + 0x09 => 0xFF, + 0x0A => 0xFF, + 0x0B => 0xFF, + 0x0C => 0xBC, + 0x0D => 0x80, + 0x0E => 0xBC, + 0x0F => 0x80, + // Math + 0x20 => no_read!(), + 0x21 => no_read!(), + 0x22 => no_read!(), + 0x23 => no_read!(), + 0x24 => no_read!(), + 0x25 => no_read!(), + 0x26 => no_read!(), + 0x27 => no_read!(), + 0x28 => { self.math.multiply(); read_h!(self.math.product_high) }, + 0x29 => { self.math.multiply(); read_l!(self.math.product_high) }, + 0x2A => { self.math.multiply(); read_h!(self.math.product_low) }, + 0x2B => { self.math.multiply(); read_l!(self.math.product_low) }, + 0x2C => { self.math.divide(); read_h!(self.math.quotient) }, + 0x2D => { self.math.divide(); read_l!(self.math.quotient) }, + 0x2E => { self.math.modulo(); read_h!(self.math.remainder) }, + 0x2F => { self.math.modulo(); read_l!(self.math.remainder) }, + // Clock + 0x30 => self.clock.year(), + 0x31 => self.clock.month(), + 0x32 => self.clock.day(), + 0x33 => self.clock.hour(), + 0x34 => self.clock.minute(), + 0x35 => self.clock.second(), + 0x36 => { self.clock.update_cumulative_seconds(); read_h!(self.clock.cumulative_seconds) }, + 0x37 => read_l!(self.clock.cumulative_seconds), + 0x38 => { self.clock.update_timer_1(); read_h!(self.clock.timer_1) } + 0x39 => read_l!(self.clock.timer_1), + 0x3A => { self.clock.update_timer_2(); read_h!(self.clock.timer_2) } + 0x3B => read_l!(self.clock.timer_2), + 0x3C => { self.clock.update_timer_3(); read_h!(self.clock.timer_3) } + 0x3D => read_l!(self.clock.timer_3), + 0x3E => { self.clock.update_timer_4(); read_h!(self.clock.timer_4) } + 0x3F => read_l!(self.clock.timer_4), + // Input + 0x40 => read_h!(self.input.mouse_position.x), + 0x41 => read_l!(self.input.mouse_position.x), + 0x42 => read_h!(self.input.mouse_position.y), + 0x43 => read_l!(self.input.mouse_position.y), + 0x44 => read_h!(self.input.horizontal_scroll_value), + 0x45 => read_l!(self.input.horizontal_scroll_value), + 0x46 => read_h!(self.input.vertical_scroll_value), + 0x47 => read_l!(self.input.vertical_scroll_value), + 0x48 => self.input.character_queue.pop_front().unwrap_or(0), + 0x49 => self.input.modifier_state, + 0x4A => self.input.mouse_button_state, + 0x4B => self.input.navigation_state, + // Screen + 0x50 => read_h!(self.screen.dimensions.width), + 0x51 => read_l!(self.screen.dimensions.width), + 0x52 => read_h!(self.screen.dimensions.height), + 0x53 => read_l!(self.screen.dimensions.height), + 0x54 => read_h!(self.screen.cursor.x), + 0x55 => read_l!(self.screen.cursor.x), + 0x56 => read_h!(self.screen.cursor.y), + 0x57 => read_l!(self.screen.cursor.y), + 0x58 => no_read!(), + 0x59 => no_read!(), + 0x5A => no_read!(), + 0x5B => no_read!(), + 0x5C => no_read!(), + 0x5D => no_read!(), + 0x5E => no_read!(), + 0x5F => no_read!(), + // Scratch + 0x80 => no_read!(), + 0x81 => no_read!(), + 0x82 => no_read!(), + 0x83 => no_read!(), + 0x84 => no_read!(), + 0x85 => no_read!(), + 0x86 => no_read!(), + 0x87 => no_read!(), + 0x88 => self.scratch.read_head_1(), + 0x89 => self.scratch.read_head_1(), + 0x8A => self.scratch.read_head_2(), + 0x8B => self.scratch.read_head_2(), + 0x8C => read_hh!(self.scratch.max_capacity), + 0x8D => read_hl!(self.scratch.max_capacity), + 0x8E => read_lh!(self.scratch.max_capacity), + 0x8F => read_ll!(self.scratch.max_capacity), + // Stream + // File + 0xA0 => read_b!(self.file.file.is_some()), + 0xA1 => read_b!(self.file.operation_state), + 0xA2 => no_read!(), + 0xA3 => no_read!(), + 0xA4 => read_hh!(self.file.pointer), + 0xA5 => read_hl!(self.file.pointer), + 0xA6 => read_lh!(self.file.pointer), + 0xA7 => read_ll!(self.file.pointer), + 0xA8 => self.file.read(), + 0xA9 => self.file.read(), + 0xAA => no_read!(), + 0xAB => no_read!(), + 0xAC => read_hh!(self.file.file_size), + 0xAD => read_hl!(self.file.file_size), + 0xAE => read_lh!(self.file.file_size), + 0xAF => read_ll!(self.file.file_size), + + _ => unimplemented!("Reading from device port 0x{port:02x}"), + } + } + + fn write_u8(&mut self, val: u8, port: u8) -> Option<Signal> { + + macro_rules! write_hh { ($v:expr) => { $v = $v & 0x00ffffff | ((val as u32) << 24) }; } + macro_rules! write_hl { ($v:expr) => { $v = $v & 0xff00ffff | ((val as u32) << 16) }; } + macro_rules! write_lh { ($v:expr) => { $v = $v & 0x00ff | ((val as u32) << 8) }; } + macro_rules! write_ll { ($v:expr) => { $v = $v & 0xff00 | (val as u32) }; } + macro_rules! write_h { ($v:expr) => { $v = $v & 0x00ff | ((val as u16) << 8) }; } + macro_rules! write_l { ($v:expr) => { $v = $v & 0xff00 | (val as u16) }; } + + macro_rules! no_write { () => { () }; } + + match port { + // System + 0x00 => write_h!(self.wake_mask), + 0x01 => { write_l!(self.wake_mask); return Some(Signal::Pause) }, + 0x02 => no_write!(), + 0x03 => no_write!(), + 0x04 => no_write!(), + 0x05 => no_write!(), + 0x06 => no_write!(), + 0x07 => no_write!(), + 0x08 => no_write!(), + 0x09 => no_write!(), + 0x0A => no_write!(), + 0x0B => no_write!(), + 0x0C => no_write!(), + 0x0D => no_write!(), + 0x0E => no_write!(), + 0x0F => no_write!(), + // Math + 0x20 => write_h!(self.math.operand_1), + 0x21 => write_l!(self.math.operand_1), + 0x22 => write_h!(self.math.operand_2), + 0x23 => write_l!(self.math.operand_2), + 0x24 => no_write!(), + 0x25 => no_write!(), + 0x26 => no_write!(), + 0x27 => no_write!(), + 0x28 => no_write!(), + 0x29 => no_write!(), + 0x2A => no_write!(), + 0x2B => no_write!(), + 0x2C => no_write!(), + 0x2D => no_write!(), + 0x2E => no_write!(), + 0x2F => no_write!(), + // Clock + 0x38 => write_h!(self.clock.timer_1), + 0x39 => { write_l!(self.clock.timer_1); self.clock.set_timer_1() }, + 0x3A => write_h!(self.clock.timer_2), + 0x3B => { write_l!(self.clock.timer_2); self.clock.set_timer_2() }, + 0x3C => write_h!(self.clock.timer_3), + 0x3D => { write_l!(self.clock.timer_3); self.clock.set_timer_3() }, + 0x3E => write_h!(self.clock.timer_4), + 0x3F => { write_l!(self.clock.timer_4); self.clock.set_timer_4() }, + // Input + 0x40 => no_write!(), + 0x41 => no_write!(), + 0x42 => no_write!(), + 0x43 => no_write!(), + 0x44 => no_write!(), + 0x45 => no_write!(), + 0x46 => no_write!(), + 0x47 => no_write!(), + 0x48 => self.input.character_queue.clear(), + 0x49 => no_write!(), + 0x4A => no_write!(), + 0x4B => no_write!(), + 0x4C => no_write!(), + 0x4D => no_write!(), + 0x4E => no_write!(), + 0x4F => no_write!(), + // Screen + 0x50 => write_h!(self.screen.dimensions.width), + 0x51 => { write_l!(self.screen.dimensions.width); self.screen.set_size(self.screen.dimensions) }, + 0x52 => write_h!(self.screen.dimensions.height), + 0x53 => { write_l!(self.screen.dimensions.height); self.screen.set_size(self.screen.dimensions) }, + 0x54 => write_h!(self.screen.cursor.x), + 0x55 => write_l!(self.screen.cursor.x), + 0x56 => write_h!(self.screen.cursor.y), + 0x57 => write_l!(self.screen.cursor.y), + 0x58 => self.screen.draw(val), + 0x59 => self.screen.shunt(val), + 0x5A => self.screen.sprite_data.push(val), + 0x5B => self.screen.sprite_data.push(val), + 0x5C => self.screen.set_palette_high(val), + 0x5D => self.screen.set_palette_low(val), + 0x5E => self.screen.set_sprite_colour_high(val), + 0x5F => self.screen.set_sprite_colour_low(val), + // Scratch + 0x80 => write_hh!(self.scratch.pointer_1), + 0x81 => write_hl!(self.scratch.pointer_1), + 0x82 => write_lh!(self.scratch.pointer_1), + 0x83 => write_ll!(self.scratch.pointer_1), + 0x84 => write_hh!(self.scratch.pointer_2), + 0x85 => write_hl!(self.scratch.pointer_2), + 0x86 => write_lh!(self.scratch.pointer_2), + 0x87 => write_ll!(self.scratch.pointer_2), + 0x88 => self.scratch.write_head_1(val), + 0x89 => self.scratch.write_head_1(val), + 0x8A => self.scratch.write_head_2(val), + 0x8B => self.scratch.write_head_2(val), + 0x8C => no_write!(), + 0x8D => no_write!(), + 0x8E => no_write!(), + 0x8F => no_write!(), + // File + 0xA0 => self.file.push_name_byte(val), + 0xA1 => no_write!(), + 0xA2 => no_write!(), + 0xA3 => no_write!(), + 0xA4 => write_hh!(self.file.pointer), + 0xA5 => write_hl!(self.file.pointer), + 0xA6 => write_lh!(self.file.pointer), + 0xA7 => write_ll!(self.file.pointer), + 0xA8 => no_write!(), + 0xA9 => no_write!(), + 0xAA => no_write!(), + 0xAB => no_write!(), + 0xAC => write_hh!(self.file.file_size), + 0xAD => write_hl!(self.file.file_size), + 0xAE => write_lh!(self.file.file_size), + 0xAF => { write_ll!(self.file.file_size); self.file.set_file_size() }, + + // Bytestreams + 0x96 => self.stream.write_stdout(val), + 0x97 => self.stream.write_stdout(val), + + _ => unimplemented!("Writing to device port 0x{port:02x}"), + }; + + return None; + + } +} diff --git a/src/devices/clock.rs b/src/devices/clock.rs new file mode 100644 index 0000000..063c67b --- /dev/null +++ b/src/devices/clock.rs @@ -0,0 +1,130 @@ +use std::time::{Duration, Instant}; + +macro_rules! now { () => { + time::OffsetDateTime::now_local() + .unwrap_or_else(|_| time::OffsetDateTime::now_utc()) +};} + +macro_rules! time_from_ticks { + ($ticks:expr) => { Instant::now() + Duration::from_millis(($ticks * 4).into()) }; +} + +macro_rules! ticks_since_time { + ($time:expr) => { ($time.duration_since(Instant::now()).as_millis() / 4) as u16 }; +} + +/// 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) { + self. [< timer_ $i _instant >] = time_from_ticks!(self. [< timer_ $i >]); + } + }}; +} + +/// 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) { + if self. [< timer_ $i >] > 0 { + self. [< timer_ $i >] = ticks_since_time!(self.[< timer_ $i _instant >]); + if self. [< timer_ $i >] == 0 { self.wake_flag = true; } + } + } + }}; +} + +pub struct ClockDevice { + pub wake_flag: bool, + + pub boot_time: Instant, + pub cumulative_seconds: u16, + + pub timer_1_instant: Instant, + pub timer_2_instant: Instant, + pub timer_3_instant: Instant, + pub timer_4_instant: 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, + + boot_time: Instant::now(), + cumulative_seconds: 0, + + timer_1_instant: Instant::now(), + timer_2_instant: Instant::now(), + timer_3_instant: Instant::now(), + timer_4_instant: Instant::now(), + timer_1: 0, + timer_2: 0, + timer_3: 0, + timer_4: 0, + } + } + + pub fn update_cumulative_seconds(&mut self) { + self.cumulative_seconds = self.boot_time.elapsed().as_secs() as u16; + } + + 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 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 shortest_active_timer(&self) -> Option<Duration> { + let mut is_some = false; + let mut value = u16::MAX; + macro_rules! contribute_to_min { + ($timer:expr) => { + if $timer > 0 { + is_some = true; + value = std::cmp::min(value, $timer); + } + }; + } + contribute_to_min!(self.timer_1); + contribute_to_min!(self.timer_2); + contribute_to_min!(self.timer_3); + contribute_to_min!(self.timer_4); + match is_some { + true => Some(Duration::from_millis((value*4).into())), + false => None, + } + } +} diff --git a/src/devices/file.rs b/src/devices/file.rs new file mode 100644 index 0000000..f442dd8 --- /dev/null +++ b/src/devices/file.rs @@ -0,0 +1,90 @@ +use std::io::{ErrorKind, Read, Seek, SeekFrom}; +use std::fs::{File, OpenOptions}; + +pub struct FileDevice { + pub name: Vec<u8>, + pub rename: Vec<u8>, + + pub file: Option<File>, + pub operation_state: bool, + pub pointer: u32, + pub file_size: u32, +} + +impl FileDevice { + pub fn new() -> Self { + Self { + name: Vec::new(), + rename: Vec::new(), + + file: None, + operation_state: false, + pointer: 0, + file_size: 0, + } + } + + pub fn push_name_byte(&mut self, byte: u8) { + if byte != 0 { self.name.push(byte); return } + // If char was null, attempt to open the file and read file size. + let path: std::ffi::OsString = std::os::unix::ffi::OsStringExt::from_vec( + std::mem::take(&mut self.name)); + let try_open = OpenOptions::new().read(true).write(true).open(path); + if let Ok(file) = try_open { + if let Ok(metadata) = file.metadata() { + // Success. + self.file = Some(file); + match u32::try_from(metadata.len()) { + Ok(len) => self.file_size = len, + Err(_) => self.file_size = u32::MAX, + } + return; + } + } + // Failure. + self.close(); + } + + pub fn set_file_size(&mut self) { + if let Some(file) = &self.file { + if let Ok(()) = file.set_len(self.file_size.into()) { + return; + }; + } + self.close(); + } + + pub fn read(&mut self) -> u8 { + if let Some(file) = &mut self.file { + let mut buffer: [u8; 1] = [0; 1]; + match file.read_exact(&mut buffer) { + Ok(()) => return buffer[0], + Err(err) => match err.kind() { + ErrorKind::UnexpectedEof => return 0, + _ => unimplemented!("File read error: ErrorKind::{:?}", err.kind()), + } + } + } + self.close(); + return 0; + } + + pub fn seek(&mut self) { + if let Some(file) = &mut self.file { + let point = SeekFrom::Start(self.pointer.into()); + if let Ok(pointer) = file.seek(point) { + if let Ok(pointer_u32) = pointer.try_into() { + self.pointer = pointer_u32; + return; + } + }; + } + self.close(); + } + + pub fn close(&mut self) { + self.file = None; + self.pointer = 0; + self.file_size = 0; + } +} diff --git a/src/devices/input.rs b/src/devices/input.rs new file mode 100644 index 0000000..f3191dd --- /dev/null +++ b/src/devices/input.rs @@ -0,0 +1,128 @@ +use crate::*; + +use std::collections::VecDeque; + +pub struct InputDevice { + pub wake_flag: bool, + + pub mouse_position: ScreenPosition, + pub mouse_button_state: u8, + + pub horizontal_scroll_value: u16, + pub vertical_scroll_value: u16, + pub horizontal_scroll_value_delta: f64, + pub vertical_scroll_value_delta: f64, + + pub character_queue: VecDeque<u8>, + pub modifier_state: u8, + pub navigation_state: u8, +} + +impl InputDevice { + pub fn new() -> Self { + Self { + wake_flag: false, + + mouse_position: ScreenPosition::ZERO, + mouse_button_state: 0x00, + + horizontal_scroll_value: 0x0000, + vertical_scroll_value: 0x0000, + horizontal_scroll_value_delta: 0.0, + vertical_scroll_value_delta: 0.0, + + character_queue: VecDeque::new(), + modifier_state: 0x00, + navigation_state: 0x00, + } + } + + pub fn mouse_button_action(&mut self, mask: u8, action: phosphor::Action) { + let new_button_state = match action { + phosphor::Action::Pressed => self.mouse_button_state | mask, + phosphor::Action::Released => self.mouse_button_state & !mask, + }; + if new_button_state != self.mouse_button_state { + self.mouse_button_state = new_button_state; + self.wake_flag = true; + } + } + + pub fn move_mouse(&mut self, new_mouse_position: ScreenPosition) { + let old_mouse_position = self.mouse_position; + if new_mouse_position != old_mouse_position { + self.mouse_position = new_mouse_position; + self.wake_flag = true; + } + } + + pub fn on_scroll_horizontal(&mut self, delta: f64) { + self.horizontal_scroll_value_delta += delta; + while self.horizontal_scroll_value_delta > 20.0 { + self.horizontal_scroll_value += 1; + self.horizontal_scroll_value_delta -= 20.0; + self.wake_flag = true; + } + while self.horizontal_scroll_value_delta < -20.0 { + self.horizontal_scroll_value -= 1; + self.horizontal_scroll_value_delta += 20.0; + self.wake_flag = true; + } + } + + pub fn on_scroll_vertical(&mut self, delta: f64) { + self.vertical_scroll_value_delta += delta; + while self.vertical_scroll_value_delta > 20.0 { + self.vertical_scroll_value += 1; + self.vertical_scroll_value_delta -= 20.0; + self.wake_flag = true; + } + while self.vertical_scroll_value_delta < -20.0 { + self.vertical_scroll_value -= 1; + self.vertical_scroll_value_delta += 20.0; + self.wake_flag = true; + } + } + + pub fn on_character_input(&mut self, input: char) { + if let Ok(ascii) = u8::try_from(u32::from(input)) { + self.character_queue.push_back(ascii); + self.wake_flag = true; + } + } + + pub fn on_keyboard_input(&mut self, input: phosphor::KeyboardInput) { + let tab = self.modifier_state & 0x40 != 0; + let mask = match input.key { + phosphor::KeyCode::Up => 0x80, + phosphor::KeyCode::Down => 0x40, + phosphor::KeyCode::Left => 0x20, + phosphor::KeyCode::Right => 0x10, + phosphor::KeyCode::Tab => match tab { false => 0x08, true => 0x04 }, + phosphor::KeyCode::Return => 0x02, + phosphor::KeyCode::Escape => 0x01, + _ => return, + }; + let new_navigation_state = match input.action { + phosphor::Action::Pressed => self.navigation_state | mask, + phosphor::Action::Released => self.navigation_state & !mask, + }; + if new_navigation_state != self.navigation_state { + self.navigation_state = new_navigation_state; + self.wake_flag = true; + } + } + + pub fn on_modifier_change(&mut self, modifiers: phosphor::ModifiersState) { + let mut new_modifier_state = 0x00; + if modifiers.ctrl() { new_modifier_state |= 0x80 } + if modifiers.shift() { new_modifier_state |= 0x40 } + if modifiers.alt() { new_modifier_state |= 0x20 } + if modifiers.logo() { new_modifier_state |= 0x10 } + if new_modifier_state != self.modifier_state { + self.modifier_state = new_modifier_state; + self.wake_flag = true; + } + + } +} diff --git a/src/devices/math.rs b/src/devices/math.rs new file mode 100644 index 0000000..6260529 --- /dev/null +++ b/src/devices/math.rs @@ -0,0 +1,35 @@ +pub struct MathDevice { + pub operand_1: u16, + pub operand_2: u16, + pub product_high: u16, + pub product_low: u16, + pub quotient: u16, + pub remainder: u16, +} + +impl MathDevice { + pub fn new() -> Self { + Self { + operand_1: 0x0000, + operand_2: 0x0000, + product_high: 0x0000, + product_low: 0x0000, + quotient: 0x0000, + remainder: 0x0000, + } + } + + pub fn multiply(&mut self) { + let (high, low) = self.operand_1.widening_mul(self.operand_2); + self.product_high = high; + self.product_low = low; + } + + pub fn divide(&mut self) { + self.quotient = self.operand_1.checked_div(self.operand_2).unwrap_or(0); + } + + pub fn modulo(&mut self) { + self.remainder = self.operand_1.checked_rem(self.operand_2).unwrap_or(0); + } +} diff --git a/src/devices/scratch.rs b/src/devices/scratch.rs new file mode 100644 index 0000000..eea011b --- /dev/null +++ b/src/devices/scratch.rs @@ -0,0 +1,58 @@ +pub struct ScratchDevice { + memory: Vec<u8>, + pub max_capacity: u32, + pub pointer_1: u32, + pub pointer_2: u32, +} + +impl ScratchDevice { + pub fn new() -> Self { + Self { + memory: Vec::new(), + max_capacity: 16 * 65536, + + pointer_1: 0, + pointer_2: 0, + } + } + + pub fn read_head_1(&mut self) -> u8 { + let value = self.read_byte(self.pointer_1); + self.pointer_1 += 1; + return value; + } + + pub fn read_head_2(&mut self) -> u8 { + let value = self.read_byte(self.pointer_2); + self.pointer_2 += 1; + return value; + } + + pub fn write_head_1(&mut self, value: u8) { + self.write_byte(self.pointer_1, value); + self.pointer_1 += 1; + } + + pub fn write_head_2(&mut self, value: u8) { + self.write_byte(self.pointer_2, value); + self.pointer_2 += 1; + } + + fn read_byte(&self, pointer: u32) -> u8 { + let pointer = pointer as usize; + match self.memory.get(pointer) { + Some(value) => *value, + None => 0, + } + } + + fn write_byte(&mut self, pointer: u32, value: u8) { + if pointer < self.max_capacity { + let pointer = pointer as usize; + if pointer >= self.memory.len() { + self.memory.resize(pointer + 1, 0); + } + self.memory[pointer] = value; + } + } +} diff --git a/src/devices/screen.rs b/src/devices/screen.rs new file mode 100644 index 0000000..df9539a --- /dev/null +++ b/src/devices/screen.rs @@ -0,0 +1,469 @@ +mod sprite_data; +mod vector_points; + +pub use sprite_data::*; +pub use vector_points::*; + +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>; + +#[derive(Copy, Clone)] +pub enum ScreenLayer { Background, Foreground } + +pub struct ScreenDevice { + pub wake_flag: bool, + /// Each byte represents a screen pixel, left-right-top-bottom. + // Only the bottom four bits of each byte are used. + pub foreground: Vec<u8>, + pub background: Vec<u8>, + pub is_dirty: bool, + pub is_resizable: bool, + + pub cursor: ScreenPosition, + pub dimensions: ScreenDimensions, + pub sprite_data: SpriteData, + pub palette: [Colour; 16], + pub palette_high: u8, + pub sprite_colours: [u8; 4], + pub vector: VectorPoints, +} + +impl ScreenDevice { + pub fn new() -> Self { + Self { + wake_flag: false, + + foreground: Vec::new(), + background: Vec::new(), + is_dirty: false, + is_resizable: true, + + cursor: ScreenPosition::ZERO, + dimensions: ScreenDimensions::ZERO, + sprite_data: SpriteData::new(), + palette: [Colour::BLACK; 16], + palette_high: 0, + sprite_colours: [0; 4], + + vector: VectorPoints::new(), + } + } + + pub fn set_size(&mut self, dimensions: ScreenDimensions) { + self.is_resizable = false; + self.resize(dimensions); + } + + 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.is_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 palette = [Colour::BLACK; 256]; + for (i, c) in palette.iter_mut().enumerate() { + match i > 0x0f { + true => *c = self.palette[i >> 4], + false => *c = self.palette[i & 0x0f], + } + }; + 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 = palette[(fg << 4 | bg) as usize]; + } + } 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 = palette[(fg << 4 | bg) as usize]; + } + b_slice[bi+width..bi+width+width_excess].fill(palette[0]); + bi += b_width; + si += s_width; + } + b_slice[bi..].fill(palette[0]); + } + + // Set flags + self.is_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.is_dirty = true; + } + + pub fn set_sprite_colour_high(&mut self, val: u8) { + self.sprite_colours[0] = val >> 4; + self.sprite_colours[1] = val & 0x0f; + } + + pub fn set_sprite_colour_low(&mut self, val: u8) { + self.sprite_colours[2] = val >> 4; + self.sprite_colours[3] = val & 0x0f; + } + + pub fn shunt(&mut self, val: u8) { + let is_negative = val & 0x80 != 0; + let is_vertical = val & 0x40 != 0; + let dist = (val & 0x3f) as u16; + match (is_negative, is_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) { + self.vector.push(self.cursor); + self.is_dirty = true; + // Parse draw byte + let draw_mode = val & 0x70; + let params = val & 0x0f; + let layer = match val & 0x80 != 0 { + true => ScreenLayer::Foreground, + false => ScreenLayer::Background + }; + match draw_mode { + 0x00 => self.draw_pixel(params, layer, self.cursor), + 0x10 => self.draw_sprite_1bit(params, layer), + 0x20 => self.fill_layer(params, layer), + 0x30 => self.draw_sprite_2bit(params, layer), + 0x40 => self.draw_line(params, layer), + 0x50 => self.draw_rect(params, layer), + 0x60 => todo!("Draw 1-bit sprite triangle"), + 0x70 => self.draw_rect_1bit(params, layer), + _ => unreachable!(), + }; + } + + fn draw_pixel(&mut self, colour: u8, layer: ScreenLayer, point: ScreenPosition) { + let dim = self.dimensions; + if !dim.contains_point(point) { 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_sprite_1bit(&mut self, params: u8, layer: ScreenLayer) { + let mut sprite = [0; 64]; + let mut pointer: usize = 0; + let data = self.sprite_data.get_1bit_sprite(); + for row in data { + for x in (0..8).rev() { + sprite[pointer] = (row >> x) & 0x1; + pointer += 1; + } + } + self.draw_sprite(params, layer, sprite); + } + + fn fill_layer(&mut self, colour: u8, layer: ScreenLayer) { + match layer { + ScreenLayer::Background => self.background.fill(colour), + ScreenLayer::Foreground => self.foreground.fill(colour), + } + } + + fn draw_sprite_2bit(&mut self, params: u8, layer: ScreenLayer) { + let mut sprite = [0; 64]; + let mut pointer: usize = 0; + let data = self.sprite_data.get_2bit_sprite(); + let (spr1, spr2) = data.split_array_ref::<8>(); + for (row1, row2) in std::iter::zip(spr1, spr2) { + for x in (0..8).rev() { + let bit1 = (row1 >> x << 1) & 0x2; + let bit2 = (row2 >> x) & 0x1; + sprite[pointer] = bit1 | bit2; + pointer += 1; + } + } + self.draw_sprite(params, layer, sprite); + } + + fn draw_line(&mut self, colour: u8, layer: ScreenLayer) { + let points = self.vector.get_pair(); + match (points[0].x == points[1].x, points[0].y == points[1].y) { + (false, false) => self.draw_diagonal_line(colour, layer, points), + (false, true) => self.draw_horizontal_line(colour, layer, points), + ( true, false) => self.draw_vertical_line(colour, layer, points), + ( true, true) => self.draw_pixel(colour, layer, points[0]), + }; + } + + fn draw_diagonal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + fn abs_diff(v0: u16, v1: u16) -> u16 { + let v = v1.wrapping_sub(v0); + if v > 0x8000 { !v + 1 } else { v } + } + let [p0, p1] = points; + + // 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 so that y0 is always smaller than y1. + let (x0, y0, x1, y1) = match points[0].y > points[1].y { + true => (points[1].x, points[1].y, points[0].x, points[0].y), + false => (points[0].x, points[0].y, points[1].x, points[1].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 points[0].x > points[1].x { + true => (points[1].x, points[1].y, points[0].x, points[0].y), + false => (points[0].x, points[0].y, points[1].x, points[1].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_horizontal_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + let [start, end] = points; + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + if (x0 >= dim.width && x1 >= dim.width) || start.y >= dim.height { return } + let x0 = min(x0, dim.width.saturating_sub(1)); + let x1 = min(x1, dim.width.saturating_sub(1)); + let row_i = (dim.width as usize) * (start.y as usize); + let start_i = row_i + x0 as usize; + let end_i = row_i + x1 as usize; + let layer = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + layer[start_i..=end_i].fill(colour); + return + } + + fn draw_vertical_line(&mut self, colour: u8, layer: ScreenLayer, points: [ScreenPosition; 2]) { + let [start, end] = points; + let dim = self.dimensions; + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (y0 >= dim.height && y1 >= dim.height) || start.x >= dim.width { return } + let y0 = min(y0, dim.height.saturating_sub(1)); + let y1 = min(y1, dim.height.saturating_sub(1)); + let mut i = (start.x as usize) + (dim.width as usize * (y0 as usize)); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + pixels[i] = colour; + i += dim.width as usize; + } + return + } + + fn draw_rect(&mut self, colour: u8, layer: ScreenLayer) { + let [start, end] = self.vector.get_pair(); + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return } + let x0 = min(x0, dim.width.saturating_sub(1)) as usize; + let x1 = min(x1, dim.width.saturating_sub(1)) as usize; + let y0 = min(y0, dim.height.saturating_sub(1)) as usize; + let y1 = min(y1, dim.height.saturating_sub(1)) as usize; + let width = x1 - x0 + 1; + let mut i = x0 + ((dim.width as usize) * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + for _ in y0..=y1 { + pixels[i..i+width].fill(colour); + i += dim.width as usize; + } + } + + fn draw_rect_1bit(&mut self, params: u8, layer: ScreenLayer) { + let [start, end] = self.vector.get_pair(); + let dim = self.dimensions; + let x0 = min(start.x, end.x); + let x1 = max(start.x, end.x); + let y0 = min(start.y, end.y); + let y1 = max(start.y, end.y); + if (x0 >= dim.width && x1 >= dim.width) || (y0 >= dim.height && y1 >= dim.height) { return } + let x0 = min(x0, dim.width.saturating_sub(1)) as usize; + let x1 = min(x1, dim.width.saturating_sub(1)) as usize; + let y0 = min(y0, dim.height.saturating_sub(1)) as usize; + let y1 = min(y1, dim.height.saturating_sub(1)) as usize; + let width = x1 - x0 + 1; + let mut i = x0 + ((dim.width as usize) * y0); + let pixels = match layer { + ScreenLayer::Background => &mut self.background, + ScreenLayer::Foreground => &mut self.foreground, + }; + + let sprite_data = self.sprite_data.get_1bit_sprite(); + let mut sprite_i = y0 % 8; + let sprite_x_off = (x0 % 8) as u32; + let transparent = params & 0x08 != 0; + if params & 0x07 != 0 { + todo!("Pre-treat sprite, with rotation/translation"); + } + + for _ in y0..=y1 { + let mut row = sprite_data[sprite_i].rotate_left(sprite_x_off); + for _ in x0..=x1 { + let colour = (row >> 7) as usize; + if !(transparent && colour == 0) { + pixels[i] = self.sprite_colours[colour]; + } + row = row.rotate_left(1); + i += 1; + } + sprite_i = (sprite_i + 1) % 8; + i += (dim.width as usize) - width; + } + } + + fn draw_sprite(&mut self, params: u8, layer: ScreenLayer, sprite: [u8; 64]) { + let transparent = params & 0x08 != 0; + let mut position = self.cursor; + let mut pointer: usize = 0; + + macro_rules! inc_x { ($v:expr) => { position.x = position.x.wrapping_add($v) }; } + macro_rules! dec_x { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; } + macro_rules! inc_y { ($v:expr) => { position.y = position.y.wrapping_add($v) }; } + macro_rules! dec_y { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; } + macro_rules! plot { () => { + let colour = sprite[pointer]; + if !(transparent && colour == 0) { + self.draw_pixel(self.sprite_colours[colour as usize], layer, position); + } + pointer += 1; + }; } + + match params & 0x07 { + 0x00 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); inc_y!(1); } } + 0x01 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); inc_y!(1); } } + 0x02 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_x!(1); } dec_x!(8); dec_y!(1); } } + 0x03 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_x!(1); } inc_x!(8); dec_y!(1); } } + + 0x04 => { for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); inc_x!(1); } } + 0x05 => { inc_x!(7); for _ in 0..8 { for _ in 0..8 { plot!(); inc_y!(1); } dec_y!(8); dec_x!(1); } } + 0x06 => { inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); inc_x!(1); } } + 0x07 => { inc_x!(7); inc_y!(7); for _ in 0..8 { for _ in 0..8 { plot!(); dec_y!(1); } inc_y!(8); dec_x!(1); } } + + _ => unreachable!(), + } + } +} diff --git a/src/devices/screen/sprite_data.rs b/src/devices/screen/sprite_data.rs new file mode 100644 index 0000000..aade2c6 --- /dev/null +++ b/src/devices/screen/sprite_data.rs @@ -0,0 +1,33 @@ +pub struct SpriteData { + data: [u8; 16], + pointer: usize, +} + +impl SpriteData { + pub fn new() -> Self { + Self { data: [0; 16], pointer: 0 } + } + + pub fn push(&mut self, val: u8) { + self.data[self.pointer] = val; + self.pointer = (self.pointer + 1) % 16; + } + + pub fn get_1bit_sprite(&self) -> [u8; 8] { + let mut sprite = [0u8; 8]; + for (i, r) in sprite.iter_mut().enumerate() { + *r = self.data[(self.pointer + i + 8) % 16] + } + return sprite; + } + + pub fn get_2bit_sprite(&self) -> [u8; 16] { + let mut sprite = [0u8; 16]; + for (i, r) in sprite.iter_mut().enumerate() { + *r = self.data[(self.pointer + i) % 16] + } + return sprite; + } +} + + diff --git a/src/devices/screen/vector_points.rs b/src/devices/screen/vector_points.rs new file mode 100644 index 0000000..97d95b6 --- /dev/null +++ b/src/devices/screen/vector_points.rs @@ -0,0 +1,28 @@ +use crate::*; + +pub struct VectorPoints { + points: [ScreenPosition; 3], + pointer: usize, +} + +impl VectorPoints { + pub fn new() -> Self { + Self { points: [ScreenPosition::ZERO; 3], pointer: 0 } + } + + pub fn push(&mut self, point: ScreenPosition) { + self.points[self.pointer] = point; + self.pointer = (self.pointer + 1) % 3; + } + + pub fn get_pair(&self) -> [ScreenPosition; 2] { + [ + self.points[(self.pointer + 1) % 3], + self.points[(self.pointer + 2) % 3], + ] + } + + pub fn get_triple(&self) -> [ScreenPosition; 3] { + self.points + } +} diff --git a/src/devices/stream.rs b/src/devices/stream.rs new file mode 100644 index 0000000..3d376b6 --- /dev/null +++ b/src/devices/stream.rs @@ -0,0 +1,46 @@ +use std::io::{Read, Write, Stdin, Stdout}; + +pub struct StreamDevice { + pub wake_flag: bool, + + pub input_control: bool, + pub output_control: bool, + pub read_queue: Vec<u8>, + + pub stdin: Stdin, + pub stdout: Stdout, +} + +impl StreamDevice { + pub fn new() -> Self { + Self { + wake_flag: false, + + input_control: true, + output_control: true, + read_queue: Vec::with_capacity(256), + + stdin: std::io::stdin(), + stdout: std::io::stdout(), + } + } + + // pub fn fetch_stdin(&mut self) { + // match self.stdin.read(&mut self.read_queue) { + // Ok() => (), + // Err() => (), + // } + // } + + 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(); + } +} diff --git a/src/emulator.rs b/src/emulator.rs new file mode 100644 index 0000000..91d33e9 --- /dev/null +++ b/src/emulator.rs @@ -0,0 +1,194 @@ +use crate::*; + +use bedrock_core::*; +use phosphor::*; + +use std::time::*; + +const FRAME: Duration = Duration::from_micros(16666); + +pub struct BedrockEmulator { + vm: Processor<StandardDevices>, + process_mark: Instant, + frame_mark: Instant, + debug_mark: Instant, + waiting_to_start: bool, + is_paused: bool, + scale: u32, + cycles_since_debug: usize, +} + +impl BedrockEmulator { + pub fn new(bytecode: &[u8]) -> Self { + let mut vm = Processor::new(StandardDevices::new()); + vm.load_program(bytecode); + Self { + vm, + process_mark: Instant::now(), + frame_mark: Instant::now(), + debug_mark: Instant::now(), + waiting_to_start: true, + is_paused: false, + scale: 2, + cycles_since_debug: 0, + } + } + + pub fn debug(&mut self) { + macro_rules! yellow {()=>{eprint!("\x1b[33m")};} + macro_rules! normal {()=>{eprint!("\x1b[0m")};} + macro_rules! print_stack { + ($stack:expr, $len:expr) => { + for i in 0..$len { + if i == $stack.sp as usize { yellow!(); } else { normal!(); } + eprint!("{:02x} ", $stack.mem[i]); + } + normal!(); + }; + } + eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", + self.vm.mem.pc, self.vm.cycles, + self.vm.cycles - self.cycles_since_debug, + self.debug_mark.elapsed()); + eprint!("WST: "); + print_stack!(self.vm.wst, 0x10); + eprint!("\nRST: "); + print_stack!(self.vm.rst, 0x10); + eprintln!(); + self.cycles_since_debug = self.vm.cycles; + self.debug_mark = Instant::now(); + } + + pub fn run(self) -> ! { + let mut wm = WindowManager::new(); + wm.add_window(Box::new(self)); + wm.run(); + } +} + +impl WindowController for BedrockEmulator { + fn title(&self) -> String { + String::from("Bedrock emulator") + } + + fn exact_size(&self) -> Option<Dimensions> { + match self.vm.dev.screen.is_resizable { + true => None, + false => Some(Dimensions::new( + self.vm.dev.screen.dimensions.width as u32, + self.vm.dev.screen.dimensions.height as u32, + )), + } + } + + fn is_cursor_visible(&self) -> bool { + let pos = self.vm.dev.input.mouse_position; + let dim = self.vm.dev.screen.dimensions; + pos.x >= dim.width || pos.y >= dim.height + } + + fn pixel_scale(&self) -> NonZeroU32 { + NonZeroU32::new(self.scale).unwrap() + } + + fn on_resize(&mut self, dimensions: Dimensions) { + self.vm.dev.screen.resize(ScreenDimensions { + width: dimensions.width as u16, + height: dimensions.height as u16, + }); + self.waiting_to_start = false; + } + + fn on_cursor_move(&mut self, position: Point) { + self.vm.dev.input.move_mouse(ScreenPosition::new(position.x as u16, position.y as u16)); + } + fn on_cursor_exit(&mut self) { + self.vm.dev.input.move_mouse(ScreenPosition::new(0x7FFF, 0x7FFF)); + } + fn on_left_mouse_button(&mut self, action: Action) { + self.vm.dev.input.mouse_button_action(0x80, action); + } + fn on_middle_mouse_button(&mut self, action: Action) { + self.vm.dev.input.mouse_button_action(0x40, action); + } + fn on_right_mouse_button(&mut self, action: Action) { + self.vm.dev.input.mouse_button_action(0x20, action); + } + fn on_line_scroll_horizontal(&mut self, delta: f64) { + self.vm.dev.input.on_scroll_horizontal(delta * 20.0); + } + fn on_line_scroll_vertical(&mut self, delta: f64) { + self.vm.dev.input.on_scroll_vertical(delta * 20.0); + } + fn on_pixel_scroll_horizontal(&mut self, delta: f64) { + self.vm.dev.input.on_scroll_horizontal(delta); + } + fn on_pixel_scroll_vertical(&mut self, delta: f64) { + self.vm.dev.input.on_scroll_vertical(delta); + } + fn on_character_input(&mut self, input: char) { + self.vm.dev.input.on_character_input(input); + } + fn on_keyboard_input(&mut self, input: KeyboardInput) { + self.vm.dev.input.on_keyboard_input(input); + if input.action.is_pressed() { + match input.key { + KeyCode::F1 => self.scale = std::cmp::max(1, self.scale - 1), + KeyCode::F2 => self.scale = std::cmp::min(8, self.scale + 1), + _ => (), + } + } + } + fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) { + self.vm.dev.input.on_modifier_change(modifiers); + } + + fn on_process(&mut self) { + let frame_remaining = match self.frame_mark.elapsed() < FRAME { + true => FRAME.saturating_sub(self.frame_mark.elapsed()), + false => FRAME, + }; + if self.waiting_to_start || (self.is_paused && !self.vm.dev.can_wake()) { + let sleep_duration = match self.vm.dev.clock.shortest_active_timer() { + Some(ms) => std::cmp::min(frame_remaining, ms), + None => frame_remaining, + }; + std::thread::sleep(sleep_duration); + self.process_mark = Instant::now(); + return; + } + self.is_paused = false; + while self.process_mark.elapsed() < frame_remaining { + // std::thread::sleep(std::time::Duration::from_micros(1 * 1000 as u64)); + if let Some(signal) = self.vm.evaluate(1000) { + match signal { + Signal::Debug => self.debug(), + Signal::Pause => { self.is_paused = true; break }, + Signal::Halt => exit(0), + } + } + } + std::thread::sleep(frame_remaining.saturating_sub(self.process_mark.elapsed())); + self.process_mark = Instant::now(); + } + + fn render_request(&mut self) -> RenderRequest { + if self.vm.dev.screen.is_dirty { + match self.is_paused { + true => RenderRequest::UPDATE, + false => match self.frame_mark.elapsed() >= 6 * FRAME { + true => RenderRequest::UPDATE, + false => RenderRequest::NONE, + } + } + } else { + self.frame_mark = Instant::now(); + RenderRequest::NONE + } + } + + fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) { + self.vm.dev.screen.render(buffer); + self.frame_mark = Instant::now(); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..98bc20f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,22 @@ +#![feature(bigint_helper_methods)] +#![feature(split_array)] + +use std::io::Read; +use std::process::exit; + +mod devices; +mod emulator; + +pub use devices::*; +pub use emulator::*; + +fn main() { + // Read bytecode from standard input + let mut bytecode: Vec<u8> = Vec::new(); + match std::io::stdin().take(64*1024).read_to_end(&mut bytecode) { + Ok(len) => eprintln!("Loaded {len} bytes of bytecode."), + Err(err) => { eprintln!("Could not read from standard input, quitting.\n({err:?})"); exit(1); } + }; + BedrockEmulator::new(&bytecode).run(); +} + |