This repository has been archived on 2024-12-14. You can view files and clone it, but cannot push or open issues or pull requests.
rustboyadvance-ng/platform/rustboyadvance-wasm/src/emulator.rs
Michel Heily 12d9edf5c4 Fix some cargo-clippy, and broken sub-crates
Former-commit-id: 93db7bc11bff9a48f4d66e0a378cd77ab42ca197
Former-commit-id: a6ce714c2a6a4112ff30d748c0686b1b2da41c6b
2022-09-05 00:34:00 +03:00

183 lines
5 KiB
Rust

use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::Clamped;
use js_sys::Float32Array;
use web_sys::AudioContext;
use web_sys::CanvasRenderingContext2d;
use rustboyadvance_core::keypad as gba_keypad;
use rustboyadvance_core::prelude::*;
use rustboyadvance_utils::audio::AudioRingBuffer;
use bit::BitIndex;
#[wasm_bindgen]
pub struct Emulator {
gba: GameBoyAdvance,
interface: Rc<RefCell<Interface>>,
}
struct Interface {
frame: Vec<u8>,
keyinput: u16,
sample_rate: i32,
audio_ctx: AudioContext,
audio_ring_buffer: AudioRingBuffer,
}
impl Drop for Interface {
fn drop(&mut self) {
let _ = self.audio_ctx.clone();
}
}
impl Interface {
fn new(audio_ctx: AudioContext) -> Result<Interface, JsValue> {
Ok(Interface {
frame: vec![0; 240 * 160 * 4],
keyinput: gba_keypad::KEYINPUT_ALL_RELEASED,
sample_rate: audio_ctx.sample_rate() as i32,
audio_ctx: audio_ctx,
audio_ring_buffer: Default::default(),
})
}
}
impl VideoInterface for Interface {
fn render(&mut self, buffer: &[u32]) {
// TODO optimize
for i in 0..buffer.len() {
let color = buffer[i];
self.frame[4 * i + 0] = ((color >> 16) & 0xff) as u8;
self.frame[4 * i + 1] = ((color >> 8) & 0xff) as u8;
self.frame[4 * i + 2] = (color & 0xff) as u8;
self.frame[4 * i + 3] = 255;
}
}
}
fn convert_sample(s: i16) -> f32 {
(s as f32) / 32767_f32
}
impl AudioInterface for Interface {
fn get_sample_rate(&self) -> i32 {
self.sample_rate
}
fn push_sample(&mut self, samples: &[i16]) {
let prod = self.audio_ring_buffer.producer();
for s in samples.iter() {
let _ = prod.push(*s);
}
}
}
impl InputInterface for Interface {
fn poll(&mut self) -> u16 {
self.keyinput
}
}
#[wasm_bindgen]
impl Emulator {
#[wasm_bindgen(constructor)]
pub fn new(bios: &[u8], rom: &[u8]) -> Result<Emulator, JsValue> {
let audio_ctx = web_sys::AudioContext::new()?;
let interface = Rc::new(RefCell::new(Interface::new(audio_ctx)?));
let gamepak = GamepakBuilder::new()
.take_buffer(rom.to_vec().into_boxed_slice())
.without_backup_to_file()
.build()
.unwrap();
let gba = GameBoyAdvance::new(
bios.to_vec().into_boxed_slice(),
gamepak,
interface.clone(),
interface.clone(),
interface.clone(),
);
Ok(Emulator { gba, interface })
}
pub fn skip_bios(&mut self) {
self.gba.skip_bios();
}
pub fn run_frame(&mut self, ctx: &CanvasRenderingContext2d) -> Result<(), JsValue> {
self.gba.frame();
let mut frame_buffer = &mut self.interface.borrow_mut().frame;
let data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(
Clamped(&mut frame_buffer),
240,
160,
)
.unwrap();
ctx.put_image_data(&data, 0.0, 0.0)
}
fn map_key(event_key: &str) -> Option<gba_keypad::Keys> {
match event_key {
"Enter" => Some(gba_keypad::Keys::Start),
"Backspace" => Some(gba_keypad::Keys::Select),
"ArrowUp" => Some(gba_keypad::Keys::Up),
"ArrowDown" => Some(gba_keypad::Keys::Down),
"ArrowLeft" => Some(gba_keypad::Keys::Left),
"ArrowRight" => Some(gba_keypad::Keys::Right),
"z" => Some(gba_keypad::Keys::ButtonB),
"x" => Some(gba_keypad::Keys::ButtonA),
"a" => Some(gba_keypad::Keys::ButtonL),
"s" => Some(gba_keypad::Keys::ButtonR),
_ => None,
}
}
pub fn key_down(&mut self, event_key: &str) {
debug!("Key down: {}", event_key);
let mut interface = self.interface.borrow_mut();
if let Some(key) = Emulator::map_key(event_key) {
interface.keyinput.set_bit(key as usize, false);
}
}
pub fn key_up(&mut self, event_key: &str) {
debug!("Key up: {}", event_key);
let mut interface = self.interface.borrow_mut();
if let Some(key) = Emulator::map_key(event_key) {
interface.keyinput.set_bit(key as usize, true);
}
}
pub fn test_fps(&mut self) {
use rustboyadvance_utils::FpsCounter;
let mut fps_counter = FpsCounter::default();
self.gba.skip_bios();
for _ in 0..6000 {
self.gba.frame();
if let Some(fps) = fps_counter.tick() {
info!("FPS: {}", fps);
}
}
}
pub fn collect_audio_samples(&self) -> Result<Float32Array, JsValue> {
let mut interface = self.interface.borrow_mut();
let consumer = interface.audio_ring_buffer.consumer();
let mut samples = Vec::with_capacity(consumer.len());
while let Some(sample) = consumer.pop() {
samples.push(convert_sample(sample));
}
Ok(Float32Array::from(samples.as_slice()))
}
}