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