UI: Refactor library module to promote code reuse
This commit is contained in:
parent
dbc96648f1
commit
85956504d7
210
src/io.rs
210
src/io.rs
@ -1,40 +1,62 @@
|
|||||||
//!
|
//! Platform-specific IO/UI code, and some debug functionality.
|
||||||
|
//! TODO: Break this into its own crate.
|
||||||
|
|
||||||
use crate::{bus::{Bus, Region}, cpu::CPU, error::Result};
|
use std::{
|
||||||
|
ffi::OsStr,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
bus::{Bus, Region},
|
||||||
|
error::Result,
|
||||||
|
Chip8,
|
||||||
|
};
|
||||||
use minifb::*;
|
use minifb::*;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct WindowBuilder {
|
pub struct UIBuilder {
|
||||||
pub width: usize,
|
pub width: usize,
|
||||||
pub height: usize,
|
pub height: usize,
|
||||||
pub name: Option<&'static str>,
|
pub name: Option<&'static str>,
|
||||||
|
pub rom: Option<PathBuf>,
|
||||||
pub window_options: WindowOptions,
|
pub window_options: WindowOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilder {
|
impl UIBuilder {
|
||||||
pub fn new(height: usize, width: usize) -> Self {
|
pub fn new(height: usize, width: usize) -> Self {
|
||||||
WindowBuilder {
|
UIBuilder {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn build(&self) -> Result<Window> {
|
pub fn rom(&mut self, path: impl AsRef<Path>) -> &mut Self {
|
||||||
Ok(Window::new(
|
self.rom = Some(path.as_ref().into());
|
||||||
self.name.unwrap_or_default(),
|
self
|
||||||
self.width,
|
}
|
||||||
self.height,
|
pub fn build(&self) -> Result<UI> {
|
||||||
self.window_options,
|
let ui = UI {
|
||||||
)?)
|
window: Window::new(
|
||||||
|
self.name.unwrap_or_default(),
|
||||||
|
self.width,
|
||||||
|
self.height,
|
||||||
|
self.window_options,
|
||||||
|
)?,
|
||||||
|
keyboard: Default::default(),
|
||||||
|
fb: Default::default(),
|
||||||
|
rom: self.rom.to_owned().unwrap_or_default(),
|
||||||
|
};
|
||||||
|
Ok(ui)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WindowBuilder {
|
impl Default for UIBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
WindowBuilder {
|
UIBuilder {
|
||||||
width: 64,
|
width: 64,
|
||||||
height: 32,
|
height: 32,
|
||||||
name: Some("Chip-8 Interpreter"),
|
name: Some("Chip-8 Interpreter"),
|
||||||
|
rom: None,
|
||||||
window_options: WindowOptions {
|
window_options: WindowOptions {
|
||||||
title: true,
|
title: true,
|
||||||
resize: false,
|
resize: false,
|
||||||
@ -94,7 +116,7 @@ impl FrameBuffer {
|
|||||||
//TODO: NOT THIS
|
//TODO: NOT THIS
|
||||||
window
|
window
|
||||||
.update_with_buffer(&self.buffer, self.width, self.height)
|
.update_with_buffer(&self.buffer, self.width, self.height)
|
||||||
.expect("The window manager has encountered an issue I don't want to deal with");
|
.expect("The window manager should update the buffer.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +126,121 @@ impl Default for FrameBuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UI {
|
||||||
|
window: Window,
|
||||||
|
keyboard: Vec<Key>,
|
||||||
|
fb: FrameBuffer,
|
||||||
|
rom: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UI {
|
||||||
|
pub fn frame(&mut self, ch8: &mut Chip8) -> Option<()> {
|
||||||
|
{
|
||||||
|
if ch8.cpu.flags.pause {
|
||||||
|
self.window.set_title("Chirp ⏸")
|
||||||
|
} else {
|
||||||
|
self.window.set_title("Chirp ▶");
|
||||||
|
}
|
||||||
|
// update framebuffer
|
||||||
|
self.fb.render(&mut self.window, &mut ch8.bus);
|
||||||
|
}
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn keys(&mut self, ch8: &mut Chip8) -> Option<()> {
|
||||||
|
// TODO: Remove this hacky workaround for minifb's broken get_keys_* functions.
|
||||||
|
let get_keys_pressed = || {
|
||||||
|
self.window
|
||||||
|
.get_keys()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|key| !self.keyboard.contains(key))
|
||||||
|
};
|
||||||
|
let get_keys_released = || {
|
||||||
|
self.keyboard
|
||||||
|
.clone()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|key| !self.window.get_keys().contains(key))
|
||||||
|
};
|
||||||
|
use crate::io::Region::*;
|
||||||
|
for key in get_keys_released() {
|
||||||
|
ch8.cpu.release(identify_key(key));
|
||||||
|
}
|
||||||
|
// handle keybinds for the UI
|
||||||
|
for key in get_keys_pressed() {
|
||||||
|
use Key::*;
|
||||||
|
match key {
|
||||||
|
F1 | Comma => ch8.cpu.dump(),
|
||||||
|
F2 | Period => ch8
|
||||||
|
.bus
|
||||||
|
.print_screen()
|
||||||
|
.expect("The 'screen' memory region should exist"),
|
||||||
|
F3 => {
|
||||||
|
debug_dump_screen(&ch8, &self.rom).expect("Unable to write debug screen dump");
|
||||||
|
}
|
||||||
|
F4 | Slash => {
|
||||||
|
eprintln!("Debug {}.", {
|
||||||
|
ch8.cpu.flags.debug();
|
||||||
|
if ch8.cpu.flags.debug {
|
||||||
|
"enabled"
|
||||||
|
} else {
|
||||||
|
"disabled"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
F5 | Backslash => eprintln!("{}.", {
|
||||||
|
ch8.cpu.flags.pause();
|
||||||
|
if ch8.cpu.flags.pause {
|
||||||
|
"Paused"
|
||||||
|
} else {
|
||||||
|
"Unpaused"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
F6 | Enter => {
|
||||||
|
eprintln!("Step");
|
||||||
|
ch8.cpu.singlestep(&mut ch8.bus);
|
||||||
|
}
|
||||||
|
F7 => {
|
||||||
|
eprintln!("Set breakpoint {:03x}.", ch8.cpu.pc());
|
||||||
|
ch8.cpu.set_break(ch8.cpu.pc());
|
||||||
|
}
|
||||||
|
F8 => {
|
||||||
|
eprintln!("Unset breakpoint {:03x}.", ch8.cpu.pc());
|
||||||
|
ch8.cpu.unset_break(ch8.cpu.pc());
|
||||||
|
}
|
||||||
|
F9 | Delete => {
|
||||||
|
eprintln!("Soft reset state.cpu {:03x}", ch8.cpu.pc());
|
||||||
|
ch8.cpu.soft_reset();
|
||||||
|
ch8.bus.clear_region(Screen);
|
||||||
|
}
|
||||||
|
Escape => return None,
|
||||||
|
key => ch8.cpu.press(identify_key(key)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.keyboard = self.window.get_keys();
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const KEYMAP: [Key; 16] = [
|
||||||
|
Key::X,
|
||||||
|
Key::Key1,
|
||||||
|
Key::Key2,
|
||||||
|
Key::Key3,
|
||||||
|
Key::Q,
|
||||||
|
Key::W,
|
||||||
|
Key::E,
|
||||||
|
Key::A,
|
||||||
|
Key::S,
|
||||||
|
Key::D,
|
||||||
|
Key::Z,
|
||||||
|
Key::C,
|
||||||
|
Key::Key4,
|
||||||
|
Key::R,
|
||||||
|
Key::F,
|
||||||
|
Key::V,
|
||||||
|
];
|
||||||
|
|
||||||
pub fn identify_key(key: Key) -> usize {
|
pub fn identify_key(key: Key) -> usize {
|
||||||
match key {
|
match key {
|
||||||
Key::Key1 => 0x1,
|
Key::Key1 => 0x1,
|
||||||
@ -126,19 +263,30 @@ pub fn identify_key(key: Key) -> usize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets keys from the Window, and feeds them directly to the CPU
|
pub fn debug_dump_screen(ch8: &Chip8, rom: &Path) -> Result<()> {
|
||||||
pub fn get_keys(window: &mut Window, cpu: &mut CPU) {
|
let path = PathBuf::new()
|
||||||
cpu.release();
|
.join("src/cpu/tests/screens/")
|
||||||
window
|
.join(if rom.is_absolute() {
|
||||||
.get_keys()
|
Path::new("unknown/")
|
||||||
.iter()
|
} else {
|
||||||
.for_each(|key| cpu.press(identify_key(*key)));
|
rom.file_name().unwrap_or(OsStr::new("unknown")).as_ref()
|
||||||
}
|
})
|
||||||
|
.join(format!("{}.bin", ch8.cpu.cycle()));
|
||||||
pub fn debug_dump_screen(bus: &Bus) -> Result<()> {
|
std::fs::write(
|
||||||
Ok(std::fs::write(
|
&path,
|
||||||
"screen_dump.bin",
|
ch8.bus
|
||||||
bus.get_region(Region::Screen)
|
.get_region(Region::Screen)
|
||||||
.expect("Screen should exist, but does not"),
|
.expect("Region::Screen should exist"),
|
||||||
)?)
|
)
|
||||||
|
.unwrap_or_else(|_| {
|
||||||
|
std::fs::write(
|
||||||
|
"screendump.bin",
|
||||||
|
ch8.bus
|
||||||
|
.get_region(Region::Screen)
|
||||||
|
.expect("Region::Screen should exist"),
|
||||||
|
)
|
||||||
|
.ok(); // lmao
|
||||||
|
});
|
||||||
|
eprintln!("Saved to {}", &path.display());
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -26,5 +26,5 @@ pub mod prelude {
|
|||||||
pub use bus::{Bus, Read, Region::*, Write};
|
pub use bus::{Bus, Read, Region::*, Write};
|
||||||
pub use cpu::{disassemble::Disassemble, ControlFlags, CPU};
|
pub use cpu::{disassemble::Disassemble, ControlFlags, CPU};
|
||||||
pub use error::Result;
|
pub use error::Result;
|
||||||
pub use io::{WindowBuilder, *};
|
pub use io::{UIBuilder, *};
|
||||||
}
|
}
|
||||||
|
216
src/main.rs
216
src/main.rs
@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
use chirp::{error::Result, prelude::*};
|
use chirp::{error::Result, prelude::*};
|
||||||
use gumdrop::*;
|
use gumdrop::*;
|
||||||
use minifb::*;
|
|
||||||
use std::fs::read;
|
use std::fs::read;
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
@ -12,28 +11,62 @@ use std::{
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Options, Hash)]
|
||||||
struct Arguments {
|
struct Arguments {
|
||||||
|
#[options(help = "Load a ROM to run on Chirp.", required, free)]
|
||||||
|
pub file: PathBuf,
|
||||||
#[options(help = "Print this help message.")]
|
#[options(help = "Print this help message.")]
|
||||||
help: bool,
|
help: bool,
|
||||||
#[options(help = "Enable behavior incompatible with modern software.")]
|
#[options(help = "Enable debug mode at startup.")]
|
||||||
pub authentic: bool,
|
pub debug: bool,
|
||||||
|
#[options(help = "Enable pause mode at startup.")]
|
||||||
|
pub pause: bool,
|
||||||
|
|
||||||
|
#[options(help = "Set the instructions-per-frame rate.")]
|
||||||
|
pub speed: Option<usize>,
|
||||||
|
#[options(help = "Run the emulator as fast as possible for `step` instructions.")]
|
||||||
|
pub step: Option<usize>,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
short = "z",
|
||||||
|
help = "Disable setting vF to 0 after a bitwise operation."
|
||||||
|
)]
|
||||||
|
pub vfreset: bool,
|
||||||
|
#[options(
|
||||||
|
short = "x",
|
||||||
|
help = "Disable waiting for vblank after issuing a draw call."
|
||||||
|
)]
|
||||||
|
pub drawsync: bool,
|
||||||
|
|
||||||
|
#[options(
|
||||||
|
short = "c",
|
||||||
|
help = "Use CHIP-48 style DMA instructions, which don't touch I."
|
||||||
|
)]
|
||||||
|
pub memory: bool,
|
||||||
|
#[options(
|
||||||
|
short = "v",
|
||||||
|
help = "Use CHIP-48 style bit-shifts, which don't touch vY."
|
||||||
|
)]
|
||||||
|
pub shift: bool,
|
||||||
|
#[options(
|
||||||
|
short = "b",
|
||||||
|
help = "Use SUPER-CHIP style indexed jump, which is indexed relative to v[adr]."
|
||||||
|
)]
|
||||||
|
pub jumping: bool,
|
||||||
|
|
||||||
#[options(
|
#[options(
|
||||||
long = "break",
|
long = "break",
|
||||||
help = "Set breakpoints for the emulator to stop at.",
|
help = "Set breakpoints for the emulator to stop at.",
|
||||||
parse(try_from_str = "parse_hex")
|
parse(try_from_str = "parse_hex"),
|
||||||
|
meta = "BP"
|
||||||
)]
|
)]
|
||||||
pub breakpoints: Vec<u16>,
|
pub breakpoints: Vec<u16>,
|
||||||
#[options(help = "Enable debug mode at startup.")]
|
#[options(
|
||||||
pub debug: bool,
|
help = "Load additional word at address 0x1fe",
|
||||||
#[options(help = "Enable pause mode at startup.", default = "false")]
|
parse(try_from_str = "parse_hex"),
|
||||||
pub pause: bool,
|
meta = "WORD"
|
||||||
#[options(help = "Load a ROM to run on Chirp.", required, free)]
|
)]
|
||||||
pub file: PathBuf,
|
pub data: u16,
|
||||||
#[options(help = "Set the target framerate.", default = "60")]
|
#[options(help = "Set the target framerate.", default = "60", meta = "FR")]
|
||||||
pub frame_rate: u64,
|
pub frame_rate: u64,
|
||||||
#[options(help = "Set the instructions-per-frame rate.", default = "8")]
|
|
||||||
pub speed: usize,
|
|
||||||
#[options(help = "Run the emulator as fast as possible for `step` instructions.")]
|
|
||||||
pub step: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -42,133 +75,77 @@ struct State {
|
|||||||
pub step: Option<usize>,
|
pub step: Option<usize>,
|
||||||
pub rate: u64,
|
pub rate: u64,
|
||||||
pub ch8: Chip8,
|
pub ch8: Chip8,
|
||||||
pub win: Window,
|
pub ui: UI,
|
||||||
pub fb: FrameBuffer,
|
|
||||||
pub ft: Instant,
|
pub ft: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
fn new(options: Arguments) -> Result<Self> {
|
fn new(options: Arguments) -> Result<Self> {
|
||||||
let mut state = State {
|
let mut state = State {
|
||||||
speed: options.speed,
|
speed: options.speed.unwrap_or(8),
|
||||||
step: options.step,
|
step: options.step,
|
||||||
rate: options.frame_rate,
|
rate: options.frame_rate,
|
||||||
ch8: Chip8 { bus: bus! {
|
ch8: Chip8 {
|
||||||
// Load the charset into ROM
|
bus: bus! {
|
||||||
Charset [0x0050..0x00A0] = include_bytes!("mem/charset.bin"),
|
// Load the charset into ROM
|
||||||
// Load the ROM file into RAM
|
Charset [0x0050..0x00A0] = include_bytes!("mem/charset.bin"),
|
||||||
Program [0x0200..0x1000] = &read(options.file)?,
|
// Load the ROM file into RAM
|
||||||
// Create a screen
|
Program [0x0200..0x1000] = &read(&options.file)?,
|
||||||
Screen [0x1000..0x1100],
|
// Create a screen
|
||||||
// Create a stack
|
Screen [0x1000..0x1100],
|
||||||
Stack [0x0EA0..0x0F00],
|
// Create a stack
|
||||||
},
|
Stack [0x0EA0..0x0F00],
|
||||||
cpu: CPU::new(
|
|
||||||
0x1000,
|
|
||||||
0x50,
|
|
||||||
0x200,
|
|
||||||
0xefe,
|
|
||||||
Disassemble::default(),
|
|
||||||
options.breakpoints,
|
|
||||||
ControlFlags {
|
|
||||||
authentic: options.authentic,
|
|
||||||
debug: options.debug,
|
|
||||||
pause: options.pause,
|
|
||||||
..Default::default()
|
|
||||||
},
|
},
|
||||||
)},
|
cpu: CPU::new(
|
||||||
win: WindowBuilder::default().build()?,
|
0x1000,
|
||||||
fb: FrameBuffer::new(64, 32),
|
0x50,
|
||||||
|
0x200,
|
||||||
|
0xefe,
|
||||||
|
Disassemble::default(),
|
||||||
|
options.breakpoints,
|
||||||
|
ControlFlags {
|
||||||
|
quirks: chirp::cpu::Quirks {
|
||||||
|
bin_ops: !options.vfreset,
|
||||||
|
shift: !options.shift,
|
||||||
|
draw_wait: !options.drawsync,
|
||||||
|
dma_inc: !options.memory,
|
||||||
|
stupid_jumps: options.jumping,
|
||||||
|
},
|
||||||
|
debug: options.debug,
|
||||||
|
pause: options.pause,
|
||||||
|
monotonic: options.speed,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
ui: UIBuilder::default().rom(&options.file).build()?,
|
||||||
ft: Instant::now(),
|
ft: Instant::now(),
|
||||||
};
|
};
|
||||||
state.fb.render(&mut state.win, &state.ch8.bus);
|
state.ch8.bus.write(0x1feu16, options.data);
|
||||||
Ok(state)
|
Ok(state)
|
||||||
}
|
}
|
||||||
|
fn keys(&mut self) -> Option<()> {
|
||||||
|
self.ui.keys(&mut self.ch8)
|
||||||
|
}
|
||||||
|
fn frame(&mut self) -> Option<()> {
|
||||||
|
self.ui.frame(&mut self.ch8)
|
||||||
|
}
|
||||||
fn tick_cpu(&mut self) {
|
fn tick_cpu(&mut self) {
|
||||||
if !self.ch8.cpu.flags.pause {
|
if !self.ch8.cpu.flags.pause {
|
||||||
let rate = self.speed;
|
let rate = self.speed;
|
||||||
match self.step {
|
match self.step {
|
||||||
Some(ticks) => {
|
Some(ticks) => {
|
||||||
self.ch8.cpu.multistep(&mut self.ch8.bus, ticks, rate);
|
self.ch8.cpu.multistep(&mut self.ch8.bus, ticks);
|
||||||
// Pause the CPU and clear step
|
// Pause the CPU and clear step
|
||||||
self.ch8.cpu.flags.pause = true;
|
self.ch8.cpu.flags.pause = true;
|
||||||
self.step = None;
|
self.step = None;
|
||||||
},
|
}
|
||||||
None => {
|
None => {
|
||||||
self.ch8.cpu.multistep(&mut self.ch8.bus, rate, rate);
|
self.ch8.cpu.multistep(&mut self.ch8.bus, rate);
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn frame(&mut self) -> Option<()> {
|
|
||||||
{
|
|
||||||
if self.ch8.cpu.flags.pause {
|
|
||||||
self.win.set_title("Chirp ⏸")
|
|
||||||
} else {
|
|
||||||
self.win.set_title("Chirp ▶");
|
|
||||||
}
|
|
||||||
// update framebuffer
|
|
||||||
self.fb.render(&mut self.win, &mut self.ch8.bus);
|
|
||||||
// get key input (has to happen after render)
|
|
||||||
chirp::io::get_keys(&mut self.win, &mut self.ch8.cpu);
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
fn keys(&mut self) -> Option<()> {
|
|
||||||
// handle keybinds for the UI
|
|
||||||
for key in self.win.get_keys_pressed(KeyRepeat::No) {
|
|
||||||
use Key::*;
|
|
||||||
match key {
|
|
||||||
F1 | Comma => self.ch8.cpu.dump(),
|
|
||||||
F2 | Period => self
|
|
||||||
.ch8.bus
|
|
||||||
.print_screen()
|
|
||||||
.expect("The 'screen' memory region exists"),
|
|
||||||
F3 => {chirp::io::debug_dump_screen(&self.ch8.bus).expect("Unable to write debug screen dump"); eprintln!("Screen dumped to file.")},
|
|
||||||
F4 | Slash => {
|
|
||||||
eprintln!(
|
|
||||||
"{}",
|
|
||||||
endis("Debug", {
|
|
||||||
self.ch8.cpu.flags.debug();
|
|
||||||
self.ch8.cpu.flags.debug
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
F5 | Backslash => eprintln!(
|
|
||||||
"{}",
|
|
||||||
endis("Pause", {
|
|
||||||
self.ch8.cpu.flags.pause();
|
|
||||||
self.ch8.cpu.flags.pause
|
|
||||||
})
|
|
||||||
),
|
|
||||||
F6 | Enter => {
|
|
||||||
eprintln!("Step");
|
|
||||||
self.ch8.cpu.singlestep(&mut self.ch8.bus);
|
|
||||||
}
|
|
||||||
F7 => {
|
|
||||||
eprintln!("Set breakpoint {:x}", self.ch8.cpu.pc());
|
|
||||||
self.ch8.cpu.set_break(self.ch8.cpu.pc());
|
|
||||||
}
|
|
||||||
F8 => {
|
|
||||||
eprintln!("Unset breakpoint {:x}", self.ch8.cpu.pc());
|
|
||||||
self.ch8.cpu.unset_break(self.ch8.cpu.pc());
|
|
||||||
}
|
|
||||||
F9 | Delete => {
|
|
||||||
eprintln!("Soft reset state.cpu {:x}", self.ch8.cpu.pc());
|
|
||||||
self.ch8.cpu.soft_reset();
|
|
||||||
self.ch8.bus.clear_region(Screen);
|
|
||||||
}
|
|
||||||
F10 | Backspace => {
|
|
||||||
eprintln!("Hard reset state.cpu");
|
|
||||||
self.ch8.cpu = CPU::default();
|
|
||||||
self.ch8.bus.clear_region(Screen);
|
|
||||||
}
|
|
||||||
Escape => return None,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
fn wait_for_next_frame(&mut self) {
|
fn wait_for_next_frame(&mut self) {
|
||||||
let rate = 1_000_000_000 / self.rate + 1;
|
let rate = 1_000_000_000 / self.rate + 1;
|
||||||
std::thread::sleep(Duration::from_nanos(rate).saturating_sub(self.ft.elapsed()));
|
std::thread::sleep(Duration::from_nanos(rate).saturating_sub(self.ft.elapsed()));
|
||||||
@ -198,8 +175,3 @@ fn main() -> Result<()> {
|
|||||||
fn parse_hex(value: &str) -> std::result::Result<u16, std::num::ParseIntError> {
|
fn parse_hex(value: &str) -> std::result::Result<u16, std::num::ParseIntError> {
|
||||||
u16::from_str_radix(value, 16)
|
u16::from_str_radix(value, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transforms a bool into "enabled"/"disabled"
|
|
||||||
fn endis(name: &str, state: bool) -> String {
|
|
||||||
format!("{name} {}", if state { "enabled" } else { "disabled" })
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user