input handling

This commit is contained in:
Alex Janka 2023-03-08 15:19:10 +11:00
parent b4d5510bc5
commit f333723f34
6 changed files with 112 additions and 21 deletions

9
Cargo.lock generated
View file

@ -1101,6 +1101,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68" checksum = "0b7668b7cff6a51fe61cdde64cd27c8a220786f399501b57ebe36f7d8112fd68"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"serde",
"unicode-segmentation",
] ]
[[package]] [[package]]
@ -2199,6 +2201,12 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7" checksum = "775c11906edafc97bc378816b94585fbd9a054eabaf86fdd0ced94af449efab7"
[[package]]
name = "unicode-segmentation"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"
version = "0.1.10" version = "0.1.10"
@ -2252,6 +2260,7 @@ dependencies = [
"baseview", "baseview",
"futures", "futures",
"gb-emu-lib", "gb-emu-lib",
"keyboard-types",
"nih_plug", "nih_plug",
"pixels", "pixels",
] ]

View file

@ -17,3 +17,4 @@ baseview = { path = "../vendored/baseview" }
pixels = "0.11.0" pixels = "0.11.0"
async-ringbuf = "0.1.2" async-ringbuf = "0.1.2"
futures = "0.3.26" futures = "0.3.26"
keyboard-types = "0.6.2"

View file

