moa/emulator/frontends/minifb/src/lib.rs

363 lines
12 KiB
Rust

use std::thread;
use std::io::{self, Write};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use minifb::{self, Key, MouseMode, MouseButton};
use clap::{Command, Arg, ArgAction, ArgMatches};
use moa_core::{System, Error, ClockDuration, Device, Debugger, DebugControl};
use moa_core::host::{Host, Audio, KeyEvent, MouseEvent, MouseState, ControllerDevice, ControllerEvent, EventSender, PixelEncoding, Frame, FrameReceiver};
use moa_common::{AudioMixer, AudioSource};
use moa_common::CpalAudioOutput;
mod keys;
mod controllers;
use crate::keys::map_key;
use crate::controllers::map_controller_a;
const WIDTH: u32 = 320;
const HEIGHT: u32 = 224;
pub fn new(name: &'static str) -> Command {
Command::new(name)
.arg(Arg::new("scale")
.short('s')
.long("scale")
.help("Scale the screen"))
.arg(Arg::new("speed")
.short('x')
.long("speed")
.help("Adjust the speed of the simulation"))
.arg(Arg::new("threaded")
.short('t')
.long("threaded")
.action(ArgAction::SetTrue)
.help("Run the simulation in a separate thread"))
.arg(Arg::new("log-level")
.short('l')
.long("log-level")
.help("Set the type of log messages to print"))
.arg(Arg::new("debugger")
.short('d')
.long("debugger")
.action(ArgAction::SetTrue)
.help("Start the debugger before running machine"))
.arg(Arg::new("disable-audio")
.short('a')
.long("disable-audio")
.action(ArgAction::SetTrue)
.help("Disable audio output"))
}
pub fn run<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result<System, Error> + Send + 'static {
if matches.get_flag("threaded") {
run_threaded(matches, init);
} else {
run_inline(matches, init);
}
}
pub fn run_inline<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result<System, Error> {
let mut frontend = MiniFrontendBuilder::default();
let system = init(&mut frontend).unwrap();
frontend
.build()
.start(matches, Some(system));
}
pub fn run_threaded<I>(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result<System, Error> + Send + 'static {
let frontend = Arc::new(Mutex::new(MiniFrontendBuilder::default()));
{
let frontend = frontend.clone();
thread::spawn(move || {
let mut system = init(&mut frontend.lock().unwrap()).unwrap();
frontend.lock().unwrap().finalize();
system.run_forever().unwrap();
});
}
wait_until_initialized(frontend.clone());
frontend
.lock().unwrap()
.build()
.start(matches, None);
}
fn wait_until_initialized(frontend: Arc<Mutex<MiniFrontendBuilder>>) {
while !frontend.lock().unwrap().finalized {
thread::sleep(Duration::from_millis(10));
}
}
pub struct MiniFrontendBuilder {
video: Option<FrameReceiver>,
controllers: Option<EventSender<ControllerEvent>>,
keyboard: Option<EventSender<KeyEvent>>,
mouse: Option<EventSender<MouseEvent>>,
mixer: Option<AudioMixer>,
finalized: bool,
}
impl Default for MiniFrontendBuilder {
fn default() -> Self {
Self {
video: None,
controllers: None,
keyboard: None,
mouse: None,
mixer: Some(AudioMixer::with_default_rate()),
finalized: false,
}
}
}
impl MiniFrontendBuilder {
pub fn finalize(&mut self) {
self.finalized = true;
}
pub fn build(&mut self) -> MiniFrontend {
let video = std::mem::take(&mut self.video);
let controllers = std::mem::take(&mut self.controllers);
let keyboard = std::mem::take(&mut self.keyboard);
let mouse = std::mem::take(&mut self.mouse);
let mixer = std::mem::take(&mut self.mixer);
MiniFrontend::new(video, controllers, keyboard, mouse, mixer.unwrap())
}
}
impl Host for MiniFrontendBuilder {
fn add_video_source(&mut self, receiver: FrameReceiver) -> Result<(), Error> {
if self.video.is_some() {
return Err(Error::new("Only one video source can be registered with this frontend"));
}
self.video = Some(receiver);
Ok(())
}
fn add_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
let source = AudioSource::new(self.mixer.as_ref().unwrap().clone());
Ok(Box::new(source))
}
fn register_controllers(&mut self, sender: EventSender<ControllerEvent>) -> Result<(), Error> {
if self.controllers.is_some() {
return Err(Error::new("A controller updater has already been registered with the frontend"));
}
self.controllers = Some(sender);
Ok(())
}
fn register_keyboard(&mut self, sender: EventSender<KeyEvent>) -> Result<(), Error> {
if self.keyboard.is_some() {
return Err(Error::new("A keyboard updater has already been registered with the frontend"));
}
self.keyboard = Some(sender);
Ok(())
}
fn register_mouse(&mut self, sender: EventSender<MouseEvent>) -> Result<(), Error> {
if self.mouse.is_some() {
return Err(Error::new("A mouse updater has already been registered with the frontend"));
}
self.mouse = Some(sender);
Ok(())
}
}
pub struct MiniFrontend {
pub modifiers: u16,
pub mouse_state: MouseState,
pub video: Option<FrameReceiver>,
pub controllers: Option<EventSender<ControllerEvent>>,
pub keyboard: Option<EventSender<KeyEvent>>,
pub mouse: Option<EventSender<MouseEvent>>,
pub audio: Option<CpalAudioOutput>,
pub mixer: AudioMixer,
}
impl MiniFrontend {
pub fn new(
video: Option<FrameReceiver>,
controllers: Option<EventSender<ControllerEvent>>,
keyboard: Option<EventSender<KeyEvent>>,
mouse: Option<EventSender<MouseEvent>>,
mixer: AudioMixer,
) -> Self {
Self {
modifiers: 0,
mouse_state: Default::default(),
video,
controllers,
keyboard,
mouse,
audio: None,
mixer,
}
}
pub fn start(&mut self, matches: ArgMatches, mut system: Option<System>) {
let log_level = match matches.get_one("log-level").map(|s: &String| s.as_str()) {
Some("trace") => log::Level::Trace,
Some("debug") => log::Level::Debug,
Some("info") => log::Level::Info,
Some("warn") => log::Level::Warn,
Some("error") => log::Level::Error,
_ => log::Level::Warn,
};
simple_logger::SimpleLogger::new()
.with_level(log_level.to_level_filter())
.without_timestamps()
.init()
.unwrap();
if self.mixer.borrow_mut().num_sources() != 0 && !matches.get_flag("disable-audio") {
if let Some(system) = system.as_mut() {
system.add_device("mixer", Device::new(self.mixer.clone())).unwrap();
}
self.audio = Some(CpalAudioOutput::create_audio_output(self.mixer.borrow_mut().get_sink()));
}
let options = minifb::WindowOptions {
scale: match matches.get_one::<String>("scale").map(|s| s.parse::<u8>().unwrap()) {
Some(1) => minifb::Scale::X1,
Some(2) => minifb::Scale::X2,
Some(4) => minifb::Scale::X4,
Some(8) => minifb::Scale::X8,
_ => minifb::Scale::X2,
},
..Default::default()
};
let speed = matches.get_one::<f32>("speed").cloned().unwrap_or(1.0);
let mut size = (WIDTH, HEIGHT);
if let Some(queue) = self.video.as_mut() {
size = queue.max_size();
queue.request_encoding(PixelEncoding::ARGB);
}
let mut window = minifb::Window::new(
"Test - ESC to exit",
size.0 as usize,
size.1 as usize,
options,
)
.unwrap_or_else(|e| {
panic!("{}", e);
});
// Limit to max ~60 fps update rate
window.limit_update_rate(Some(Duration::from_micros(16600)));
//let nanoseconds_per_frame = (16_600_000 as f32 * speed) as u64;
let mut debugger = Debugger::default();
let mut run_debugger = matches.get_flag("debugger");
let mut update_timer = Instant::now();
let mut last_frame = Frame::new(size.0, size.1, PixelEncoding::ARGB);
while window.is_open() && !window.is_key_down(Key::Escape) {
if run_debugger {
if let Some(system) = system.as_mut() {
debugger.print_step(system).unwrap();
if debugger.check_auto_command(system).unwrap() != DebugControl::Continue {
let mut buffer = String::new();
io::stdout().write_all(b"> ").unwrap();
io::stdin().read_line(&mut buffer).unwrap();
match debugger.run_command(system, &buffer) {
Ok(DebugControl::Exit) => {
run_debugger = false;
},
Ok(_) => {},
Err(err) => {
println!("Error: {:?}", err);
},
}
}
}
} else {
let frame_time = update_timer.elapsed();
update_timer = Instant::now();
//println!("new frame after {:?}us", frame_time.as_micros());
//let run_timer = Instant::now();
if let Some(system) = system.as_mut() {
//system.run_for(nanoseconds_per_frame).unwrap();
match system.run_for_duration(ClockDuration::from_nanos((frame_time.as_nanos() as f32 * speed) as u64)) {
Ok(()) => {},
Err(Error::Breakpoint(_)) => {
run_debugger = true;
},
Err(err) => panic!("{:?}", err),
}
//system.run_until_break().unwrap();
}
//let sim_time = run_timer.elapsed().as_micros();
//println!("ran simulation for {:?}us in {:?}us (avg: {:?}us)", frame_time.as_micros(), sim_time, frame_time.as_micros() as f64 / sim_time as f64);
}
if let Some(keys) = window.get_keys_pressed(minifb::KeyRepeat::No) {
for key in keys {
self.check_key(key, true);
// Process special keys
if let Key::D = key {
run_debugger = true;
}
}
}
if let Some(keys) = window.get_keys_released() {
for key in keys {
self.check_key(key, false);
}
}
if let Some(sender) = self.mouse.as_mut() {
if let Some((x, y)) = window.get_mouse_pos(MouseMode::Clamp) {
let left = window.get_mouse_down(MouseButton::Left);
let right = window.get_mouse_down(MouseButton::Right);
let middle = window.get_mouse_down(MouseButton::Middle);
let next_state = MouseState::with(left, right, middle, x as u32, y as u32);
self.mouse_state
.to_events(next_state)
.into_iter()
.for_each(|event| sender.send(event));
}
}
if let Some(queue) = self.video.as_mut() {
if let Some((_clock, frame)) = queue.latest() {
last_frame = frame
}
window.update_with_buffer(&last_frame.bitmap, last_frame.width as usize, last_frame.height as usize).unwrap();
}
}
}
fn check_key(&mut self, key: Key, state: bool) {
if let Some(sender) = self.keyboard.as_mut() {
sender.send(KeyEvent::new(map_key(key), state));
}
if let Some(sender) = self.controllers.as_mut() {
if let Some(input) = map_controller_a(key, state) {
let event = ControllerEvent::new(ControllerDevice::A, input);
sender.send(event);
}
}
}
}