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

View file

@ -16,4 +16,5 @@ nih_plug = { path = "../vendored/nih-plug", features = ["standalone"] }
baseview = { path = "../vendored/baseview" }
pixels = "0.11.0"
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 futures::executor;
use gb_emu_lib::{
connect::{AudioOutput, EmulatorMessage, RomFile},
connect::{AudioOutput, EmulatorMessage, JoypadButtons, RomFile},
EmulatorCore,
};
use nih_plug::prelude::*;
@ -26,9 +26,11 @@ struct EmuVars {
pub struct GameboyEmu {
vars: Option<EmuVars>,
frame_receiver: Arc<FrameReceiver>,
key_handler: Arc<JoypadSender>,
}
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");
@ -89,7 +91,10 @@ impl Plugin for GameboyEmu {
}
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(
@ -111,9 +116,10 @@ impl Plugin for GameboyEmu {
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.key_handler.lock().unwrap() = Some(key_handler);
let mut emulator_core =
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,
};
use gb_emu_lib::{
connect::{Renderer, HEIGHT, WIDTH},
connect::{JoypadButtons, JoypadState, Renderer, HEIGHT, WIDTH},
util::scale_buffer,
};
use keyboard_types::{Code, KeyState};
use nih_plug::prelude::*;
use pixels::{Pixels, SurfaceTexture};
use crate::FrameReceiver;
use crate::{FrameReceiver, JoypadSender};
pub struct Emulator {
frame_receiver: Arc<FrameReceiver>,
joypad_sender: Arc<JoypadSender>,
}
impl Emulator {
pub fn new(frame_receiver: Arc<FrameReceiver>) -> Self {
Self { frame_receiver }
pub fn new(frame_receiver: Arc<FrameReceiver>, joypad_sender: Arc<JoypadSender>) -> Self {
Self {
frame_receiver,
joypad_sender,
}
}
}
@ -31,7 +36,8 @@ impl Editor for Emulator {
parent: ParentWindowHandle,
_context: Arc<dyn GuiContext>,
) -> 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(
&parent,
WindowOpenOptions {
@ -40,9 +46,12 @@ impl Editor for Emulator {
scale: baseview::WindowScalePolicy::SystemScaleFactor,
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) {
@ -64,10 +73,15 @@ pub struct EmulatorWindow {
pix: Pixels,
scale: usize,
frame_receiver: Arc<FrameReceiver>,
joypad_sender: Arc<JoypadSender>,
}
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 (pix, scale) = init_pixbuf(info, window);
@ -76,6 +90,7 @@ impl EmulatorWindow {
pix,
scale,
frame_receiver,
joypad_sender,
}
}
}
@ -117,22 +132,59 @@ impl WindowHandler for EmulatorWindow {
}
fn on_event(&mut self, window: &mut Window, event: baseview::Event) -> EventStatus {
if let Event::Window(WindowEvent::Resized(info)) = event {
(self.pix, self.scale) = init_pixbuf(info, window);
return EventStatus::Captured;
match event {
Event::Window(WindowEvent::Resized(info)) => {
(self.pix, self.scale) = init_pixbuf(info, window);
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 {
tx: Sender<Vec<[u8; 4]>>,
joypad: JoypadState,
keys: Receiver<(JoypadButtons, bool)>,
}
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]>>();
(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());
}
fn latest_joypad_state(&mut self) -> gb_emu_lib::connect::JoypadState {
gb_emu_lib::connect::JoypadState::default()
fn latest_joypad_state(&mut self) -> JoypadState {
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;
pub use crate::processor::memory::mmio::joypad::JoypadState;
pub use crate::processor::memory::mmio::joypad::{JoypadButtons, JoypadState};
pub use crate::{HEIGHT, WIDTH};
use async_ringbuf::{AsyncHeapConsumer, AsyncHeapProducer, AsyncHeapRb};
use futures::executor;

View file

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