#![cfg(target_arch = "wasm32")] use std::rc::Rc; use std::cell::RefCell; use instant::Instant; use wasm_bindgen::prelude::*; use winit::dpi::LogicalSize; use winit::event_loop::EventLoop; use winit::window::{Window, WindowBuilder}; use web_sys::Event; use wasm_bindgen::JsCast; use wasm_bindgen::closure::Closure; use moa_core::{ClockDuration, System, Device}; use moa_core::host::{ControllerInput, ControllerDevice, ControllerEvent, EventSender}; use crate::settings; use crate::frontend::{self, PixelsFrontend, LoadSystemFn}; pub fn start(load: LoadSystemFn) { settings::set_rom_data(include_bytes!("../sonic.bin").to_vec()); std::panic::set_hook(Box::new(console_error_panic_hook::hook)); console_log::init_with_level(log::Level::Warn).expect("error initializing logger"); //wasm_bindgen_futures::spawn_local(frontend::run(load)); } #[wasm_bindgen] pub fn set_rom_data(rom_data: Vec) { settings::set_rom_data(rom_data); } #[wasm_bindgen] pub fn set_size(width: u32, height: u32) { settings::set_size(width, height); } #[wasm_bindgen] pub fn request_stop() { settings::request_stop(); } #[wasm_bindgen] pub fn toggle_run() { settings::toggle_run(); } #[wasm_bindgen] pub fn is_running() -> bool { settings::get().run } #[wasm_bindgen] pub fn set_speed(speed: f32) { //settings::get().speed = speed; } #[wasm_bindgen] pub fn get_speed() -> f32 { settings::get().speed } #[wasm_bindgen] pub fn get_frames_since() -> usize { settings::get_frames_since() } #[wasm_bindgen] pub fn set_mute(mute: bool) { settings::get().mute = mute; } #[wasm_bindgen] pub fn button_press(handle: &ControllersHandle, name: String, state: bool) { let input = match name.as_str() { "up" => Some(ControllerInput::DpadUp(state)), "down" => Some(ControllerInput::DpadDown(state)), "left" => Some(ControllerInput::DpadLeft(state)), "right" => Some(ControllerInput::DpadRight(state)), "start" => Some(ControllerInput::Start(state)), "a" => Some(ControllerInput::ButtonA(state)), "b" => Some(ControllerInput::ButtonB(state)), "c" => Some(ControllerInput::ButtonC(state)), _ => None }; if let Some(input) = input { handle.0.send(ControllerEvent::new(ControllerDevice::A, input)); } } #[wasm_bindgen] pub struct HostHandle(PixelsFrontend); #[wasm_bindgen] pub struct ControllersHandle(EventSender); #[wasm_bindgen] pub fn new_host() -> HostHandle { HostHandle(PixelsFrontend::new()) } #[wasm_bindgen] pub fn get_controllers(handle: &HostHandle) -> ControllersHandle { ControllersHandle(handle.0.get_controllers().unwrap()) } #[wasm_bindgen] pub fn host_run_loop(handle: HostHandle) { wasm_bindgen_futures::spawn_local(frontend::run_loop(handle.0)); } #[wasm_bindgen] pub struct SystemHandle(System); #[wasm_bindgen] pub struct LoadSystemFnHandle(LoadSystemFn); impl LoadSystemFnHandle { pub fn new(load: LoadSystemFn) -> Self { Self(load) } } #[wasm_bindgen] pub fn load_system(handle: &mut HostHandle, load: LoadSystemFnHandle) -> SystemHandle { let mut system = load.0(&mut handle.0, settings::get().rom_data.clone()).unwrap(); let mixer = handle.0.get_mixer(); if mixer.borrow_mut().num_sources() > 0 { system.add_device("mixer", Device::new(mixer.clone())).unwrap(); } SystemHandle(system) } #[wasm_bindgen] pub fn run_system_for(handle: &mut SystemHandle, nanos: u32) -> usize { let run_timer = Instant::now(); let nanoseconds_per_frame = ClockDuration::from_nanos(nanos as u64); //let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock; if let Err(err) = handle.0.run_for(nanoseconds_per_frame) { log::error!("{:?}", err); } let run_time = run_timer.elapsed().as_millis(); log::debug!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_time); run_time as usize } pub fn create_window(event_loop: &EventLoop) -> Rc { use web_sys::HtmlCanvasElement; use winit::platform::web::{WindowExtWebSys, WindowBuilderExtWebSys}; let canvas = web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.get_element_by_id("video")) .and_then(|el| el.dyn_into::().ok()) .expect("document to have canvas"); let window = { let size = LogicalSize::new(frontend::WIDTH as f64, frontend::HEIGHT as f64); WindowBuilder::new() .with_canvas(Some(canvas)) .with_title("Moa Emulator") //.with_inner_size(size) //.with_min_inner_size(size) .build(event_loop) .expect("WindowBuilder error") }; let window = Rc::new(window); /* // Retrieve current width and height dimensions of browser client window let get_window_size = || { let client_window = web_sys::window().unwrap(); LogicalSize::new( client_window.inner_width().unwrap().as_f64().unwrap(), client_window.inner_height().unwrap().as_f64().unwrap(), ) }; // Initialize winit window with current dimensions of browser client window.set_inner_size(get_window_size()); */ let client_window = web_sys::window().unwrap(); /* // Attach winit canvas to body element web_sys::window() .and_then(|win| win.document()) .and_then(|doc| doc.get_element_by_id("video-screen")) .and_then(|el| { while let Some(child) = el.first_child() { el.remove_child(&child); } el.append_child(&web_sys::Element::from(window.canvas())) .ok() }) .expect("couldn't append canvas to document body"); */ /* { let window = window.clone(); // Listen for resize event on browser client. Adjust winit window dimensions // on event trigger let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| { let size = get_window_size(); window.set_inner_size(size) }) as Box); client_window .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) .unwrap(); closure.forget(); } */ window } #[wasm_bindgen] pub fn start_system(handle: SystemHandle) -> Handle { let emulator = Emulator::new(handle.0); set_timeout(emulator.clone(), 0); Handle(emulator) } #[wasm_bindgen] pub struct Handle(Rc>); pub struct Emulator { running: bool, //frontend: PixelsFrontend, system: System, last_update: Instant, } impl Emulator { pub fn new(system: System) -> Rc> { Rc::new(RefCell::new(Self { running: false, system, last_update: Instant::now(), })) } pub fn stop(&mut self) { self.running = false; } } /// This updater tries to work like the JS one, by simulating the amount of time that has passed /// since the last update, but it doesn't work too well yet fn update(emulator: Rc>) { let run_timer = Instant::now(); let last_update = { let mut emulator = emulator.borrow_mut(); let last_update = emulator.last_update; emulator.last_update = run_timer; last_update }; let diff = run_timer.duration_since(last_update); let nanoseconds_per_frame = ClockDuration::from_nanos(diff.as_nanos() as u64); //let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock; if let Err(err) = emulator.borrow_mut().system.run_for(nanoseconds_per_frame) { log::error!("{:?}", err); } let run_time = run_timer.elapsed().as_millis(); log::debug!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_time); let running = emulator.borrow().running; if running { let remaining = (diff.as_millis() - run_time - (diff.as_millis() / 10)).max(1); set_timeout(emulator, 16); } } /* /// This is the constant-time updater which always tries to simulate one frame fn update(emulator: Rc>) { let run_timer = Instant::now(); let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as u64; if let Err(err) = emulator.borrow_mut().system.run_for(ClockDuration::from_nanos(nanoseconds_per_frame)) { log::error!("{:?}", err); } log::info!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_timer.elapsed().as_millis()); let running = emulator.borrow().running; if running { set_timeout(emulator, 17); } } */ fn set_timeout(emulator: Rc>, timeout: i32) { emulator.borrow_mut().running = true; let closure: Closure = Closure::new(move |_e: Event| { update(emulator.clone()); }); let client_window = web_sys::window().unwrap(); client_window .set_timeout_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), timeout) .unwrap(); closure.forget(); }