diff options
Diffstat (limited to 'src/emulator.rs')
-rw-r--r-- | src/emulator.rs | 167 |
1 files changed, 111 insertions, 56 deletions
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, |