@ -1,7 +1,7 @@
use async_ringbuf::AsyncHeapConsumer; use async_ringbuf::AsyncHeapConsumer;
use futures::executor; use futures::executor;
use gb_emu_lib::{ use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, RomFile}, connect::{AudioOutput, EmulatorMessage, JoypadButtons, RomFile},
EmulatorCore, EmulatorCore,
}; };
use nih_plug::prelude::*; use nih_plug::prelude::*;
@ -26,9 +26,11 @@ struct EmuVars {
pub struct GameboyEmu { pub struct GameboyEmu {
vars: Option<EmuVars>, vars: Option<EmuVars>,
frame_receiver: Arc<FrameReceiver>, frame_receiver: Arc<FrameReceiver>,
key_handler: Arc<JoypadSender>,
} }
type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>; type FrameReceiver = Mutex<Option<Receiver<Vec<[u8; 4]>>>>;
type JoypadSender = Mutex<Option<Sender<(JoypadButtons, bool)>>>;
const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb"); const ROM: &[u8; 32768] = include_bytes!("../../test-roms/Tetris.gb");
@ -89,7 +91,10 @@ impl Plugin for GameboyEmu {
} }
fn editor(&self, _: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> { fn editor(&self, _: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
Some(Box::new(Emulator::new(self.frame_receiver.clone()))) Some(Box::new(Emulator::new(
self.frame_receiver.clone(),
self.key_handler.clone(),
)))
} }
fn initialize( fn initialize(
@ -111,9 +116,10 @@ impl Plugin for GameboyEmu {
let (output, rx) = AudioOutput::new_unfilled(buffer_config.sample_rate, false); let (output, rx) = AudioOutput::new_unfilled(buffer_config.sample_rate, false);
let (renderer, frame_receiver) = EmulatorRenderer::new(); let (renderer, frame_receiver, key_handler) = EmulatorRenderer::new();
*self.frame_receiver.lock().unwrap() = Some(frame_receiver); *self.frame_receiver.lock().unwrap() = Some(frame_receiver);
*self.key_handler.lock().unwrap() = Some(key_handler);
let mut emulator_core = let mut emulator_core =
EmulatorCore::init(receiver, options, Box::new(renderer), output, None); EmulatorCore::init(receiver, options, Box::new(renderer), output, None);

View file

@ -7,21 +7,26 @@ use baseview::{
Event, EventStatus, Size, Window, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions, Event, EventStatus, Size, Window, WindowEvent, WindowHandler, WindowInfo, WindowOpenOptions,
}; };
use gb_emu_lib::{ use gb_emu_lib::{
connect::{Renderer, HEIGHT, WIDTH}, connect::{JoypadButtons, JoypadState, Renderer, HEIGHT, WIDTH},
util::scale_buffer, util::scale_buffer,
}; };
use keyboard_types::{Code, KeyState};
use nih_plug::prelude::*; use nih_plug::prelude::*;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
use crate::FrameReceiver; use crate::{FrameReceiver, JoypadSender};
pub struct Emulator { pub struct Emulator {
frame_receiver: Arc<FrameReceiver>, frame_receiver: Arc<FrameReceiver>,
joypad_sender: Arc<JoypadSender>,
} }
impl Emulator { impl Emulator {
pub fn new(frame_receiver: Arc<FrameReceiver>) -> Self { pub fn new(frame_receiver: Arc<FrameReceiver>, joypad_sender: Arc<JoypadSender>) -> Self {
Self { frame_receiver } Self {
frame_receiver,
joypad_sender,
}
} }
} }
@ -31,7 +36,8 @@ impl Editor for Emulator {
parent: ParentWindowHandle, parent: ParentWindowHandle,
_context: Arc<dyn GuiContext>, _context: Arc<dyn GuiContext>,
) -> Box<dyn std::any::Any + Send> { ) -> Box<dyn std::any::Any + Send> {
let cloned = self.frame_receiver.clone(); let fr_cloned = self.frame_receiver.clone();
let js_cloned = self.joypad_sender.clone();
Window::open_parented( Window::open_parented(
&parent, &parent,
WindowOpenOptions { WindowOpenOptions {
@ -40,9 +46,12 @@ impl Editor for Emulator {
scale: baseview::WindowScalePolicy::SystemScaleFactor, scale: baseview::WindowScalePolicy::SystemScaleFactor,
gl_config: None, gl_config: None,
}, },
|w| EmulatorWindow::new(w, cloned), |w| EmulatorWindow::new(w, fr_cloned, js_cloned),
); );
Box::new(Self::new(self.frame_receiver.clone())) Box::new(Self::new(
self.frame_receiver.clone(),
self.joypad_sender.clone(),
))
} }
fn size(&self) -> (u32, u32) { fn size(&self) -> (u32, u32) {
@ -64,10 +73,15 @@ pub struct EmulatorWindow {
pix: Pixels, pix: Pixels,
scale: usize, scale: usize,
frame_receiver: Arc<FrameReceiver>, frame_receiver: Arc<FrameReceiver>,
joypad_sender: Arc<JoypadSender>,
} }
impl EmulatorWindow { impl EmulatorWindow {
fn new(window: &mut Window, frame_receiver: Arc<FrameReceiver>) -> Self { fn new(
window: &mut Window,
frame_receiver: Arc<FrameReceiver>,
joypad_sender: Arc<JoypadSender>,
) -> Self {
let info = WindowInfo::from_logical_size(Size::new(WIDTH as f64, HEIGHT as f64), 1.); let info = WindowInfo::from_logical_size(Size::new(WIDTH as f64, HEIGHT as f64), 1.);
let (pix, scale) = init_pixbuf(info, window); let (pix, scale) = init_pixbuf(info, window);
@ -76,6 +90,7 @@ impl EmulatorWindow {
pix, pix,
scale, scale,
frame_receiver, frame_receiver,
joypad_sender,
} }
} }
} }
@ -117,22 +132,59 @@ impl WindowHandler for EmulatorWindow {
} }
fn on_event(&mut self, window: &mut Window, event: baseview::Event) -> EventStatus { fn on_event(&mut self, window: &mut Window, event: baseview::Event) -> EventStatus {
if let Event::Window(WindowEvent::Resized(info)) = event { match event {
Event::Window(WindowEvent::Resized(info)) => {
(self.pix, self.scale) = init_pixbuf(info, window); (self.pix, self.scale) = init_pixbuf(info, window);
return EventStatus::Captured; EventStatus::Captured
} }
Event::Keyboard(event) => {
let status = event.state == KeyState::Down;
if let Some(button) = match event.code {
Code::Equal => Some(JoypadButtons::Start),
Code::Minus => Some(JoypadButtons::Select),
Code::Quote => Some(JoypadButtons::A),
Code::Semicolon => Some(JoypadButtons::B),
Code::KeyW | Code::ArrowUp => Some(JoypadButtons::Up),
Code::KeyA | Code::ArrowLeft => Some(JoypadButtons::Left),
Code::KeyS | Code::ArrowDown => Some(JoypadButtons::Down),
Code::KeyD | Code::ArrowRight => Some(JoypadButtons::Right),
_ => None,
} {
if let Some(ref mut sender) =
*self.joypad_sender.lock().expect("failed to lock mutex")
{
sender.send((button, status)).unwrap();
}
EventStatus::Captured
} else {
EventStatus::Ignored EventStatus::Ignored
} }
}
_ => EventStatus::Ignored,
}
}
} }
pub struct EmulatorRenderer { pub struct EmulatorRenderer {
tx: Sender<Vec<[u8; 4]>>, tx: Sender<Vec<[u8; 4]>>,
joypad: JoypadState,
keys: Receiver<(JoypadButtons, bool)>,
} }
impl EmulatorRenderer { impl EmulatorRenderer {
pub(super) fn new() -> (Self, Receiver<Vec<[u8; 4]>>) { #[allow(clippy::type_complexity)]
pub(super) fn new() -> (Self, Receiver<Vec<[u8; 4]>>, Sender<(JoypadButtons, bool)>) {
let (tx, rx) = mpsc::channel::<Vec<[u8; 4]>>(); let (tx, rx) = mpsc::channel::<Vec<[u8; 4]>>();
(Self { tx }, rx) let (keys_tx, keys) = mpsc::channel::<(JoypadButtons, bool)>();
(
Self {
tx,
joypad: JoypadState::default(),
keys,
},
rx,
keys_tx,
)
} }
} }
@ -144,7 +196,19 @@ impl Renderer<[u8; 4]> for EmulatorRenderer {
self.tx.send(buffer.to_vec()); self.tx.send(buffer.to_vec());
} }
fn latest_joypad_state(&mut self) -> gb_emu_lib::connect::JoypadState { fn latest_joypad_state(&mut self) -> JoypadState {
gb_emu_lib::connect::JoypadState::default() while let Ok((key, state)) = self.keys.try_recv() {
match key {
JoypadButtons::Down => self.joypad.down = state,
JoypadButtons::Up => self.joypad.up = state,
JoypadButtons::Left => self.joypad.left = state,
JoypadButtons::Right => self.joypad.right = state,
JoypadButtons::Start => self.joypad.start = state,
JoypadButtons::Select => self.joypad.select = state,
JoypadButtons::B => self.joypad.b = state,
JoypadButtons::A => self.joypad.a = state,
}
}
self.joypad
} }
} }

View file

@ -1,5 +1,5 @@
use crate::processor::memory::mmio::gpu::Colour; use crate::processor::memory::mmio::gpu::Colour;
pub use crate::processor::memory::mmio::joypad::JoypadState; pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
pub use crate::{HEIGHT, WIDTH}; pub use crate::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb}; use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
use futures::executor; use futures::executor;

View file

@ -19,6 +19,17 @@ pub struct JoypadState {
pub a: bool, pub a: bool,
} }
pub enum JoypadButtons {
Down,
Up,
Left,
Right,
Start,
Select,
B,
A,
}
impl JoypadState { impl JoypadState {
pub fn reset(&mut self) { pub fn reset(&mut self) {
self.down = false; self.down = false;