diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-04-16 10:51:13 +1200 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-04-16 10:51:26 +1200 |
commit | 6b3796c9a0d3a2f1422bcbde4790c43417659722 (patch) | |
tree | 6429a5fa2f8c4d3b26790775e07e46e6338b61d3 | |
parent | 28101de56231252ca0cfa6a9f107b75112c9acad (diff) | |
download | bedrock-pc-6b3796c9a0d3a2f1422bcbde4790c43417659722.zip |
Update devices to match new specifications
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/devices.rs | 319 | ||||
-rw-r--r-- | src/devices/clock.rs | 57 | ||||
-rw-r--r-- | src/devices/file/directory_entry.rs | 2 | ||||
-rw-r--r-- | src/devices/file/directory_listing.rs | 18 | ||||
-rw-r--r-- | src/devices/file/entry.rs | 2 | ||||
-rw-r--r-- | src/devices/input.rs | 176 | ||||
-rw-r--r-- | src/devices/math.rs | 28 | ||||
-rw-r--r-- | src/devices/memory.rs | 89 | ||||
-rw-r--r-- | src/devices/scratch.rs | 61 | ||||
-rw-r--r-- | src/devices/screen.rs | 344 | ||||
-rw-r--r-- | src/devices/screen/draw_line.rs | 223 | ||||
-rw-r--r-- | src/devices/screen/draw_rect.rs | 42 | ||||
-rw-r--r-- | src/devices/screen/draw_sprite.rs | 25 | ||||
-rw-r--r-- | src/devices/screen/sprite_data.rs | 98 | ||||
-rw-r--r-- | src/devices/screen/vector_points.rs | 21 | ||||
-rw-r--r-- | src/devices/system.rs | 4 | ||||
-rw-r--r-- | src/devices/system/read_only_text_buffer.rs | 23 | ||||
-rw-r--r-- | src/emulator.rs | 167 | ||||
-rw-r--r-- | src/main.rs | 6 |
21 files changed, 1010 insertions, 701 deletions
@@ -40,8 +40,8 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "bedrock_core" -version = "1.0.0" -source = "git+git://benbridle.com/bedrock_core?tag=v2.0.0#82b5f5c1cfc5c164e357b7ac02ce3f8417faa260" +version = "3.0.0" +source = "git+git://benbridle.com/bedrock_core?tag=v3.0.0#8929fbc0c62db0a3ddf73c745636609f951afc20" [[package]] name = "bedrock_emu" @@ -8,7 +8,7 @@ description = "Emulator for running Bedrock programs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bedrock_core = { git = "git://benbridle.com/bedrock_core", tag = "v2.0.0" } +bedrock_core = { git = "git://benbridle.com/bedrock_core", tag = "v3.0.0" } phosphor = { git = "git://benbridle.com/phosphor", tag = "v1.0.0" } geometry = { git = "git://benbridle.com/geometry", tag = "v1.0.0" } diff --git a/src/devices.rs b/src/devices.rs index e1a00f7..e191746 100644 --- a/src/devices.rs +++ b/src/devices.rs @@ -1,69 +1,67 @@ -use bedrock_core::*; - +mod system; +mod memory; 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::*; +use bedrock_core::*; +pub use system::ReadOnlyTextBuffer; +pub use screen::{ScreenDimensions, ScreenPosition}; + +const LEN_PROGRAM_MEMORY: u16 = 0xffff; +const LEN_WORKING_STACK: u8 = 0xff; +const LEN_RETURN_STACK: u8 = 0xff; +const CONNECTED_DEVICES: u16 = 0b_1111_1100_1100_0000; + 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 memory: memory::MemoryDevice, + pub math: math::MathDevice, + pub clock: clock::ClockDevice, + pub input: input::InputDevice, + pub screen: screen::ScreenDevice, + pub stream: stream::StreamDevice, + pub file: file::FileDevice, + + pub name: ReadOnlyTextBuffer, pub wake_mask: u16, - pub wake_device: u8, + pub wake_id: 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(), + memory: memory::MemoryDevice::new(), + math: math::MathDevice::new(), + clock: clock::ClockDevice::new(), + input: input::InputDevice::new(), + screen: screen::ScreenDevice::new(), + stream: stream::StreamDevice::new(), + file: file::FileDevice::new(), + + name: ReadOnlyTextBuffer::from_text("Bedrock for PC"), wake_mask: 0x0000, - wake_device: 0x00, + wake_id: 0x00, } } pub fn can_wake(&mut self) -> bool { macro_rules! test_wake { - ($flag:expr, $mask:expr, $index:expr) => { + ($flag:expr, $id:expr, $mask:expr) => { if $flag && self.wake_mask & $mask != 0 { - $flag = false; self.wake_device = $index; return true; - } + $flag = false; self.wake_id = $id; 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); + self.clock.update_timers(); + test_wake!(self.clock.wake_flag, 0x3, 0x1000); + test_wake!(self.input.wake_flag, 0x4, 0x0800); + test_wake!(self.screen.wake_flag, 0x5, 0x0400); + test_wake!(self.stream.wake_flag, 0x8, 0x0080); return false; } } @@ -77,27 +75,43 @@ impl DeviceBus for StandardDevices { 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, + 0x00 => self.name.read_byte(), + 0x01 => self.wake_id, + 0x02 => no_read!(), 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, + 0x04 => 0x00, + 0x05 => 0x00, + 0x06 => 0x00, + 0x07 => 0x00, + 0x08 => read_h!(LEN_PROGRAM_MEMORY), + 0x09 => read_l!(LEN_PROGRAM_MEMORY), + 0x0A => LEN_WORKING_STACK, + 0x0B => LEN_RETURN_STACK, + 0x0C => read_h!(CONNECTED_DEVICES), + 0x0D => read_l!(CONNECTED_DEVICES), + 0x0E => no_read!(), + 0x0F => no_read!(), + // Memory + 0x10 => read_h!(self.memory.page_1), + 0x11 => read_l!(self.memory.page_1), + 0x12 => read_h!(self.memory.address_1), + 0x13 => read_l!(self.memory.address_1), + 0x14 => self.memory.read_from_head_1(), + 0x15 => self.memory.read_from_head_1(), + 0x16 => read_h!(self.memory.page_count()), + 0x17 => read_l!(self.memory.page_count()), + 0x18 => read_h!(self.memory.page_2), + 0x19 => read_l!(self.memory.page_2), + 0x1A => read_h!(self.memory.address_2), + 0x1B => read_l!(self.memory.address_2), + 0x1C => self.memory.read_from_head_2(), + 0x1D => self.memory.read_from_head_2(), + 0x1E => no_read!(), + 0x1F => no_read!(), // Math 0x20 => no_read!(), 0x21 => no_read!(), @@ -107,14 +121,14 @@ impl DeviceBus for StandardDevices { 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) }, + 0x28 => read_h!(self.math.multiply_high()), + 0x29 => read_l!(self.math.multiply_high()), + 0x2A => read_h!(self.math.multiply_low()), + 0x2B => read_l!(self.math.multiply_low()), + 0x2C => read_h!(self.math.divide()), + 0x2D => read_l!(self.math.divide()), + 0x2E => read_h!(self.math.modulo()), + 0x2F => read_l!(self.math.modulo()), // Clock 0x30 => self.clock.year(), 0x31 => self.clock.month(), @@ -122,8 +136,8 @@ impl DeviceBus for StandardDevices { 0x33 => self.clock.hour(), 0x34 => self.clock.minute(), 0x35 => self.clock.second(), - 0x36 => read_h!(self.clock.update_cumulative_timer()), - 0x37 => read_l!(self.clock.cumulative_timer), + 0x36 => read_h!(self.clock.update_uptime()), + 0x37 => read_l!(self.clock.uptime), 0x38 => read_h!(self.clock.update_timer_1()), 0x39 => read_l!(self.clock.timer_1), 0x3A => read_h!(self.clock.update_timer_2()), @@ -133,27 +147,31 @@ impl DeviceBus for StandardDevices { 0x3E => read_h!(self.clock.update_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, + 0x40 => read_h!(self.input.pointer_position.x), + 0x41 => read_l!(self.input.pointer_position.x), + 0x42 => read_h!(self.input.pointer_position.y), + 0x43 => read_l!(self.input.pointer_position.y), + 0x44 => read_b!(self.input.pointer_active), + 0x45 => self.input.pointer_buttons, + 0x46 => self.input.read_horizontal_scroll(), + 0x47 => self.input.read_vertical_scroll(), + 0x48 => 0xff, + 0x49 => self.input.text_queue.pop_front().unwrap_or(0), + 0x4A => self.input.modifiers, + 0x4B => self.input.navigation, + 0x4C => self.input.controller_1, + 0x4D => self.input.controller_2, + 0x4E => self.input.controller_3, + 0x4F => self.input.controller_4, // 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), + 0x50 => read_h!(self.screen.cursor.x), + 0x51 => read_l!(self.screen.cursor.x), + 0x52 => read_h!(self.screen.cursor.y), + 0x53 => read_l!(self.screen.cursor.y), + 0x54 => read_h!(self.screen.dimensions.width), + 0x55 => read_l!(self.screen.dimensions.width), + 0x56 => read_h!(self.screen.dimensions.height), + 0x57 => read_l!(self.screen.dimensions.height), 0x58 => no_read!(), 0x59 => no_read!(), 0x5A => no_read!(), @@ -162,25 +180,23 @@ impl DeviceBus for StandardDevices { 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 - + // 0x80 => todo!(), + // 0x81 => todo!(), + // 0x82 => todo!(), + // 0x83 => todo!(), + // 0x84 => todo!(), + // 0x85 => todo!(), + // 0x86 => todo!(), + // 0x87 => todo!(), + // 0x88 => todo!(), + // 0x89 => todo!(), + // 0x8A => todo!(), + // 0x8B => todo!(), + // 0x8C => todo!(), + // 0x8D => todo!(), + // 0x8E => todo!(), + // 0x8F => todo!(), // File 0xA0 => read_b!(self.file.entry.is_some()), 0xA1 => read_b!(self.file.move_success), @@ -204,22 +220,20 @@ impl DeviceBus for StandardDevices { } 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!(), + 0x00 => self.name.reset_pointer(), + 0x01 => no_write!(), + 0x02 => write_h!(self.wake_mask), + 0x03 => { write_l!(self.wake_mask); return Some(Signal::Sleep) }, 0x04 => no_write!(), 0x05 => no_write!(), 0x06 => no_write!(), @@ -232,6 +246,23 @@ impl DeviceBus for StandardDevices { 0x0D => no_write!(), 0x0E => no_write!(), 0x0F => no_write!(), + // Memory + 0x10 => { write_h!(self.memory.page_1); self.memory.expand_memory() }, + 0x11 => { write_l!(self.memory.page_1); self.memory.expand_memory() }, + 0x12 => write_h!(self.memory.address_1), + 0x13 => write_l!(self.memory.address_1), + 0x14 => self.memory.write_to_head_1(val), + 0x15 => self.memory.write_to_head_1(val), + 0x16 => write_h!(self.memory.page_limit), + 0x17 => write_l!(self.memory.page_limit), + 0x18 => { write_h!(self.memory.page_2); self.memory.expand_memory() }, + 0x19 => { write_l!(self.memory.page_2); self.memory.expand_memory() }, + 0x1A => write_h!(self.memory.address_2), + 0x1B => write_l!(self.memory.address_2), + 0x1C => self.memory.write_to_head_2(val), + 0x1D => self.memory.write_to_head_2(val), + 0x1E => write_h!(self.memory.copy_length), + 0x1F => { write_l!(self.memory.copy_length); self.memory.copy() }, // Math 0x20 => write_h!(self.math.operand_1), 0x21 => write_l!(self.math.operand_1), @@ -250,6 +281,14 @@ impl DeviceBus for StandardDevices { 0x2E => no_write!(), 0x2F => no_write!(), // Clock + 0x30 => no_write!(), + 0x31 => no_write!(), + 0x32 => no_write!(), + 0x33 => no_write!(), + 0x34 => no_write!(), + 0x35 => no_write!(), + 0x36 => no_write!(), + 0x37 => no_write!(), 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), @@ -267,8 +306,8 @@ impl DeviceBus for StandardDevices { 0x45 => no_write!(), 0x46 => no_write!(), 0x47 => no_write!(), - 0x48 => self.input.character_queue.clear(), - 0x49 => no_write!(), + 0x48 => no_write!(), + 0x49 => self.input.text_queue.clear(), 0x4A => no_write!(), 0x4B => no_write!(), 0x4C => no_write!(), @@ -276,39 +315,25 @@ impl DeviceBus for StandardDevices { 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!(), + 0x50 => write_h!(self.screen.cursor.x), + 0x51 => write_l!(self.screen.cursor.x), + 0x52 => write_h!(self.screen.cursor.y), + 0x53 => write_l!(self.screen.cursor.y), + 0x54 => write_h!(self.screen.dimensions.width), + 0x55 => { write_l!(self.screen.dimensions.width); self.screen.set_size() }, + 0x56 => write_h!(self.screen.dimensions.height), + 0x57 => { write_l!(self.screen.dimensions.height); self.screen.set_size() }, + 0x58 => self.screen.set_palette_high(val), + 0x59 => self.screen.set_palette_low(val), + 0x5A => self.screen.sprite_buffer.set_colour_high(val), + 0x5B => self.screen.sprite_buffer.set_colour_low(val), + 0x5C => self.screen.sprite_buffer.push(val), + 0x5D => self.screen.sprite_buffer.push(val), + 0x5E => self.screen.draw(val), + 0x5F => self.screen.shunt(val), + // Stream + 0x86 => self.stream.write_stdout(val), + 0x87 => self.stream.write_stdout(val), // File 0xA0 => self.file.write_to_open_port(val), 0xA1 => self.file.write_to_move_port(val), @@ -327,10 +352,6 @@ impl DeviceBus for StandardDevices { 0xAE => write_lh!(self.file.new_length), 0xAF => { write_ll!(self.file.new_length); self.file.commit_length() }, - // Bytestreams - 0x96 => self.stream.write_stdout(val), - 0x97 => self.stream.write_stdout(val), - _ => unimplemented!("Writing to device port 0x{port:02x}"), }; diff --git a/src/devices/clock.rs b/src/devices/clock.rs index 3faa604..60b1cad 100644 --- a/src/devices/clock.rs +++ b/src/devices/clock.rs @@ -1,22 +1,16 @@ 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()) -};} - -macro_rules! to_ticks { - ($duration:expr) => {($duration.as_millis() / 4) as u16}; } -macro_rules! from_ticks { - ($ticks:expr) => {Duration::from_millis(($ticks * 4).into())}; } - + 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 _instant >]; + let instant = &mut self. [< timer_ $i _end >]; *instant = Instant::now() + from_ticks!(*ticks); } @@ -28,13 +22,13 @@ 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 _instant >]; + 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; } } - *ticks + return *ticks; } }}; } @@ -42,13 +36,13 @@ macro_rules! generate_update_timer_method { pub struct ClockDevice { pub wake_flag: bool, - pub boot_time: Instant, - pub cumulative_timer: u16, + pub program_start: Instant, + pub uptime: u16, - pub timer_1_instant: Instant, - pub timer_2_instant: Instant, - pub timer_3_instant: Instant, - pub timer_4_instant: Instant, + 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, @@ -60,13 +54,13 @@ impl ClockDevice { Self { wake_flag: false, - boot_time: Instant::now(), - cumulative_timer: 0, + program_start: Instant::now(), + uptime: 0, - timer_1_instant: Instant::now(), - timer_2_instant: Instant::now(), - timer_3_instant: Instant::now(), - timer_4_instant: Instant::now(), + 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, @@ -84,9 +78,16 @@ impl ClockDevice { generate_update_timer_method!{3} generate_update_timer_method!{4} - pub fn update_cumulative_timer(&mut self) -> u16 { - self.cumulative_timer = to_ticks!(self.boot_time.elapsed()); - return self.cumulative_timer; + 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 { @@ -113,7 +114,7 @@ impl ClockDevice { now!().second() } - pub fn shortest_active_timer(&self) -> Option<Duration> { + 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) diff --git a/src/devices/file/directory_entry.rs b/src/devices/file/directory_entry.rs index 45de817..c4ce146 100644 --- a/src/devices/file/directory_entry.rs +++ b/src/devices/file/directory_entry.rs @@ -1,4 +1,4 @@ -use crate::*; +use super::*; use std::cmp::Ordering; diff --git a/src/devices/file/directory_listing.rs b/src/devices/file/directory_listing.rs index 1f94a3a..1295f8b 100644 --- a/src/devices/file/directory_listing.rs +++ b/src/devices/file/directory_listing.rs @@ -1,4 +1,4 @@ -use crate::*; +use super::*; use std::ffi::OsString; use std::os::unix::ffi::{OsStrExt, OsStringExt}; @@ -8,7 +8,7 @@ use std::path::{Component, Path, PathBuf}; pub struct DirectoryListing { children: Vec<DirectoryChild>, length: u32, - selected: u32, + selected: Option<u32>, name_buffer: CircularPathBuffer, } @@ -44,7 +44,7 @@ impl DirectoryListing { } children.sort_unstable(); let length = u32::try_from(children.len()).unwrap_or(u32::MAX); - let selected = 0; + let selected = None; let name_buffer = CircularPathBuffer::new(); Ok(Self { children, length, selected, name_buffer } ) } else { @@ -61,16 +61,16 @@ impl DirectoryListing { } pub fn selected(&self) -> u32 { - self.selected + self.selected.unwrap_or(0) } pub fn set_selected(&mut self, index: u32) { if let Some(info) = self.get(index) { self.name_buffer.populate(&info.byte_path.clone()); - self.selected = index; + self.selected = Some(index); } else { self.name_buffer.clear(); - self.selected = 0; + self.selected = None; } } @@ -79,14 +79,14 @@ impl DirectoryListing { } pub fn child_type(&self) -> Option<EntryType> { - self.get(self.selected).and_then(|i| Some(i.entry_type)) + self.selected.and_then(|s| self.get(s).and_then(|i| Some(i.entry_type))) } pub fn child_path(&self) -> Option<PathBuf> { - self.get(self.selected).and_then(|i| { + self.selected.and_then(|s| self.get(s).and_then(|i| { let os_string: OsString = OsStringExt::from_vec(i.byte_path.clone()); Some(os_string.into()) - }) + })) } } diff --git a/src/devices/file/entry.rs b/src/devices/file/entry.rs index a91ae82..d604bb7 100644 --- a/src/devices/file/entry.rs +++ b/src/devices/file/entry.rs @@ -1,4 +1,4 @@ -use crate::*; +use super::*; use std::cmp::Ordering; diff --git a/src/devices/input.rs b/src/devices/input.rs index f3191dd..f23d902 100644 --- a/src/devices/input.rs +++ b/src/devices/input.rs @@ -1,21 +1,45 @@ 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; +const AUX1: u8 = 0x02; +const AUX2: u8 = 0x01; + +macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } + + pub struct InputDevice { pub wake_flag: bool, - pub mouse_position: ScreenPosition, - pub mouse_button_state: u8, + 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 horizontal_scroll_value: u16, - pub vertical_scroll_value: u16, - pub horizontal_scroll_value_delta: f64, - pub vertical_scroll_value_delta: f64, + pub text_queue: VecDeque<u8>, + pub modifiers: u8, + pub navigation: u8, - pub character_queue: VecDeque<u8>, - pub modifier_state: u8, - pub navigation_state: u8, + pub controller_1: u8, + pub controller_2: u8, + pub controller_3: u8, + pub controller_4: u8, } impl InputDevice { @@ -23,104 +47,122 @@ impl InputDevice { Self { wake_flag: false, - mouse_position: ScreenPosition::ZERO, - mouse_button_state: 0x00, + pointer_position: ScreenPosition::ZERO, + pointer_buttons: 0x00, + pointer_active: false, - horizontal_scroll_value: 0x0000, - vertical_scroll_value: 0x0000, - horizontal_scroll_value_delta: 0.0, - vertical_scroll_value_delta: 0.0, + horizontal_scroll: 0x0000, + vertical_scroll: 0x0000, + horizontal_scroll_delta: 0.0, + vertical_scroll_delta: 0.0, - character_queue: VecDeque::new(), - modifier_state: 0x00, - navigation_state: 0x00, + text_queue: VecDeque::new(), + modifiers: 0x00, + navigation: 0x00, + + controller_1: 0x00, + controller_2: 0x00, + controller_3: 0x00, + controller_4: 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, + pub fn read_horizontal_scroll(&mut self) -> u8 { + let output_value = self.horizontal_scroll; + self.horizontal_scroll = 0; + return output_value as u8; + } + + pub fn read_vertical_scroll(&mut self) -> u8 { + let output_value = self.vertical_scroll; + self.vertical_scroll = 0; + return output_value 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_button_state != self.mouse_button_state { - self.mouse_button_state = new_button_state; + if new_buttons != self.pointer_buttons { + self.pointer_buttons = new_buttons; 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; + 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_value_delta += delta; - while self.horizontal_scroll_value_delta > 20.0 { - self.horizontal_scroll_value += 1; - self.horizontal_scroll_value_delta -= 20.0; + 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_value_delta < -20.0 { - self.horizontal_scroll_value -= 1; - self.horizontal_scroll_value_delta += 20.0; + 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_value_delta += delta; - while self.vertical_scroll_value_delta > 20.0 { - self.vertical_scroll_value += 1; - self.vertical_scroll_value_delta -= 20.0; + 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_value_delta < -20.0 { - self.vertical_scroll_value -= 1; - self.vertical_scroll_value_delta += 20.0; + 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(ascii) = u8::try_from(u32::from(input)) { - self.character_queue.push_back(ascii); + 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: phosphor::KeyboardInput) { - let tab = self.modifier_state & 0x40 != 0; + pub fn on_keyboard_input(&mut self, input: KeyboardInput) { 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, + 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 new_navigation_state = match input.action { - phosphor::Action::Pressed => self.navigation_state | mask, - phosphor::Action::Released => self.navigation_state & !mask, + let navigation = match input.action { + Action::Pressed => self.navigation | mask, + Action::Released => self.navigation & !mask, }; - if new_navigation_state != self.navigation_state { - self.navigation_state = new_navigation_state; + if navigation != self.navigation { + self.navigation = navigation; 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; + 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/math.rs b/src/devices/math.rs index c8d2194..cefb572 100644 --- a/src/devices/math.rs +++ b/src/devices/math.rs @@ -1,10 +1,6 @@ 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 { @@ -12,24 +8,24 @@ impl MathDevice { Self { operand_1: 0x0000, operand_2: 0x0000, - product_high: 0x0000, - product_low: 0x0000, - quotient: 0x0000, - remainder: 0x0000, } } - pub fn multiply(&mut self) { - let (low, high) = self.operand_1.widening_mul(self.operand_2); - self.product_high = high; - self.product_low = low; + pub fn multiply_high(&mut self) -> u16 { + let (_, high) = self.operand_1.widening_mul(self.operand_2); + return high; } - pub fn divide(&mut self) { - self.quotient = self.operand_1.checked_div(self.operand_2).unwrap_or(0); + pub fn multiply_low(&mut self) -> u16 { + let (low, _) = self.operand_1.widening_mul(self.operand_2); + return low; } - pub fn modulo(&mut self) { - self.remainder = self.operand_1.checked_rem(self.operand_2).unwrap_or(0); + 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/memory.rs b/src/devices/memory.rs new file mode 100644 index 0000000..1d33f64 --- /dev/null +++ b/src/devices/memory.rs @@ -0,0 +1,89 @@ +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 { + pages: Vec<Page>, + pub page_limit: u16, + 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 { + pages: Vec::new(), + page_limit: 0, + page_1: 0, + address_1: 0, + page_2: 0, + address_2: 0, + copy_length: 0, + } + } + + pub fn page_count(&self) -> u16 { + self.pages.len() as u16 + } + + pub fn copy(&mut self) { + // Return if at least one page is out-of-bounds + if self.page_1 as usize >= self.pages.len() + || self.page_2 as usize >= self.pages.len() { + return + } + 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 expand_memory(&mut self) { + let size = std::cmp::max(self.page_1, self.page_2) as usize + 1; + if size < self.page_limit as usize { + self.pages.resize_with(size, new_blank_page); + } + } + + 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); + if let Some(page) = self.pages.get_mut(self.page_1 as usize) { + page[address] = byte; + } + } + + pub fn write_to_head_2(&mut self, byte: u8) { + let address = then_inc!(self.address_2); + if let Some(page) = self.pages.get_mut(self.page_2 as usize) { + page[address] = byte; + } + } +} diff --git a/src/devices/scratch.rs b/src/devices/scratch.rs deleted file mode 100644 index 950f87a..0000000 --- a/src/devices/scratch.rs +++ /dev/null @@ -1,61 +0,0 @@ -macro_rules! then_inc { - ($v:expr) => {{ - $v = $v.wrapping_add(1); - $v as usize - }}; -} - -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 i = then_inc!(self.pointer_1); - self.read_byte(i) - } - - pub fn read_head_2(&mut self) -> u8 { - let i = then_inc!(self.pointer_2); - self.read_byte(i) - } - - pub fn write_head_1(&mut self, value: u8) { - let i = then_inc!(self.pointer_1); - self.write_byte(i, value); - } - - pub fn write_head_2(&mut self, value: u8) { - let i = then_inc!(self.pointer_2); - self.write_byte(i, value); - } - - fn read_byte(&self, pointer: usize) -> u8 { - match self.memory.get(pointer as usize) { - Some(value) => *value, - None => 0, - } - } - - fn write_byte(&mut self, pointer: usize, value: u8) { - if pointer < self.max_capacity 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 index c96d1c4..4394b6c 100644 --- a/src/devices/screen.rs +++ b/src/devices/screen.rs @@ -1,37 +1,50 @@ mod sprite_data; -mod vector_points; +mod draw_line; +mod draw_rect; +mod draw_sprite; 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>; +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-right-top-bottom. + + /// 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 is_dirty: bool, - pub is_resizable: bool, + pub dirty: bool, + pub resizable: bool, pub cursor: ScreenPosition, + pub vector: ScreenPosition, pub dimensions: ScreenDimensions, - pub sprite_data: SpriteData, - pub palette: [Colour; 16], + pub palette_high: u8, - pub sprite_colours: [u8; 4], - pub vector: VectorPoints, + pub palette: [Colour; 16], + pub sprite_buffer: SpriteBuffer, } impl ScreenDevice { @@ -41,25 +54,25 @@ impl ScreenDevice { foreground: Vec::new(), background: Vec::new(), - is_dirty: false, - is_resizable: true, + dirty: false, + resizable: true, cursor: ScreenPosition::ZERO, + vector: ScreenPosition::ZERO, dimensions: ScreenDimensions::ZERO, - sprite_data: SpriteData::new(), - palette: [Colour::BLACK; 16], - palette_high: 0, - sprite_colours: [0; 4], - vector: VectorPoints::new(), + palette_high: 0, + palette: [Colour::BLACK; 16], + sprite_buffer: SpriteBuffer::new(), } } - pub fn set_size(&mut self, dimensions: ScreenDimensions) { - self.is_resizable = false; - self.resize(dimensions); + 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; @@ -100,19 +113,20 @@ impl ScreenDevice { }; self.dimensions = dimensions; - self.is_dirty = true; + 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 palette = [Colour::BLACK; 256]; - for (i, c) in palette.iter_mut().enumerate() { + 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; @@ -123,8 +137,9 @@ impl ScreenDevice { 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]; + *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); @@ -139,17 +154,15 @@ impl ScreenDevice { &self.foreground[si..si+width], ); for (b, (bg, fg)) in zip(b_iter, s_iter) { - *b = palette[(fg << 4 | bg) as usize]; + *b = lookup[(fg << 4 | bg) as usize]; } - b_slice[bi+width..bi+width+width_excess].fill(palette[0]); + b_slice[bi+width..bi+width+width_excess].fill(lookup[0]); bi += b_width; si += s_width; } - b_slice[bi..].fill(palette[0]); + b_slice[bi..].fill(lookup[0]); } - - // Set flags - self.is_dirty = false; + self.dirty = false; } pub fn set_palette_high(&mut self, val: u8) { @@ -162,24 +175,14 @@ impl ScreenDevice { 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; + self.dirty = true; } pub fn shunt(&mut self, val: u8) { - let is_negative = val & 0x80 != 0; - let is_vertical = val & 0x40 != 0; + let negative = test!(val, NEGATIVE); + let vertical = test!(val, VERTICAL); let dist = (val & 0x3f) as u16; - match (is_negative, is_vertical) { + 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), @@ -188,31 +191,32 @@ impl ScreenDevice { } 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 operation = val & 0x70; + let parameters = 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 textured line"), - 0x70 => self.draw_rect_1bit(params, layer), + 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_rect(parameters, layer), + 0x60 => self.draw_line_1bit(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) { return } + 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, @@ -220,6 +224,7 @@ impl ScreenDevice { }; } + // 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), @@ -227,227 +232,12 @@ impl ScreenDevice { } } - 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 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 [p0, p1] = self.vector.get_pair(); - 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), - }; - } - - 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.vector.get_pair(); - - // 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 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_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 layer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - layer[i+x0..=i+x1].fill(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 layer = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - layer[i] = colour; - i += screen_width; - } - - } - } - - 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 pixels = match layer { - ScreenLayer::Background => &mut self.background, - ScreenLayer::Foreground => &mut self.foreground, - }; - for _ in y0..=y1 { - pixels[i..i+rect_width].fill(colour); - i += screen_width; - } - } - } - - 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 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 += screen_width - rect_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! for8 { ($block:block) => { for _ in 0..8 { $block } }; } - macro_rules! r { ($v:expr) => { position.x = position.x.wrapping_add($v) }; } - macro_rules! l { ($v:expr) => { position.x = position.x.wrapping_sub($v) }; } - macro_rules! d { ($v:expr) => { position.y = position.y.wrapping_add($v) }; } - macro_rules! u { ($v:expr) => { position.y = position.y.wrapping_sub($v) }; } - macro_rules! px { () => { - let colour = sprite[pointer]; - if !(transparent && colour == 0) { - self.draw_pixel(self.sprite_colours[colour as usize], layer, position); - } - pointer += 1; - }; } - - - match params & 0x07 { - 0 => { for8!{{ for8!{{ px!(); r!(1); }} l!(8); d!(1); }} } - 1 => { r!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); d!(1); }} } - 2 => { d!(7); for8!{{ for8!{{ px!(); r!(1); }} l!(8); u!(1); }} } - 3 => { r!(7); d!(7); for8!{{ for8!{{ px!(); l!(1); }} r!(8); u!(1); }} } - - 4 => { for8!{{ for8!{{ px!(); d!(1); }} u!(8); r!(1); }} } - 5 => { r!(7); for8!{{ for8!{{ px!(); d!(1); }} u!(8); l!(1); }} } - 6 => { d!(7);for8!{{ for8!{{ px!(); u!(1); }} d!(8); r!(1); }} } - 7 => { r!(7); d!(7); for8!{{ for8!{{ px!(); u!(1); }} d!(8); l!(1); }} } - - _ => unreachable!(), - } - } - - /// Returns [x0, y0, x1, y1] + /// 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.vector.get_pair(); + 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) ]; diff --git a/src/devices/screen/draw_line.rs b/src/devices/screen/draw_line.rs new file mode 100644 index 0000000..94066f4 --- /dev/null +++ b/src/devices/screen/draw_line.rs @@ -0,0 +1,223 @@ +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 new file mode 100644 index 0000000..265a87f --- /dev/null +++ b/src/devices/screen/draw_rect.rs @@ -0,0 +1,42 @@ +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 new file mode 100644 index 0000000..9b0658c --- /dev/null +++ b/src/devices/screen/draw_sprite.rs @@ -0,0 +1,25 @@ +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.x.wrapping_add(1); + } + } +} diff --git a/src/devices/screen/sprite_data.rs b/src/devices/screen/sprite_data.rs index aade2c6..0a1d3c2 100644 --- a/src/devices/screen/sprite_data.rs +++ b/src/devices/screen/sprite_data.rs @@ -1,11 +1,17 @@ -pub struct SpriteData { +use super::*; + +macro_rules! test { ($value:expr, $mask:expr) => { $value & $mask != 0 }; } + + +pub struct SpriteBuffer { data: [u8; 16], pointer: usize, + colours: [u8; 4], } -impl SpriteData { +impl SpriteBuffer { pub fn new() -> Self { - Self { data: [0; 16], pointer: 0 } + Self { data: [0; 16], pointer: 0, colours: [0; 4] } } pub fn push(&mut self, val: u8) { @@ -13,21 +19,91 @@ impl SpriteData { 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] + 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; } - 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 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/vector_points.rs b/src/devices/screen/vector_points.rs deleted file mode 100644 index e6c28ef..0000000 --- a/src/devices/screen/vector_points.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::*; - -pub struct VectorPoints { - points: [ScreenPosition; 2], - pointer: usize, -} - -impl VectorPoints { - pub fn new() -> Self { - Self { points: [ScreenPosition::ZERO; 2], pointer: 0 } - } - - pub fn push(&mut self, point: ScreenPosition) { - self.points[self.pointer] = point; - self.pointer = (self.pointer + 1) % 2; - } - - pub fn get_pair(&self) -> [ScreenPosition; 2] { - self.points - } -} diff --git a/src/devices/system.rs b/src/devices/system.rs new file mode 100644 index 0000000..6eb9510 --- /dev/null +++ b/src/devices/system.rs @@ -0,0 +1,4 @@ +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 new file mode 100644 index 0000000..7c59025 --- /dev/null +++ b/src/devices/system/read_only_text_buffer.rs @@ -0,0 +1,23 @@ +pub struct ReadOnlyTextBuffer { + chars: Vec<u8>, + pointer: usize, +} + +impl ReadOnlyTextBuffer { + pub fn from_text(text: &str) -> Self { + Self { + chars: text.chars().map(|c| c as u32 as u8).collect(), + pointer: 0, + } + } + + pub fn read_byte(&mut self) -> u8 { + let byte = self.chars[self.pointer]; + self.pointer += 1; + return byte; + } + + pub fn reset_pointer(&mut self) { + self.pointer = 0; + } +} diff --git a/src/emulator.rs b/src/emulator.rs index 91d33e9..4fc1bc4 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -3,38 +3,52 @@ use crate::*; use bedrock_core::*; use phosphor::*; +use std::cmp::{min, max}; +use std::io::Write; use std::time::*; +use std::thread::sleep; const FRAME: Duration = Duration::from_micros(16666); +const LINE_HEIGHT: f64 = 20.0; + +macro_rules! u16 { ($u32:expr) => { $u32.try_into().unwrap_or(u16::MAX) }; } + pub struct BedrockEmulator { vm: Processor<StandardDevices>, + initialising: bool, + sleeping: bool, + pixel_scale: u32, process_mark: Instant, frame_mark: Instant, debug_mark: Instant, - waiting_to_start: bool, - is_paused: bool, - scale: u32, - cycles_since_debug: usize, + cycles_elapsed: usize, } impl BedrockEmulator { pub fn new(bytecode: &[u8]) -> Self { let mut vm = Processor::new(StandardDevices::new()); + vm.dev.screen.resize(ScreenDimensions::new(256, 192)); vm.load_program(bytecode); Self { vm, + initialising: true, + sleeping: false, + pixel_scale: 3, 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, + cycles_elapsed: 0, } } - pub fn debug(&mut self) { + pub fn run(self) -> ! { + let mut wm = WindowManager::new(); + wm.add_window(Box::new(self)); + wm.run(); + } + + pub fn debug(&mut self, variant: DebugVariant) { macro_rules! yellow {()=>{eprint!("\x1b[33m")};} macro_rules! normal {()=>{eprint!("\x1b[0m")};} macro_rules! print_stack { @@ -46,24 +60,31 @@ impl BedrockEmulator { 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; + match variant { + DebugVariant::DB1 => { + eprintln!("\n PC: 0x{:04x} Cycles: {} (+{} in {:.2?})", + self.vm.mem.pc, self.vm.cycles, + self.vm.cycles - self.cycles_elapsed, + self.debug_mark.elapsed()); + eprint!("WST: "); + print_stack!(self.vm.wst, 0x10); + eprint!("\nRST: "); + print_stack!(self.vm.rst, 0x10); + eprintln!(); + } + DebugVariant::DB2 => { + eprintln!("{:>8.2?}ms ({:>8} cycles)", + self.debug_mark.elapsed().as_micros() as f64 / 1000.0, + self.vm.cycles - self.cycles_elapsed) + } + DebugVariant::DB3 => { + // Only resets the debug timer + } + _ => (), + } + self.cycles_elapsed = 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 { @@ -72,7 +93,7 @@ impl WindowController for BedrockEmulator { } fn exact_size(&self) -> Option<Dimensions> { - match self.vm.dev.screen.is_resizable { + match self.vm.dev.screen.resizable { true => None, false => Some(Dimensions::new( self.vm.dev.screen.dimensions.width as u32, @@ -82,99 +103,133 @@ impl WindowController for BedrockEmulator { } fn is_cursor_visible(&self) -> bool { - let pos = self.vm.dev.input.mouse_position; + let pos = self.vm.dev.input.pointer_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() + NonZeroU32::new(self.pixel_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; + let width = u16!(dimensions.width); + let height = u16!(dimensions.height); + self.vm.dev.screen.resize(ScreenDimensions { width, height }); + self.initialising = 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)); + let x = position.x as u16; + let y = position.y as u16; + self.vm.dev.input.on_pointer_move(ScreenPosition::new(x, y)); + self.vm.dev.input.pointer_active = true; + } + + fn on_cursor_enter(&mut self) { + self.vm.dev.input.pointer_active = true; + self.vm.dev.input.wake_flag = true; } + fn on_cursor_exit(&mut self) { - self.vm.dev.input.move_mouse(ScreenPosition::new(0x7FFF, 0x7FFF)); + self.vm.dev.input.pointer_active = false; + self.vm.dev.input.wake_flag = true; } + fn on_left_mouse_button(&mut self, action: Action) { - self.vm.dev.input.mouse_button_action(0x80, action); + self.vm.dev.input.on_pointer_button(0x80, action); } + fn on_middle_mouse_button(&mut self, action: Action) { - self.vm.dev.input.mouse_button_action(0x40, action); + self.vm.dev.input.on_pointer_button(0x20, action); } + fn on_right_mouse_button(&mut self, action: Action) { - self.vm.dev.input.mouse_button_action(0x20, action); + self.vm.dev.input.on_pointer_button(0x40, action); } + fn on_line_scroll_horizontal(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_horizontal(delta * 20.0); + self.vm.dev.input.on_scroll_horizontal(delta); } + fn on_line_scroll_vertical(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_vertical(delta * 20.0); + self.vm.dev.input.on_scroll_vertical(delta); } + fn on_pixel_scroll_horizontal(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_horizontal(delta); + self.vm.dev.input.on_scroll_horizontal(delta / LINE_HEIGHT); } + fn on_pixel_scroll_vertical(&mut self, delta: f64) { - self.vm.dev.input.on_scroll_vertical(delta); + self.vm.dev.input.on_scroll_vertical(delta / LINE_HEIGHT); } + 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), + KeyCode::F5 => self.pixel_scale = max(1, self.pixel_scale - 1), + KeyCode::F6 => self.pixel_scale = min(8, self.pixel_scale + 1), _ => (), } } } + fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) { self.vm.dev.input.on_modifier_change(modifiers); } fn on_process(&mut self) { + // The duration remaining until the next frame is due to be rendered 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), + // Wait for the initial resize event to come through + if self.initialising { + sleep(FRAME / 10); + self.process_mark = Instant::now(); + return; + } + // Sleep for the remainder of the frame, or until a timer expires + if self.sleeping && !self.vm.dev.can_wake() { + let sleep_duration = match self.vm.dev.clock.time_to_next_wake() { + Some(ms) => min(ms, frame_remaining), None => frame_remaining, }; - std::thread::sleep(sleep_duration); + sleep(sleep_duration); self.process_mark = Instant::now(); return; } - self.is_paused = false; + // Wake from sleep and evaluate the program for the remainder of the frame + self.sleeping = 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), + Signal::Debug(var) => self.debug(var), + Signal::Sleep => { + self.sleeping = true; + let frame_elapsed = self.process_mark.elapsed(); + sleep(frame_remaining.saturating_sub(frame_elapsed)); + break; + }, + Signal::Halt => { + self.vm.dev.stream.stdout.flush().unwrap(); + 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 { + if self.vm.dev.screen.dirty { + match self.sleeping { true => RenderRequest::UPDATE, false => match self.frame_mark.elapsed() >= 6 * FRAME { true => RenderRequest::UPDATE, diff --git a/src/main.rs b/src/main.rs index 80dcb54..4416f7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,11 @@ fn main() { 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); } + Err(err) => { + eprintln!("Could not read from standard input, quitting."); + eprintln!("({err:?})"); + exit(1); + } }; BedrockEmulator::new(&bytecode).run(); } |