use crate::gba::Gba;
use crate::platform::app_context::{AppContext, SharedAppContext};
use crate::platform::emulator::Emulator;
use crate::platform::frontend_toasts::cartridge_load_toast_message;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub struct WasmGba {
gba: Gba,
audio_muted: bool,
rom_loaded: bool,
pending_toasts: Vec<String>,
frame_rgba_buffer: Vec<u8>,
frame_rgb_buffer: Vec<u8>,
}
impl Default for WasmGba {
fn default() -> Self {
Self::new()
}
}
#[wasm_bindgen]
impl WasmGba {
fn create_with_skip_bios_intro(skip_bios_intro: bool) -> WasmGba {
console_error_panic_hook::set_once();
let mut config = crate::platform::config::Config::default();
config.gba.skip_bios_intro = skip_bios_intro;
let app_context: SharedAppContext =
Rc::new(RefCell::new(AppContext::new_with_config(config)));
WasmGba {
gba: Gba::new(app_context),
audio_muted: false,
rom_loaded: false,
pending_toasts: Vec::new(),
frame_rgba_buffer: Vec::new(),
frame_rgb_buffer: Vec::new(),
}
}
fn required_rgba_len() -> usize {
(Gba::SCREEN_WIDTH * Gba::SCREEN_HEIGHT * 4) as usize
}
fn ensure_rgba_buffer(&mut self) {
let required = Self::required_rgba_len();
if self.frame_rgba_buffer.len() != required {
self.frame_rgba_buffer.resize(required, 0xFF);
for alpha in self.frame_rgba_buffer.iter_mut().skip(3).step_by(4) {
*alpha = 0xFF;
}
}
}
fn required_rgb_len() -> usize {
(Gba::SCREEN_WIDTH * Gba::SCREEN_HEIGHT * 3) as usize
}
fn fill_black_rgb_frame(&mut self) {
let required = Self::required_rgb_len();
if self.frame_rgb_buffer.len() != required {
self.frame_rgb_buffer.resize(required, 0);
} else {
self.frame_rgb_buffer.fill(0);
}
}
fn fill_opaque_black_frame(&mut self) {
self.ensure_rgba_buffer();
self.frame_rgba_buffer.fill(0);
for alpha in self.frame_rgba_buffer.iter_mut().skip(3).step_by(4) {
*alpha = 0xFF;
}
}
fn run_until_frame_ready(&mut self) {
while !self.gba.is_ready_to_render() {
self.gba.run_tick();
}
self.gba.clear_ready_to_render();
}
fn drain_audio_buffer(&mut self) {
while self.gba.get_sample().is_some() {}
}
#[cfg(all(test, target_arch = "wasm32"))]
pub(crate) fn joypad_button_states_for_test(&self) -> u8 {
self.gba.get_joypad_button_states(1)
}
#[wasm_bindgen(constructor)]
pub fn new() -> WasmGba {
Self::create_with_skip_bios_intro(false)
}
#[wasm_bindgen]
pub fn new_with_skip_bios_intro(skip_bios_intro: bool) -> WasmGba {
Self::create_with_skip_bios_intro(skip_bios_intro)
}
#[wasm_bindgen]
pub fn load_rom(&mut self, rom: &[u8], rom_name: &str) -> Result<(), JsValue> {
self.rom_loaded = false;
match self.gba.load_rom(rom, rom_name) {
Ok(()) => {
self.rom_loaded = true;
self.gba.set_audio_sample_rate(44_100.0);
self.pending_toasts
.push(cartridge_load_toast_message(rom_name, true));
web_sys::console::log_1(&JsValue::from_str("GBA ROM loaded successfully"));
Ok(())
}
Err(err) => {
self.pending_toasts
.push(cartridge_load_toast_message(rom_name, false));
Err(JsValue::from_str(&err))
}
}
}
#[wasm_bindgen]
pub fn drain_toasts(&mut self) -> Vec<JsValue> {
self.pending_toasts.drain(..).map(JsValue::from).collect()
}
#[wasm_bindgen]
pub fn render_frame_rgba(&mut self) -> js_sys::Uint8Array {
if !self.rom_loaded {
self.fill_opaque_black_frame();
return unsafe { js_sys::Uint8Array::view(&self.frame_rgba_buffer) };
}
self.run_until_frame_ready();
self.ensure_rgba_buffer();
let rgb = self.gba.framebuffer_rgb();
for (rgba, rgb) in self
.frame_rgba_buffer
.chunks_exact_mut(4)
.zip(rgb.chunks_exact(3))
{
rgba[0] = rgb[0];
rgba[1] = rgb[1];
rgba[2] = rgb[2];
rgba[3] = 0xFF;
}
unsafe { js_sys::Uint8Array::view(&self.frame_rgba_buffer) }
}
#[wasm_bindgen]
pub fn render_frame_rgb(&mut self) -> js_sys::Uint8Array {
if !self.rom_loaded {
self.fill_black_rgb_frame();
return unsafe { js_sys::Uint8Array::view(&self.frame_rgb_buffer) };
}
self.run_until_frame_ready();
unsafe { js_sys::Uint8Array::view(self.gba.framebuffer_rgb()) }
}
#[wasm_bindgen]
pub fn screen_width(&self) -> u32 {
Gba::SCREEN_WIDTH
}
#[wasm_bindgen]
pub fn screen_height(&self) -> u32 {
Gba::SCREEN_HEIGHT
}
#[wasm_bindgen]
pub fn frame_rate_hz(&self) -> f64 {
16_777_216.0 / 280_896.0
}
#[wasm_bindgen]
pub fn get_audio_samples(&mut self) -> Vec<f32> {
if self.audio_muted {
self.drain_audio_buffer();
return Vec::new();
}
let mut samples = Vec::new();
while let Some(sample) = self.gba.get_sample() {
samples.push(sample);
}
samples
}
#[wasm_bindgen]
pub fn get_audio_samples_stereo(&mut self) -> Vec<f32> {
if self.audio_muted {
self.drain_audio_buffer();
return Vec::new();
}
let mut samples = Vec::new();
while let Some((left, right)) = self.gba.get_stereo_sample() {
samples.push(left);
samples.push(right);
}
samples
}
#[wasm_bindgen]
pub fn set_audio_sample_rate(&mut self, sample_rate: f32) {
self.gba.set_audio_sample_rate(sample_rate);
}
#[wasm_bindgen]
pub fn set_audio_muted(&mut self, muted: bool) {
self.audio_muted = muted;
if muted {
self.drain_audio_buffer();
}
}
#[wasm_bindgen]
pub fn is_audio_muted(&self) -> bool {
self.audio_muted
}
#[wasm_bindgen]
pub fn set_button(&mut self, controller: u8, button: u8, pressed: bool) {
if controller == 1 {
self.gba.set_button(controller, button, pressed);
}
}
#[wasm_bindgen]
pub fn reset(&mut self, soft_reset: bool) {
self.gba.reset(soft_reset);
}
}