From 77067f6e244a9cf4a6ef59df2e3d735b4f172c35 Mon Sep 17 00:00:00 2001
From: Ben Bridle <bridle.benjamin@gmail.com>
Date: Sun, 24 Dec 2023 22:19:11 +1300
Subject: First commit

---
 src/emulator.rs | 194 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 194 insertions(+)
 create mode 100644 src/emulator.rs

(limited to 'src/emulator.rs')

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();
+    }
+}
-- 
cgit v1.2.3-70-g09d2