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 90032373a8 Wasm improvments
Former-commit-id: f51fc18327f6adb0011ff2aff2787d513fb6aa37
2020-04-15 15:50:56 +03:00

184 lines
5.1 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::CanvasRenderingContext2d;
use web_sys::AudioContext;
use rustboyadvance_core::core::keypad as gba_keypad;
use rustboyadvance_core::prelude::*;
use rustboyadvance_core::util::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: AudioRingBuffer::new(),
}
)
}
}
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: StereoSample<i16>) {
self.audio_ring_buffer.prod.push(samples.0).unwrap();
self.audio_ring_buffer.prod.push(samples.1).unwrap();
}
}
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_core::util::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 = &mut interface.audio_ring_buffer.cons;
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()))
}
}