mod display;
mod input;
mod sound;
use crate::{Interpreter, Pixel};
use anyhow::Context;
use crossbeam::atomic::AtomicCell;
use crossbeam::sync::WaitGroup;
use std::error::Error;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::thread;
use std::time::Instant;
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::ControlFlow;
use winit_input_helper::WinitInputHelper;
pub fn run<I>(mut interpreter: I) -> !
where
I: Interpreter + Send + 'static,
{
log::info!("Initalising display components...");
let (event_loop, window, mut pixels) = display::init()
.context("Could not initialise display subsystem.")
.unwrap();
log::info!("Initalising input components...");
let mut input = WinitInputHelper::new();
let frame_buffer = Arc::new(AtomicCell::new(([[Pixel::default(); 64]; 32], false)));
let input_buffer = Arc::new(AtomicCell::new([false; 16]));
let wg = WaitGroup::new();
let handle = thread::Builder::new().name("VM Executor".to_string()).spawn({
let wg = wg.clone();
let frame_buffer = frame_buffer.clone();
let input_buffer = input_buffer.clone();
move || {
log::info!("Initalising audio components...");
let buzzer = sound::Buzzer::init()
.map_err(|e| {
log::error!("Failure in initalising audio: {e:?}. Continuing with no sound.")
})
.ok();
log::info!("Starting CPU...");
wg.wait(); loop {
let t0 = Instant::now();
if let Some(update) = interpreter.step(&input_buffer.load()) {
frame_buffer.store((update, false));
}
if let Some(buzzer) = &buzzer {
buzzer.switch.store(false, Ordering::Relaxed);
}
if let Some(sleepy_time) = interpreter.speed().checked_sub(Instant::now() - t0) {
thread::sleep(sleepy_time);
log::debug!(
"Took {:?} to execute instruction",
interpreter.speed() - sleepy_time
)
} else {
log::warn!("CPU clock is running slow, your interpreter is taking too long to execute instructions.")
}
}
}
}).context("Could not start VM execution thread").unwrap();
wg.wait(); log::info!("Starting input & display event loop...");
event_loop.run(move |event, _, control_flow| {
if handle.is_finished() {
log::error!("VM thread has exited, shutting down...");
*control_flow = ControlFlow::Exit;
return;
}
let new_frame = frame_buffer.load();
if !new_frame.1 {
display::update(&mut pixels, &new_frame.0)
.context("Failed to update display")
.unwrap(); }
if let Event::RedrawRequested(_) = event {
if let Err(e) = pixels.render() {
panic!("Pixels rendering failure, caused by: {:?}", e.source());
}
}
if input.update(&event) {
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
*control_flow = ControlFlow::Exit;
return;
}
input_buffer.swap(input::key_state(&input));
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
}
}
window.request_redraw();
});
}