use crate::*;
use bedrock_core::*;
use phosphor::*;
use std::time::Instant;
pub struct GraphicalDeviceBus {
pub sys: SystemDevice,
pub mem: MemoryDevice,
pub mat: MathDevice,
pub clk: ClockDevice,
pub inp: InputDevice,
pub scr: ScreenDevice,
pub loc: LocalDevice,
pub rem: RemoteDevice,
pub fs1: FileDevice,
pub fs2: FileDevice,
}
impl GraphicalDeviceBus {
pub fn new(config: &EmulatorConfig) -> Self {
Self {
sys: SystemDevice::new(),
mem: MemoryDevice::new(),
mat: MathDevice::new(),
clk: ClockDevice::new(),
inp: InputDevice::new(),
scr: ScreenDevice::new(config),
loc: LocalDevice::new(config),
rem: RemoteDevice::new(),
fs1: FileDevice::new(),
fs2: FileDevice::new(),
}
}
pub fn graphical(&self) -> bool {
self.inp.accessed || self.scr.accessed
}
}
impl DeviceBus for GraphicalDeviceBus {
fn read(&mut self, port: u8) -> u8 {
match port & 0xf0 {
0x00 => self.sys.read(port & 0x0f),
0x10 => self.mem.read(port & 0x0f),
0x20 => self.mat.read(port & 0x0f),
0x30 => self.clk.read(port & 0x0f),
0x40 => self.inp.read(port & 0x0f),
0x50 => self.scr.read(port & 0x0f),
0x80 => self.loc.read(port & 0x0f),
0x90 => self.rem.read(port & 0x0f),
0xa0 => self.fs1.read(port & 0x0f),
0xb0 => self.fs2.read(port & 0x0f),
_ => 0
}
}
fn write(&mut self, port: u8, value: u8) -> Option<Signal> {
match port & 0xf0 {
0x00 => self.sys.write(port & 0x0f, value),
0x10 => self.mem.write(port & 0x0f, value),
0x20 => self.mat.write(port & 0x0f, value),
0x30 => self.clk.write(port & 0x0f, value),
0x40 => self.inp.write(port & 0x0f, value),
0x50 => self.scr.write(port & 0x0f, value),
0x80 => self.loc.write(port & 0x0f, value),
0x90 => self.rem.write(port & 0x0f, value),
0xa0 => self.fs1.write(port & 0x0f, value),
0xb0 => self.fs2.write(port & 0x0f, value),
_ => None
}
}
fn wake(&mut self) -> bool {
macro_rules! rouse {
($id:expr, $dev:ident) => {
if self.sys.can_wake($id) && self.$dev.wake() {
self.sys.wake_id = $id;
self.sys.asleep = false;
return true;
}
};
}
rouse!(0xb, fs2);
rouse!(0xa, fs1);
rouse!(0x9, rem);
rouse!(0x8, loc);
rouse!(0x5, scr);
rouse!(0x4, inp);
rouse!(0x3, clk);
rouse!(0x2, mat);
rouse!(0x1, mem);
rouse!(0x0, sys);
return false;
}
}
pub struct GraphicalEmulator {
pub br: BedrockEmulator<GraphicalDeviceBus>,
pub debug: DebugState,
pub dimensions: ScreenDimensions,
pub fullscreen: bool,
pub scale: u32,
pub render_mark: Instant,
pub debug_palette: Option<[Colour; 16]>,
pub show_debug_palette: bool,
pub show_cursor: bool,
}
impl GraphicalEmulator {
pub fn new(config: &EmulatorConfig, debug: bool) -> Self {
let devices = GraphicalDeviceBus::new(config);
Self {
br: BedrockEmulator::new(devices),
debug: DebugState::new(debug, config.symbols_path.as_ref()),
dimensions: config.dimensions,
fullscreen: config.fullscreen,
scale: config.scale,
render_mark: Instant::now(),
debug_palette: config.debug_palette,
show_debug_palette: config.debug_palette.is_some(),
show_cursor: config.show_cursor,
}
}
pub fn load_program(&mut self, bytecode: &[u8]) {
self.br.core.mem.load_program(bytecode);
}
pub fn run(&mut self) -> EmulatorSignal {
loop {
match self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
Some(Signal::Fork) => {
self.br.core.mem.pc = 0;
self.br.core.wst.sp = 0;
self.br.core.rst.sp = 0;
}
Some(Signal::Sleep) => loop {
if self.br.dev.graphical() {
return EmulatorSignal::Promote;
}
if self.br.dev.wake() { break; }
std::thread::sleep(MIN_TICK_DURATION);
}
Some(Signal::Halt) => {
self.br.dev.loc.flush();
self.debug.info("Program halted, exiting.");
self.debug.debug_summary(&self.br.core);
return EmulatorSignal::Halt;
}
Some(Signal::Debug1) => {
self.debug.debug_summary(&self.br.core);
}
_ => (),
}
if self.br.dev.graphical() {
return EmulatorSignal::Promote;
}
}
}
pub fn size_bounds(&self) -> SizeBounds {
macro_rules! to_u32 {
($opt:expr) => {
match $opt {
Some(a) => Some(u32::from(a)),
None => None,
}
};
}
match self.fullscreen {
true => SizeBounds {
min_width: None,
max_width: None,
min_height: None,
max_height: None,
},
false => SizeBounds {
min_width: to_u32!(self.br.dev.scr.fixed_width),
max_width: to_u32!(self.br.dev.scr.fixed_width),
min_height: to_u32!(self.br.dev.scr.fixed_height),
max_height: to_u32!(self.br.dev.scr.fixed_height),
},
}
}
pub fn dimensions(&self) -> Dimensions {
Dimensions {
width: u32::from(self.br.dev.scr.dimensions.width),
height: u32::from(self.br.dev.scr.dimensions.height),
}
}
}
impl WindowProgram for GraphicalEmulator {
fn handle_event(&mut self, event: Event, r: &mut EventWriter<Request>) {
match event {
Event::CloseRequest => r.write(Request::CloseWindow),
Event::CursorEnter => self.br.dev.inp.on_cursor_enter(),
Event::CursorExit => self.br.dev.inp.on_cursor_exit(),
Event::CursorMove(p) => self.br.dev.inp.on_cursor_move(p),
Event::Resize(d) => self.br.dev.scr.resize(d),
Event::CharacterInput(c) => self.br.dev.inp.on_character(c),
Event::ModifierChange(m) => self.br.dev.inp.on_modifier(m),
Event::MouseButton { button, action } =>
self.br.dev.inp.on_mouse_button(button, action),
Event::FocusChange(_) => (),
Event::Initialise => (),
Event::ScrollLines { axis, distance } => match axis {
Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance),
Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance),
}
Event::ScrollPixels { axis, distance } => match axis {
Axis::Horizontal => self.br.dev.inp.on_horizontal_scroll(distance / 20.0),
Axis::Vertical => self.br.dev.inp.on_vertical_scroll(distance / 20.0),
}
Event::FileDrop(_path) => todo!("FileDrop"),
Event::Close => (),
Event::KeyboardInput { key, action } => {
self.br.dev.inp.on_keypress(key, action);
if action == Action::Pressed {
match key {
KeyCode::F2 => {
self.show_debug_palette = !self.show_debug_palette;
r.write(Request::Redraw);
},
KeyCode::F5 => {
self.scale = std::cmp::max(1, self.scale.saturating_sub(1));
r.write(Request::SetPixelScale(self.scale));
},
KeyCode::F6 => {
self.scale = self.scale.saturating_add(1);
r.write(Request::SetPixelScale(self.scale));
},
KeyCode::F11 => {
self.fullscreen = !self.fullscreen;
r.write(Request::SetFullscreen(self.fullscreen));
},
_ => (),
}
}
}
}
}
fn process(&mut self, requests: &mut EventWriter<Request>) {
self.br.dev.loc.flush();
if self.br.dev.sys.asleep {
// Stay asleep if there are no pending wake events.
if !self.br.dev.wake() {
if self.br.dev.scr.dirty {
requests.write(Request::Redraw);
}
std::thread::sleep(MIN_TICK_DURATION);
return;
}
// Wait for the current frame to be rendered.
if self.br.dev.scr.dirty {
if self.render_mark.elapsed() > MIN_FRAME_DURATION {
requests.write(Request::Redraw);
}
std::thread::sleep(MIN_TICK_DURATION);
return;
}
}
// Run the processor for the remainder of the frame.
let frame_end = Instant::now() + MIN_TICK_DURATION;
while Instant::now() < frame_end {
match self.br.evaluate(BATCH_SIZE, self.debug.enabled) {
Some(Signal::Fork) => {
todo!("Fork")
}
Some(Signal::Sleep) => {
self.br.dev.sys.asleep = true;
break;
}
Some(Signal::Halt) => {
self.br.dev.loc.flush();
self.debug.info("Program halted, exiting.");
self.debug.debug_summary(&self.br.core);
requests.write(Request::CloseWindow);
break;
}
Some(Signal::Debug1) => {
self.debug.debug_summary(&self.br.core);
}
_ => (),
}
}
if std::mem::take(&mut self.br.dev.scr.dirty_dimensions) {
requests.write(Request::SetSizeBounds(self.size_bounds()));
}
if self.br.dev.scr.dirty {
let elapsed = self.render_mark.elapsed();
if self.br.dev.sys.asleep && elapsed > MIN_FRAME_DURATION {
requests.write(Request::Redraw);
} else if elapsed > MAX_FRAME_DURATION {
requests.write(Request::Redraw);
}
} else {
self.render_mark = Instant::now();
}
}
fn render(&mut self, buffer: &mut Buffer, _full: bool) {
let screen = &mut self.br.dev.scr;
// Generate table for calculating pixel colours from layer values.
// A given screen pixel will be rendered as the colour given by
// table[fg][bg], where fg and bg are the corresponding layer values.
let mut table = [Colour::BLACK; 256];
let palette = match self.debug_palette {
Some(debug_palette) => match self.show_debug_palette {
true => debug_palette,
false => screen.palette,
}
None => screen.palette,
};
table[0..16].clone_from_slice(&palette);
for i in 1..16 { table[i*16..(i+1)*16].fill(palette[i]); }
// Copy pixels to buffer when it is the same size as the screen.
if buffer.area_usize() == screen.area_usize() {
for (i, colour) in buffer.iter_mut().enumerate() {
let fg = screen.fg[i];
let bg = screen.bg[i];
let index = unsafe { fg.unchecked_shl(4) | bg };
*colour = table[index as usize];
// TODO: merge fg and bg: *colour = table[screen.bg[i] as usize];
}
// Copy pixels to buffer when it is a different size to the screen.
} else {
let buffer_width = buffer.width() as usize;
let buffer_height = buffer.height() as usize;
let screen_width = screen.width() as usize;
let screen_height = screen.height() as usize;
let width = std::cmp::min(buffer_width, screen_width );
let height = std::cmp::min(buffer_height, screen_height);
let mut bi = 0;
let mut si = 0;
for _ in 0..height {
let bi_next = bi + buffer_width;
let si_next = si + screen_width;
for _ in 0..width {
let fg = screen.fg[si];
let bg = screen.bg[si];
let index = unsafe { fg.unchecked_shl(4) | bg };
buffer[bi] = table[index as usize];
bi += 1;
si += 1;
}
// Fill remaining right edge with background colour.
buffer[bi..bi_next].fill(table[0]);
bi = bi_next;
si = si_next;
}
// Fill remaining bottom edge with background colour.
buffer[bi..].fill(table[0]);
}
screen.dirty = false;
self.render_mark = Instant::now();
}
}