summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Bridle <bridle.benjamin@gmail.com>2024-04-16 10:51:13 +1200
committerBen Bridle <bridle.benjamin@gmail.com>2024-04-16 10:51:26 +1200
commit6b3796c9a0d3a2f1422bcbde4790c43417659722 (patch)
tree6429a5fa2f8c4d3b26790775e07e46e6338b61d3
parent28101de56231252ca0cfa6a9f107b75112c9acad (diff)
downloadbedrock-pc-6b3796c9a0d3a2f1422bcbde4790c43417659722.zip
Update devices to match new specifications
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml2
-rw-r--r--src/devices.rs319
-rw-r--r--src/devices/clock.rs57
-rw-r--r--src/devices/file/directory_entry.rs2
-rw-r--r--src/devices/file/directory_listing.rs18
-rw-r--r--src/devices/file/entry.rs2
-rw-r--r--src/devices/input.rs176
-rw-r--r--src/devices/math.rs28
-rw-r--r--src/devices/memory.rs89
-rw-r--r--src/devices/scratch.rs61
-rw-r--r--src/devices/screen.rs344
-rw-r--r--src/devices/screen/draw_line.rs223
-rw-r--r--src/devices/screen/draw_rect.rs42
-rw-r--r--src/devices/screen/draw_sprite.rs25
-rw-r--r--src/devices/screen/sprite_data.rs98
-rw-r--r--src/devices/screen/vector_points.rs21
-rw-r--r--src/devices/system.rs4
-rw-r--r--src/devices/system/read_only_text_buffer.rs23
-rw-r--r--src/emulator.rs167
-rw-r--r--src/main.rs6
21 files changed, 1010 insertions, 701 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 0917f4a..8928557 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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"
diff --git a/Cargo.toml b/Cargo.toml
index 5d50c51..b5a6dba 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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();
}