use crate::*; use bedrock_core::*; use phosphor::*; use std::cmp::{min, max}; use std::time::*; use std::thread::sleep; const FRAME: Duration = Duration::from_micros(8_000); const RENDER_WAIT: Duration = Duration::from_micros(500_000); const LINE_HEIGHT: f64 = 20.0; macro_rules! u16 { ($u32:expr) => { $u32.try_into().unwrap_or(u16::MAX) }; } pub struct BedrockEmulator { vm: Processor, title: String, initialising: bool, sleeping: bool, pixel_scale: u32, fullscreen: bool, start_of_process: Instant, end_of_render: Instant, debug_mark: Instant, 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.dev.memory.page_limit = 256; vm.load_program(bytecode); Self { vm, title: String::from("Bedrock emulator"), initialising: true, sleeping: false, pixel_scale: 3, fullscreen: false, start_of_process: Instant::now(), end_of_render: Instant::now(), debug_mark: Instant::now(), cycles_elapsed: 0, } } pub fn with_title(mut self, title: &str) -> Self { self.title = title.to_string(); 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) { self.vm.dev.stream.flush_local(); 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!(); }; } 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(); } } impl WindowController for BedrockEmulator { fn title(&self) -> String { self.title.clone() } fn exact_size(&self) -> Option { match self.vm.dev.screen.resizable { true => None, false => Some(Dimensions::new( self.vm.dev.screen.dimensions.width as u32, self.vm.dev.screen.dimensions.height as u32, )), } } fn fullscreen(&self) -> bool { self.fullscreen } fn cursor(&mut self) -> Option { let pos = self.vm.dev.input.pointer_position; let dim = self.vm.dev.screen.dimensions; match pos.x >= dim.width || pos.y >= dim.height { true => Some(CursorIcon::Default), false => None, } } fn pixel_scale(&self) -> NonZeroU32 { NonZeroU32::new(self.pixel_scale).unwrap() } fn on_resize(&mut self, dimensions: Dimensions) { 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) { 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.pointer_active = false; self.vm.dev.input.wake_flag = true; } fn on_left_mouse_button(&mut self, action: Action) { self.vm.dev.input.on_pointer_button(0x80, action); } fn on_middle_mouse_button(&mut self, action: Action) { self.vm.dev.input.on_pointer_button(0x20, action); } fn on_right_mouse_button(&mut self, action: 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); } fn on_line_scroll_vertical(&mut self, delta: f64) { 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 / LINE_HEIGHT); } fn on_pixel_scroll_vertical(&mut self, delta: f64) { 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::F5 => self.pixel_scale = max(1, self.pixel_scale - 1), KeyCode::F6 => self.pixel_scale = min(8, self.pixel_scale + 1), KeyCode::F11 => self.fullscreen = !self.fullscreen, _ => (), } } } fn on_keyboard_modifier_change(&mut self, modifiers: ModifiersState) { self.vm.dev.input.on_modifier_change(modifiers); } fn on_process(&mut self) { // Prevent program starting before the window receives an initial size. if self.initialising { sleep(FRAME); return; } if self.sleeping { // Pause the processor until the current frame is rendered. This // prevents the current frame from being overdrawn before rendering. if self.vm.dev.screen.dirty { sleep(FRAME); return; } // Ensure a minimum delay of FRAME between the start of consecutive // process frames when asleep, unless a timer has expired. let frame_start = self.start_of_process + FRAME; let time_to_frame_start = frame_start.duration_since(Instant::now()); if !time_to_frame_start.is_zero() { let time_to_sleep = match self.vm.dev.clock.time_to_next_wake() { Some(ms) => min(ms, time_to_frame_start), None => time_to_frame_start, }; sleep(time_to_sleep); return; } // Stay asleep if there are no pending wake events. if !self.vm.dev.can_wake() { sleep(FRAME); return; } } // Run the processor for the remainder of the frame. self.start_of_process = Instant::now(); self.sleeping = false; let frame_end = Instant::now() + FRAME; while Instant::now() < frame_end { if let Some(signal) = self.vm.evaluate(1000) { match signal { Signal::Debug(var) => self.debug(var), Signal::Sleep => { self.sleeping = true; break; }, Signal::Halt => { self.vm.dev.stream.flush_local(); self.vm.dev.file.flush_entry(); exit(0); }, } } } self.vm.dev.stream.flush_local(); self.vm.dev.file.flush_entry(); } fn render_request(&mut self) -> RenderRequest { if self.vm.dev.screen.dirty { match self.sleeping { true => RenderRequest::UPDATE, false => match self.end_of_render.elapsed() >= RENDER_WAIT { true => RenderRequest::UPDATE, false => RenderRequest::NONE, } } } else { self.end_of_render = Instant::now(); RenderRequest::NONE } } fn on_render(&mut self, buffer: &mut Buffer, _hint: RenderHint) { self.vm.dev.screen.render(buffer); self.end_of_render = Instant::now(); } }