summaryrefslogtreecommitdiff
path: root/src/emulator.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/emulator.rs')
-rw-r--r--src/emulator.rs167
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,