From 108e366781ea3ce0d1abb08c657e6c4e464dcb5b Mon Sep 17 00:00:00 2001 From: Simon Gellis Date: Mon, 11 Nov 2024 00:50:57 -0500 Subject: [PATCH] First pass at multiplayer --- shrooms-vb-core | 2 +- src/app.rs | 57 +++++++----- src/app/game.rs | 78 ++++++++-------- src/app/input.rs | 51 ++++++----- src/controller.rs | 38 ++++---- src/emulator.rs | 122 +++++++++++++++++++------- src/{ => emulator}/shrooms_vb_core.rs | 56 ++++++++---- src/input.rs | 81 ++++++++++------- src/main.rs | 1 - 9 files changed, 302 insertions(+), 184 deletions(-) rename src/{ => emulator}/shrooms_vb_core.rs (83%) diff --git a/shrooms-vb-core b/shrooms-vb-core index ae22c95..b4b9131 160000 --- a/shrooms-vb-core +++ b/shrooms-vb-core @@ -1 +1 @@ -Subproject commit ae22c95dbee3d0b338168bfdf98143e6eddc6c70 +Subproject commit b4b9131f3976388b096343725fce1bdf35f4e3df diff --git a/src/app.rs b/src/app.rs index a5d62ac..91b9381 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,6 +5,7 @@ use std::{ }; use game::GameWindow; +use input::InputWindow; use winit::{ application::ApplicationHandler, event::{Event, WindowEvent}, @@ -14,7 +15,7 @@ use winit::{ use crate::{ controller::ControllerState, - emulator::{EmulatorClient, EmulatorCommand}, + emulator::{EmulatorClient, EmulatorCommand, SimId}, input::InputMapper, }; @@ -28,6 +29,7 @@ pub struct App { input_mapper: Arc>, controller: ControllerState, proxy: EventLoopProxy, + player_2_window: Option, } impl App { @@ -40,19 +42,19 @@ impl App { input_mapper, controller, proxy, + player_2_window: None, } } } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { - let mut window = GameWindow::new( + let window = GameWindow::new( event_loop, + SimId::Player1, self.client.clone(), - self.input_mapper.clone(), self.proxy.clone(), ); - window.init(); self.windows.insert(window.id(), Box::new(window)); } @@ -63,9 +65,9 @@ impl ApplicationHandler for App { event: WindowEvent, ) { if let WindowEvent::KeyboardInput { event, .. } = &event { - if self.controller.key_event(event) { + if let Some((sim_id, pressed)) = self.controller.key_event(event) { self.client - .send_command(EmulatorCommand::SetKeys(self.controller.pressed())); + .send_command(EmulatorCommand::SetKeys(sim_id, pressed)); } } let Some(window) = self.windows.get_mut(&window_id) else { @@ -74,13 +76,30 @@ impl ApplicationHandler for App { window.handle_event(event_loop, &Event::WindowEvent { window_id, event }); } - fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: UserEvent) { + fn user_event(&mut self, event_loop: &ActiveEventLoop, event: UserEvent) { match event { - UserEvent::OpenWindow(mut window) => { - window.init(); - self.windows.insert(window.id(), window); + UserEvent::OpenInputWindow => { + let window = + InputWindow::new(event_loop, self.input_mapper.clone(), self.proxy.clone()); + self.windows.insert(window.id(), Box::new(window)); } - UserEvent::CloseWindow(window_id) => { + UserEvent::OpenPlayer2Window => { + if self.player_2_window.is_some() { + return; + } + let window = GameWindow::new( + event_loop, + SimId::Player2, + self.client.clone(), + self.proxy.clone(), + ); + self.player_2_window = Some(window.id()); + self.windows.insert(window.id(), Box::new(window)); + } + UserEvent::Close(window_id) => { + if self.player_2_window == Some(window_id) { + self.player_2_window.take(); + } self.windows.remove(&window_id); } } @@ -112,20 +131,12 @@ impl ApplicationHandler for App { pub trait AppWindow { fn id(&self) -> WindowId; - fn init(&mut self); fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event); } +#[derive(Debug)] pub enum UserEvent { - OpenWindow(Box), - CloseWindow(WindowId), -} - -impl Debug for UserEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::OpenWindow(window) => f.debug_tuple("OpenWindow").field(&window.id()).finish(), - Self::CloseWindow(window_id) => f.debug_tuple("CloseWindow").field(window_id).finish(), - } - } + OpenInputWindow, + OpenPlayer2Window, + Close(WindowId), } diff --git a/src/app/game.rs b/src/app/game.rs index efbeae0..ad3f6af 100644 --- a/src/app/game.rs +++ b/src/app/game.rs @@ -1,7 +1,4 @@ -use std::{ - sync::{Arc, RwLock}, - time::Instant, -}; +use std::{sync::Arc, time::Instant}; use wgpu::util::DeviceExt as _; use winit::{ dpi::LogicalSize, @@ -11,24 +8,22 @@ use winit::{ }; use crate::{ - emulator::{EmulatorClient, EmulatorCommand}, - input::InputMapper, + emulator::{EmulatorClient, EmulatorCommand, SimId}, renderer::GameRenderer, }; use super::{ common::{ImguiState, WindowState, WindowStateBuilder}, - input::InputWindow, AppWindow, UserEvent, }; pub struct GameWindow { window: WindowState, - imgui: Option, + imgui: ImguiState, pipeline: wgpu::RenderPipeline, bind_group: wgpu::BindGroup, + sim_id: SimId, client: EmulatorClient, - input_mapper: Arc>, proxy: EventLoopProxy, paused_due_to_minimize: bool, } @@ -36,21 +31,29 @@ pub struct GameWindow { impl GameWindow { pub fn new( event_loop: &ActiveEventLoop, + sim_id: SimId, client: EmulatorClient, - input_mapper: Arc>, proxy: EventLoopProxy, ) -> Self { + let title = if sim_id == SimId::Player2 { + "Shrooms VB (Player 2)" + } else { + "Shrooms VB" + }; let window = WindowStateBuilder::new(event_loop) - .with_title("Shrooms VB") + .with_title(title) .with_inner_size(LogicalSize::new(384, 244)) .build(); let device = &window.device; let eyes = Arc::new(GameRenderer::create_texture(device, "eye")); - client.send_command(EmulatorCommand::SetRenderer(GameRenderer { - queue: window.queue.clone(), - eyes: eyes.clone(), - })); + client.send_command(EmulatorCommand::SetRenderer( + sim_id, + GameRenderer { + queue: window.queue.clone(), + eyes: eyes.clone(), + }, + )); let eyes = eyes.create_view(&wgpu::TextureViewDescriptor::default()); let sampler = device.create_sampler(&wgpu::SamplerDescriptor::default()); let colors = Colors { @@ -158,13 +161,15 @@ impl GameWindow { cache: None, }); + let imgui = ImguiState::new(&window); + Self { window, - imgui: None, + imgui, pipeline: render_pipeline, bind_group, + sim_id, client, - input_mapper, proxy, paused_due_to_minimize: false, } @@ -172,7 +177,7 @@ impl GameWindow { fn draw(&mut self, event_loop: &ActiveEventLoop) { let window = &mut self.window; - let imgui = self.imgui.as_mut().unwrap(); + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); let mut new_size = None; @@ -204,7 +209,8 @@ impl GameWindow { .show_open_single_file() .unwrap(); if let Some(path) = rom { - self.client.send_command(EmulatorCommand::LoadGame(path)); + self.client + .send_command(EmulatorCommand::LoadGame(self.sim_id, path)); } } if ui.menu_item("Quit") { @@ -240,14 +246,14 @@ impl GameWindow { }); ui.menu("Input", || { if ui.menu_item("Bind Inputs") { - let input_window = Box::new(InputWindow::new( - event_loop, - self.input_mapper.clone(), - self.proxy.clone(), - )); - self.proxy - .send_event(UserEvent::OpenWindow(input_window)) - .unwrap(); + self.proxy.send_event(UserEvent::OpenInputWindow).unwrap(); + } + }); + ui.menu("Multiplayer", || { + if self.sim_id == SimId::Player1 && ui.menu_item("Open Player 2") { + self.client + .send_command(EmulatorCommand::StartSecondSim(None)); + self.proxy.send_event(UserEvent::OpenPlayer2Window).unwrap(); } }); }); @@ -321,11 +327,6 @@ impl AppWindow for GameWindow { self.window.window.id() } - fn init(&mut self) { - self.imgui = Some(ImguiState::new(&self.window)); - self.window.window.request_redraw(); - } - fn handle_event(&mut self, event_loop: &ActiveEventLoop, event: &Event) { match event { Event::WindowEvent { event, .. } => match event { @@ -341,7 +342,14 @@ impl AppWindow for GameWindow { self.paused_due_to_minimize = false; } } - WindowEvent::CloseRequested => event_loop.exit(), + WindowEvent::CloseRequested => { + if self.sim_id == SimId::Player2 { + self.client.send_command(EmulatorCommand::StopSecondSim); + self.proxy.send_event(UserEvent::Close(self.id())).unwrap(); + } else { + event_loop.exit(); + } + } WindowEvent::RedrawRequested => self.draw(event_loop), _ => (), }, @@ -351,9 +359,7 @@ impl AppWindow for GameWindow { _ => (), } let window = &self.window; - let Some(imgui) = self.imgui.as_mut() else { - return; - }; + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); imgui .platform diff --git a/src/app/input.rs b/src/app/input.rs index d5ffe7e..b39f8b1 100644 --- a/src/app/input.rs +++ b/src/app/input.rs @@ -7,10 +7,12 @@ use winit::{ dpi::LogicalSize, event::{Event, KeyEvent, WindowEvent}, event_loop::{ActiveEventLoop, EventLoopProxy}, - platform::modifier_supplement::KeyEventExtModifierSupplement, }; -use crate::{input::InputMapper, shrooms_vb_core::VBKey}; +use crate::{ + emulator::{SimId, VBKey}, + input::InputMapper, +}; use super::{ common::{ImguiState, UiExt, WindowState, WindowStateBuilder}, @@ -19,10 +21,10 @@ use super::{ pub struct InputWindow { window: WindowState, - imgui: Option, + imgui: ImguiState, input_mapper: Arc>, proxy: EventLoopProxy, - now_binding: Option, + now_binding: Option<(SimId, VBKey)>, } const KEY_NAMES: [(VBKey, &str); 14] = [ @@ -52,9 +54,10 @@ impl InputWindow { .with_title("Bind Inputs") .with_inner_size(LogicalSize::new(600, 400)) .build(); + let imgui = ImguiState::new(&window); Self { window, - imgui: None, + imgui, input_mapper, now_binding: None, proxy, @@ -63,7 +66,7 @@ impl InputWindow { fn draw(&mut self) { let window = &mut self.window; - let imgui = self.imgui.as_mut().unwrap(); + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); let now = Instant::now(); @@ -85,11 +88,11 @@ impl InputWindow { .expect("Failed to prepare frame"); let ui = context.new_frame(); - let mut render_key_bindings = || { + let mut render_key_bindings = |sim_id: SimId| { if let Some(table) = ui.begin_table("controls", 2) { let binding_names = { let mapper = self.input_mapper.read().unwrap(); - mapper.binding_names() + mapper.binding_names(&sim_id) }; ui.table_next_row(); @@ -100,20 +103,20 @@ impl InputWindow { ui.group(|| { ui.right_align_text(name, space * 0.20); ui.same_line(); - let label_text = if self.now_binding == Some(key) { + let label_text = if self.now_binding == Some((sim_id, key)) { "Press any input" } else { binding.unwrap_or("") }; let label = format!("{}##{}", label_text, name); if ui.button_with_size(label, [space * 0.60, 0.0]) { - self.now_binding = Some(key); + self.now_binding = Some((sim_id, key)); } }); ui.same_line(); if ui.button(format!("Clear##{name}")) { let mut mapper = self.input_mapper.write().unwrap(); - mapper.clear_binding(key); + mapper.clear_binding(sim_id, key); } } @@ -124,7 +127,11 @@ impl InputWindow { if let Some(window) = ui.fullscreen_window() { if let Some(tabs) = ui.tab_bar("tabs") { if let Some(tab) = ui.tab_item("Player 1") { - render_key_bindings(); + render_key_bindings(SimId::Player1); + tab.end(); + } + if let Some(tab) = ui.tab_item("Player 2") { + render_key_bindings(SimId::Player2); tab.end(); } tabs.end(); @@ -175,11 +182,11 @@ impl InputWindow { if !event.state.is_pressed() { return; } - let Some(vb) = self.now_binding.take() else { + let Some((sim_id, vb)) = self.now_binding.take() else { return; }; let mut mapper = self.input_mapper.write().unwrap(); - mapper.bind_key(vb, event.key_without_modifiers()); + mapper.bind_key(sim_id, vb, event.physical_key); } } @@ -188,19 +195,13 @@ impl AppWindow for InputWindow { self.window.window.id() } - fn init(&mut self) { - self.imgui = Some(ImguiState::new(&self.window)); - self.window.window.request_redraw(); - } - fn handle_event(&mut self, _: &ActiveEventLoop, event: &Event) { match event { Event::WindowEvent { event, .. } => match event { WindowEvent::Resized(size) => self.window.handle_resize(size), - WindowEvent::CloseRequested => self - .proxy - .send_event(UserEvent::CloseWindow(self.id())) - .unwrap(), + WindowEvent::CloseRequested => { + self.proxy.send_event(UserEvent::Close(self.id())).unwrap() + } WindowEvent::KeyboardInput { event, .. } => self.try_bind_key(event), WindowEvent::RedrawRequested => self.draw(), _ => (), @@ -212,9 +213,7 @@ impl AppWindow for InputWindow { } let window = &self.window; - let Some(imgui) = self.imgui.as_mut() else { - return; - }; + let imgui = &mut self.imgui; let mut context = imgui.context.lock().unwrap(); imgui .platform diff --git a/src/controller.rs b/src/controller.rs index 70861ab..d10a1ab 100644 --- a/src/controller.rs +++ b/src/controller.rs @@ -2,48 +2,46 @@ use std::sync::{Arc, RwLock}; use winit::event::{ElementState, KeyEvent}; -use crate::{input::InputMapper, shrooms_vb_core::VBKey}; +use crate::{ + emulator::{SimId, VBKey}, + input::InputMapper, +}; pub struct ControllerState { input_mapper: Arc>, - pressed: VBKey, + pressed: Vec, } impl ControllerState { pub fn new(input_mapper: Arc>) -> Self { Self { input_mapper, - pressed: VBKey::SGN, + pressed: vec![VBKey::SGN; 2], } } - pub fn pressed(&self) -> VBKey { - self.pressed - } - - pub fn key_event(&mut self, event: &KeyEvent) -> bool { - let Some(input) = self.key_event_to_input(event) else { - return false; - }; + pub fn key_event(&mut self, event: &KeyEvent) -> Option<(SimId, VBKey)> { + let (sim_id, input) = self.key_event_to_input(event)?; + let pressed = &mut self.pressed[sim_id.to_index()]; match event.state { ElementState::Pressed => { - if self.pressed.contains(input) { - return false; + if pressed.contains(input) { + return None; } - self.pressed.insert(input); - true + pressed.insert(input); + Some((sim_id, *pressed)) } ElementState::Released => { - if !self.pressed.contains(input) { - return false; + if !pressed.contains(input) { + return None; } - self.pressed.remove(input); - true + pressed.remove(input); + Some((sim_id, *pressed)) } } } - fn key_event_to_input(&self, event: &KeyEvent) -> Option { + fn key_event_to_input(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { let mapper = self.input_mapper.read().unwrap(); mapper.key_event(event) } diff --git a/src/emulator.rs b/src/emulator.rs index 38fdab6..4bb8386 100644 --- a/src/emulator.rs +++ b/src/emulator.rs @@ -1,4 +1,5 @@ use std::{ + collections::HashMap, fs, path::{Path, PathBuf}, sync::{ @@ -10,11 +11,11 @@ use std::{ use anyhow::Result; -use crate::{ - audio::Audio, - renderer::GameRenderer, - shrooms_vb_core::{CoreVB, VBKey}, -}; +use crate::{audio::Audio, renderer::GameRenderer}; +use shrooms_vb_core::Sim; +pub use shrooms_vb_core::VBKey; + +mod shrooms_vb_core; pub struct EmulatorBuilder { rom: Option, @@ -23,6 +24,20 @@ pub struct EmulatorBuilder { has_game: Arc, } +#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)] +pub enum SimId { + Player1, + Player2, +} +impl SimId { + pub fn to_index(self) -> usize { + match self { + Self::Player1 => 0, + Self::Player2 => 1, + } + } +} + impl EmulatorBuilder { pub fn new() -> (Self, EmulatorClient) { let (queue, commands) = mpsc::channel(); @@ -50,17 +65,17 @@ impl EmulatorBuilder { pub fn build(self) -> Result { let mut emulator = Emulator::new(self.commands, self.running, self.has_game)?; if let Some(path) = self.rom { - emulator.load_rom(&path)?; + emulator.load_rom_from_file(SimId::Player1, &path)?; } Ok(emulator) } } pub struct Emulator { - sim: CoreVB, + sims: Vec, audio: Audio, commands: mpsc::Receiver, - renderer: Option, + renderers: HashMap, running: Arc, has_game: Arc, } @@ -72,24 +87,50 @@ impl Emulator { has_game: Arc, ) -> Result { Ok(Self { - sim: CoreVB::new(), + sims: vec![], audio: Audio::init()?, commands, - renderer: None, + renderers: HashMap::new(), running, has_game, }) } - pub fn load_rom(&mut self, path: &Path) -> Result<()> { + pub fn load_rom_from_file(&mut self, sim_id: SimId, path: &Path) -> Result<()> { let bytes = fs::read(path)?; - self.sim.reset(); - self.sim.load_rom(bytes)?; + self.load_rom(sim_id, bytes)?; + Ok(()) + } + + pub fn start_second_sim(&mut self, rom: Option) -> Result<()> { + let bytes = if let Some(path) = rom { + fs::read(path)? + } else if let Some(rom) = self.sims.first().and_then(|s| s.clone_rom()) { + rom + } else { + return Ok(()); + }; + self.load_rom(SimId::Player2, bytes)?; + Ok(()) + } + + fn load_rom(&mut self, sim_id: SimId, bytes: Vec) -> Result<()> { + while self.sims.len() <= sim_id.to_index() { + self.sims.push(Sim::new()); + } + let sim = &mut self.sims[sim_id.to_index()]; + sim.reset(); + sim.load_rom(bytes)?; self.has_game.store(true, Ordering::Release); self.running.store(true, Ordering::Release); Ok(()) } + pub fn stop_second_sim(&mut self) { + self.renderers.remove(&SimId::Player2); + self.sims.truncate(1); + } + pub fn run(&mut self) { let mut eye_contents = vec![0u8; 384 * 224 * 2]; let mut audio_samples = vec![]; @@ -97,15 +138,20 @@ impl Emulator { let mut idle = true; if self.running.load(Ordering::Acquire) { idle = false; - self.sim.emulate_frame(); + Sim::emulate_many(&mut self.sims); } - if let Some(renderer) = &mut self.renderer { - if self.sim.read_pixels(&mut eye_contents) { - idle = false; - renderer.render(&eye_contents); + for (sim_id, renderer) in self.renderers.iter_mut() { + if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { + if sim.read_pixels(&mut eye_contents) { + idle = false; + renderer.render(&eye_contents); + } } } - self.sim.read_samples(&mut audio_samples); + let weight = 1.0 / self.sims.len() as f32; + for sim in self.sims.iter_mut() { + sim.read_samples(&mut audio_samples, weight); + } if !audio_samples.is_empty() { idle = false; self.audio.update(&audio_samples); @@ -137,14 +183,22 @@ impl Emulator { fn handle_command(&mut self, command: EmulatorCommand) { match command { - EmulatorCommand::SetRenderer(renderer) => { - self.renderer = Some(renderer); + EmulatorCommand::SetRenderer(sim_id, renderer) => { + self.renderers.insert(sim_id, renderer); } - EmulatorCommand::LoadGame(path) => { - if let Err(error) = self.load_rom(&path) { + EmulatorCommand::LoadGame(sim_id, path) => { + if let Err(error) = self.load_rom_from_file(sim_id, &path) { eprintln!("error loading rom: {}", error); } } + EmulatorCommand::StartSecondSim(path) => { + if let Err(error) = self.start_second_sim(path) { + eprintln!("error starting second sim: {}", error); + } + } + EmulatorCommand::StopSecondSim => { + self.stop_second_sim(); + } EmulatorCommand::Pause => { self.running.store(false, Ordering::Release); } @@ -154,11 +208,17 @@ impl Emulator { } } EmulatorCommand::Reset => { - self.sim.reset(); - self.running.store(true, Ordering::Release); + for sim in self.sims.iter_mut() { + sim.reset(); + } + if !self.sims.is_empty() { + self.running.store(true, Ordering::Release); + } } - EmulatorCommand::SetKeys(keys) => { - self.sim.set_keys(keys); + EmulatorCommand::SetKeys(sim_id, keys) => { + if let Some(sim) = self.sims.get_mut(sim_id.to_index()) { + sim.set_keys(keys); + } } } } @@ -166,12 +226,14 @@ impl Emulator { #[derive(Debug)] pub enum EmulatorCommand { - SetRenderer(GameRenderer), - LoadGame(PathBuf), + SetRenderer(SimId, GameRenderer), + LoadGame(SimId, PathBuf), + StartSecondSim(Option), + StopSecondSim, Pause, Resume, Reset, - SetKeys(VBKey), + SetKeys(SimId, VBKey), } #[derive(Clone)] diff --git a/src/shrooms_vb_core.rs b/src/emulator/shrooms_vb_core.rs similarity index 83% rename from src/shrooms_vb_core.rs rename to src/emulator/shrooms_vb_core.rs index 324689d..507085e 100644 --- a/src/shrooms_vb_core.rs +++ b/src/emulator/shrooms_vb_core.rs @@ -25,6 +25,12 @@ enum VBDataType { F32 = 5, } +#[repr(i32)] +#[derive(FromPrimitive, ToPrimitive)] +enum VBOption { + PseudoHalt = 0, +} + bitflags! { #[repr(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -52,8 +58,8 @@ type OnFrame = extern "C" fn(sim: *mut VB) -> c_int; #[link(name = "vb")] extern "C" { - #[link_name = "vbEmulate"] - fn vb_emulate(sim: *mut VB, cycles: *mut u32) -> c_int; + #[link_name = "vbEmulateEx"] + fn vb_emulate_ex(sims: *mut *mut VB, count: c_uint, cycles: *mut u32) -> c_int; #[link_name = "vbGetCartROM"] fn vb_get_cart_rom(sim: *mut VB, size: *mut u32) -> *mut c_void; #[link_name = "vbGetPixels"] @@ -81,10 +87,12 @@ extern "C" { fn vb_reset(sim: *mut VB); #[link_name = "vbSetCartROM"] fn vb_set_cart_rom(sim: *mut VB, rom: *mut c_void, size: u32) -> c_int; - #[link_name = "vbSetKeys"] - fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; #[link_name = "vbSetFrameCallback"] fn vb_set_frame_callback(sim: *mut VB, on_frame: OnFrame); + #[link_name = "vbSetKeys"] + fn vb_set_keys(sim: *mut VB, keys: u16) -> u16; + #[link_name = "vbSetOption"] + fn vb_set_option(sim: *mut VB, key: VBOption, value: c_int); #[link_name = "vbSetSamples"] fn vb_set_samples( sim: *mut VB, @@ -113,14 +121,15 @@ struct VBState { frame_seen: bool, } -pub struct CoreVB { +#[repr(transparent)] +pub struct Sim { sim: *mut VB, } // SAFETY: the memory pointed to by sim is valid -unsafe impl Send for CoreVB {} +unsafe impl Send for Sim {} -impl CoreVB { +impl Sim { pub fn new() -> Self { // init the VB instance itself let size = unsafe { vb_size_of() }; @@ -128,6 +137,7 @@ impl CoreVB { let memory = vec![0u64; size.div_ceil(4)]; let sim: *mut VB = Box::into_raw(memory.into_boxed_slice()).cast(); unsafe { vb_init(sim) }; + unsafe { vb_set_option(sim, VBOption::PseudoHalt, 1) }; unsafe { vb_reset(sim) }; // set up userdata @@ -140,7 +150,7 @@ impl CoreVB { let samples: *mut c_void = Box::into_raw(audio_buffer.into_boxed_slice()).cast(); unsafe { vb_set_samples(sim, samples, VBDataType::F32, AUDIO_CAPACITY_SAMPLES as u32) }; - CoreVB { sim } + Sim { sim } } pub fn reset(&mut self) { @@ -162,6 +172,17 @@ impl CoreVB { } } + pub fn clone_rom(&self) -> Option> { + let mut size = 0; + let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; + if rom.is_null() { + return None; + } + // SAFETY: rom definitely points to a valid array of `size` bytes + let slice: &[u8] = unsafe { slice::from_raw_parts(rom.cast(), size as usize) }; + Some(slice.to_vec()) + } + fn unload_rom(&mut self) -> Option> { let mut size = 0; let rom = unsafe { vb_get_cart_rom(self.sim, &mut size) }; @@ -173,9 +194,11 @@ impl CoreVB { Some(vec) } - pub fn emulate_frame(&mut self) { + pub fn emulate_many(sims: &mut [Sim]) { let mut cycles = 20_000_000; - unsafe { vb_emulate(self.sim, &mut cycles) }; + let count = sims.len() as c_uint; + let sims = sims.as_mut_ptr().cast(); + unsafe { vb_emulate_ex(sims, count, &mut cycles) }; } pub fn read_pixels(&mut self, buffers: &mut [u8]) -> bool { @@ -203,14 +226,17 @@ impl CoreVB { true } - pub fn read_samples(&mut self, samples: &mut Vec) { + pub fn read_samples(&mut self, samples: &mut Vec, weight: f32) { let mut position = 0; let ptr = unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), &mut position) }; // SAFETY: position is an offset in a buffer of (f32, f32). so, position * 2 is an offset in a buffer of f32. - let read_samples: &mut [f32] = - unsafe { slice::from_raw_parts_mut(ptr.cast(), position as usize * 2) }; - samples.extend_from_slice(read_samples); + let read_samples: &[f32] = + unsafe { slice::from_raw_parts(ptr.cast(), position as usize * 2) }; + samples.resize(read_samples.len(), 0.0); + for (index, sample) in read_samples.iter().enumerate() { + samples[index] += sample * weight; + } unsafe { vb_set_samples( @@ -227,7 +253,7 @@ impl CoreVB { } } -impl Drop for CoreVB { +impl Drop for Sim { fn drop(&mut self) { let ptr = unsafe { vb_get_samples(self.sim, ptr::null_mut(), ptr::null_mut(), ptr::null_mut()) }; diff --git a/src/input.rs b/src/input.rs index 5367d60..44e626e 100644 --- a/src/input.rs +++ b/src/input.rs @@ -2,15 +2,14 @@ use std::collections::HashMap; use winit::{ event::KeyEvent, - keyboard::{Key, NamedKey}, - platform::modifier_supplement::KeyEventExtModifierSupplement, + keyboard::{KeyCode, PhysicalKey}, }; -use crate::shrooms_vb_core::VBKey; +use crate::emulator::{SimId, VBKey}; pub struct InputMapper { - vb_bindings: HashMap, - key_bindings: HashMap, + vb_bindings: HashMap>, + key_bindings: HashMap, } impl InputMapper { @@ -19,30 +18,48 @@ impl InputMapper { vb_bindings: HashMap::new(), key_bindings: HashMap::new(), }; - mapper.bind_key(VBKey::SEL, Key::Character("a".into())); - mapper.bind_key(VBKey::STA, Key::Character("s".into())); - mapper.bind_key(VBKey::B, Key::Character("d".into())); - mapper.bind_key(VBKey::A, Key::Character("f".into())); - mapper.bind_key(VBKey::LT, Key::Character("e".into())); - mapper.bind_key(VBKey::RT, Key::Character("r".into())); - mapper.bind_key(VBKey::RU, Key::Character("i".into())); - mapper.bind_key(VBKey::RL, Key::Character("j".into())); - mapper.bind_key(VBKey::RD, Key::Character("k".into())); - mapper.bind_key(VBKey::RR, Key::Character("l".into())); - mapper.bind_key(VBKey::LU, Key::Named(NamedKey::ArrowUp)); - mapper.bind_key(VBKey::LL, Key::Named(NamedKey::ArrowLeft)); - mapper.bind_key(VBKey::LD, Key::Named(NamedKey::ArrowDown)); - mapper.bind_key(VBKey::LR, Key::Named(NamedKey::ArrowRight)); + mapper.bind_key(SimId::Player1, VBKey::SEL, PhysicalKey::Code(KeyCode::KeyA)); + mapper.bind_key(SimId::Player1, VBKey::STA, PhysicalKey::Code(KeyCode::KeyS)); + mapper.bind_key(SimId::Player1, VBKey::B, PhysicalKey::Code(KeyCode::KeyD)); + mapper.bind_key(SimId::Player1, VBKey::A, PhysicalKey::Code(KeyCode::KeyF)); + mapper.bind_key(SimId::Player1, VBKey::LT, PhysicalKey::Code(KeyCode::KeyE)); + mapper.bind_key(SimId::Player1, VBKey::RT, PhysicalKey::Code(KeyCode::KeyR)); + mapper.bind_key(SimId::Player1, VBKey::RU, PhysicalKey::Code(KeyCode::KeyI)); + mapper.bind_key(SimId::Player1, VBKey::RL, PhysicalKey::Code(KeyCode::KeyJ)); + mapper.bind_key(SimId::Player1, VBKey::RD, PhysicalKey::Code(KeyCode::KeyK)); + mapper.bind_key(SimId::Player1, VBKey::RR, PhysicalKey::Code(KeyCode::KeyL)); + mapper.bind_key( + SimId::Player1, + VBKey::LU, + PhysicalKey::Code(KeyCode::ArrowUp), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LL, + PhysicalKey::Code(KeyCode::ArrowLeft), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LD, + PhysicalKey::Code(KeyCode::ArrowDown), + ); + mapper.bind_key( + SimId::Player1, + VBKey::LR, + PhysicalKey::Code(KeyCode::ArrowRight), + ); mapper } - pub fn binding_names(&self) -> HashMap { - self.vb_bindings + pub fn binding_names(&self, sim_id: &SimId) -> HashMap { + let Some(bindings) = self.vb_bindings.get(sim_id) else { + return HashMap::new(); + }; + bindings .iter() .map(|(k, v)| { let name = match v { - Key::Character(char) => char.to_string(), - Key::Named(key) => format!("{:?}", key), + PhysicalKey::Code(code) => format!("{code:?}"), k => format!("{:?}", k), }; (*k, name) @@ -50,22 +67,22 @@ impl InputMapper { .collect() } - pub fn bind_key(&mut self, vb: VBKey, key: Key) { - if let Some(old) = self.vb_bindings.insert(vb, key.clone()) { + pub fn bind_key(&mut self, sim_id: SimId, vb: VBKey, key: PhysicalKey) { + let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); + if let Some(old) = vb_bindings.insert(vb, key) { self.key_bindings.remove(&old); } - self.key_bindings.insert(key, vb); + self.key_bindings.insert(key, (sim_id, vb)); } - pub fn clear_binding(&mut self, vb: VBKey) { - if let Some(old) = self.vb_bindings.remove(&vb) { + pub fn clear_binding(&mut self, sim_id: SimId, vb: VBKey) { + let vb_bindings = self.vb_bindings.entry(sim_id).or_default(); + if let Some(old) = vb_bindings.remove(&vb) { self.key_bindings.remove(&old); } } - pub fn key_event(&self, event: &KeyEvent) -> Option { - self.key_bindings - .get(&event.key_without_modifiers()) - .cloned() + pub fn key_event(&self, event: &KeyEvent) -> Option<(SimId, VBKey)> { + self.key_bindings.get(&event.physical_key).copied() } } diff --git a/src/main.rs b/src/main.rs index b8de1cf..9297ee4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ mod controller; mod emulator; mod input; mod renderer; -mod shrooms_vb_core; #[derive(Parser)] struct Args {