shrooms-vb-native/src/emulator.rs

200 lines
5.4 KiB
Rust

use std::{
fs,
path::{Path, PathBuf},
sync::{
atomic::{AtomicBool, Ordering},
mpsc::{self, RecvError, TryRecvError},
Arc,
},
};
use anyhow::Result;
use crate::{
audio::Audio,
renderer::GameRenderer,
shrooms_vb_core::{CoreVB, VBKey},
};
pub struct EmulatorBuilder {
rom: Option<PathBuf>,
commands: mpsc::Receiver<EmulatorCommand>,
running: Arc<AtomicBool>,
has_game: Arc<AtomicBool>,
}
impl EmulatorBuilder {
pub fn new() -> (Self, EmulatorClient) {
let (queue, commands) = mpsc::channel();
let builder = Self {
rom: None,
commands,
running: Arc::new(AtomicBool::new(false)),
has_game: Arc::new(AtomicBool::new(false)),
};
let client = EmulatorClient {
queue,
running: builder.running.clone(),
has_game: builder.has_game.clone(),
};
(builder, client)
}
pub fn with_rom(self, path: &Path) -> Self {
Self {
rom: Some(path.into()),
..self
}
}
pub fn build(self) -> Result<Emulator> {
let mut emulator = Emulator::new(self.commands, self.running, self.has_game)?;
if let Some(path) = self.rom {
emulator.load_rom(&path)?;
}
Ok(emulator)
}
}
pub struct Emulator {
sim: CoreVB,
audio: Audio,
commands: mpsc::Receiver<EmulatorCommand>,
renderer: Option<GameRenderer>,
running: Arc<AtomicBool>,
has_game: Arc<AtomicBool>,
}
impl Emulator {
fn new(
commands: mpsc::Receiver<EmulatorCommand>,
running: Arc<AtomicBool>,
has_game: Arc<AtomicBool>,
) -> Result<Self> {
Ok(Self {
sim: CoreVB::new(),
audio: Audio::init()?,
commands,
renderer: None,
running,
has_game,
})
}
pub fn load_rom(&mut self, path: &Path) -> Result<()> {
let bytes = fs::read(path)?;
self.sim.reset();
self.sim.load_rom(bytes)?;
self.has_game.store(true, Ordering::Release);
self.running.store(true, Ordering::Release);
Ok(())
}
pub fn run(&mut self) {
let mut eye_contents = vec![0u8; 384 * 224 * 2];
let mut audio_samples = vec![];
loop {
let mut idle = true;
if self.running.load(Ordering::Acquire) {
idle = false;
self.sim.emulate_frame();
}
if let Some(renderer) = &mut self.renderer {
if self.sim.read_pixels(&mut eye_contents) {
idle = false;
renderer.render(&eye_contents);
}
}
self.sim.read_samples(&mut audio_samples);
if !audio_samples.is_empty() {
idle = false;
self.audio.update(&audio_samples);
audio_samples.clear();
}
if idle {
// The game is paused, and we have output all the video/audio we have.
// Block the thread until a new command comes in.
match self.commands.recv() {
Ok(command) => self.handle_command(command),
Err(RecvError) => {
return;
}
}
}
loop {
match self.commands.try_recv() {
Ok(command) => self.handle_command(command),
Err(TryRecvError::Empty) => {
break;
}
Err(TryRecvError::Disconnected) => {
return;
}
}
}
}
}
fn handle_command(&mut self, command: EmulatorCommand) {
match command {
EmulatorCommand::SetRenderer(renderer) => {
self.renderer = Some(renderer);
}
EmulatorCommand::LoadGame(path) => {
if let Err(error) = self.load_rom(&path) {
eprintln!("error loading rom: {}", error);
}
}
EmulatorCommand::Pause => {
self.running.store(false, Ordering::Release);
}
EmulatorCommand::Resume => {
if self.has_game.load(Ordering::Acquire) {
self.running.store(true, Ordering::Relaxed);
}
}
EmulatorCommand::Reset => {
self.sim.reset();
self.running.store(true, Ordering::Release);
}
EmulatorCommand::SetKeys(keys) => {
self.sim.set_keys(keys);
}
}
}
}
#[derive(Debug)]
pub enum EmulatorCommand {
SetRenderer(GameRenderer),
LoadGame(PathBuf),
Pause,
Resume,
Reset,
SetKeys(VBKey),
}
#[derive(Clone)]
pub struct EmulatorClient {
queue: mpsc::Sender<EmulatorCommand>,
running: Arc<AtomicBool>,
has_game: Arc<AtomicBool>,
}
impl EmulatorClient {
pub fn is_running(&self) -> bool {
self.running.load(Ordering::Acquire)
}
pub fn has_game(&self) -> bool {
self.has_game.load(Ordering::Acquire)
}
pub fn send_command(&self, command: EmulatorCommand) {
if let Err(err) = self.queue.send(command) {
eprintln!(
"could not send command {:?} as emulator is shut down",
err.0
);
}
}
}