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(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + Send + 'static { if matches.get_flag("threaded") { run_threaded(matches, init); } else { run_inline(matches, init); } } pub fn run_inline(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result { let mut frontend = MiniFrontendBuilder::default(); let system = init(&mut frontend).unwrap(); frontend .build() .start(matches, Some(system)); } pub fn run_threaded(matches: ArgMatches, init: I) where I: FnOnce(&mut MiniFrontendBuilder) -> Result + 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>) { while !frontend.lock().unwrap().finalized { thread::sleep(Duration::from_millis(10)); } } pub struct MiniFrontendBuilder { video: Option, controllers: Option>, keyboard: Option>, mouse: Option>, mixer: Option, 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, Error> { let source = AudioSource::new(self.mixer.as_ref().unwrap().clone()); Ok(Box::new(source)) } fn register_controllers(&mut self, sender: EventSender) -> 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) -> 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) -> 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, pub controllers: Option>, pub keyboard: Option>, pub mouse: Option>, pub audio: Option, pub mixer: AudioMixer, } impl MiniFrontend { pub fn new( video: Option, controllers: Option>, keyboard: Option>, mouse: Option>, 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) { 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::("scale").map(|s| s.parse::().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::("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); } } } }