diff options
author | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-28 20:25:01 +1300 |
---|---|---|
committer | Ben Bridle <bridle.benjamin@gmail.com> | 2024-10-28 20:29:12 +1300 |
commit | 1a830a3d1b9d99653322d5ae49ea8165de7ed9d0 (patch) | |
tree | 798e77b6fcf2438b1c2538a67efe856a2f7cb979 /src/emulator.rs | |
parent | 03c4b069e1806af256730639cefdae115b24401a (diff) | |
download | bedrock-pc-1a830a3d1b9d99653322d5ae49ea8165de7ed9d0.zip |
Rewrite emulatorv1.0.0-alpha1
This is a complete rewrite and restructure of the entire emulator
project, as part of the effort in locking down the Bedrock specification
and in creating much better tooling for creating and using Bedrock
programs.
This commit adds a command-line argument scheme, an embedded assembler,
a headless emulator for use in non-graphical environments, deferred
window creation for programs that do not access the screen device,
and new versions of phosphor and bedrock-core. The new version of
phosphor supports multi-window programs, which will make it possible to
implement program forking in the system device later on, and the new
version of bedrock-core implements the final core specification.
Diffstat (limited to 'src/emulator.rs')
-rw-r--r-- | src/emulator.rs | 293 |
1 files changed, 0 insertions, 293 deletions
diff --git a/src/emulator.rs b/src/emulator.rs deleted file mode 100644 index d983378..0000000 --- a/src/emulator.rs +++ /dev/null @@ -1,293 +0,0 @@ -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<StandardDevices>, - 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 - } - DebugVariant::DB4 => { - if self.vm.wst.sp == 1 - && self.vm.rst.sp == 0 - && self.vm.wst.mem[0] == 0xff { - print!(".") - } else { - print!("X") - } - } - _ => (), - } - 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<Dimensions> { - 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<CursorIcon> { - 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(); - } -} |