mirror of
https://github.com/transistorfet/moa.git
synced 2024-06-07 18:29:32 +00:00
Refactored pixels web frontend to separate sim run
It will now run the sim on a separate loop powered by setTimeout instead of trying to do it inline with the frame updating
This commit is contained in:
parent
0b27ac04e7
commit
d26e80ffaa
|
@ -3,37 +3,16 @@
|
|||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #000;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#config {
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
#metrics {
|
||||
float: right;
|
||||
color: #DDD;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#metrics input {
|
||||
width: 2em;
|
||||
color: #DDD;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
</style>
|
||||
<title>Hello Pixels + Web</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<title>Sega Genesis - Moa</title>
|
||||
<script type="module" src="./interface.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="config">
|
||||
<label>ROM File (only .bin format)</label>
|
||||
<input type="file" id="rom-file" accept=".bin,.smd,.md" />
|
||||
<input type="button" id="reset" value="Reset" />
|
||||
<input type="button" id="power" value="Power" />
|
||||
<input type="text" id="speed" value="4.0" />
|
||||
</div>
|
||||
|
||||
|
@ -42,74 +21,8 @@
|
|||
<input type="text" id="frame-rate" disabled />
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
import init from "./moa-genesis.js";
|
||||
import {
|
||||
set_rom_data,
|
||||
request_reset,
|
||||
get_frames_since,
|
||||
smd_to_bin,
|
||||
set_speed,
|
||||
} from "./moa-genesis.js";
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function (e) {
|
||||
var data = new Uint8Array(reader.result);
|
||||
// If the SMD file magic number is present, then convert it before loading
|
||||
if (data[8] == 0xAA && data[9] == 0xBB)
|
||||
data = smd_to_bin(data);
|
||||
set_rom_data(data);
|
||||
};
|
||||
|
||||
var file_input = document.getElementById("rom-file");
|
||||
file_input.addEventListener("change", e => {
|
||||
reader.readAsArrayBuffer(file_input.files[0])
|
||||
});
|
||||
|
||||
document.getElementById("reset").addEventListener("click", () => {
|
||||
request_reset();
|
||||
});
|
||||
|
||||
document.getElementById("speed").addEventListener("change", (e) => {
|
||||
console.log(e.target.value);
|
||||
set_speed(e.target.value);
|
||||
});
|
||||
|
||||
var file_input = document.getElementById("rom-file");
|
||||
var frame_rate_el = document.getElementById("frame-rate");
|
||||
var frame_rate = setInterval(function () {
|
||||
frame_rate_el.value = get_frames_since();
|
||||
}, 1000);
|
||||
|
||||
/*
|
||||
var last_update = performance.now();
|
||||
window.requestIdleCallback(function () {
|
||||
var current = performance.now();
|
||||
var diff = current - last_update;
|
||||
last_update = current;
|
||||
|
||||
try {
|
||||
run_system_for(diff * 1000000);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}, { timeout: 100 });
|
||||
|
||||
setInterval(function () {
|
||||
run_system_for(66_000_000);
|
||||
}, 66);
|
||||
*/
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
init();
|
||||
});
|
||||
|
||||
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
||||
|
||||
const sample_size = audioContext.sampleRate / 100;
|
||||
const audioBuffer = audioContext.createBuffer(2, sample_size, audioContext.sampleRate);
|
||||
|
||||
|
||||
</script>
|
||||
<div id="video-screen">
|
||||
<canvas id="video" tabindex="0" draw-raw-handle="1" style="width: 640px; height: 448px;" width="640" height="448" />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
69
emulator/frontends/pixels/assets/moa-genesis/interface.js
Normal file
69
emulator/frontends/pixels/assets/moa-genesis/interface.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
|
||||
import * as Emulator from './moa-genesis.js';
|
||||
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = function (e) {
|
||||
var data = new Uint8Array(reader.result);
|
||||
// If the SMD file magic number is present, then convert it before loading
|
||||
if (data[8] == 0xAA && data[9] == 0xBB)
|
||||
data = Emulator.smd_to_bin(data);
|
||||
Emulator.set_rom_data(data);
|
||||
};
|
||||
|
||||
var file_input = document.getElementById("rom-file");
|
||||
file_input.addEventListener("change", e => {
|
||||
reader.readAsArrayBuffer(file_input.files[0])
|
||||
});
|
||||
|
||||
function initialize_emulator() {
|
||||
let host = Emulator.new_host();
|
||||
let system = Emulator.load_system(host, Emulator.get_load_system_fn());
|
||||
|
||||
let last_update = performance.now();
|
||||
setTimeout(function refreshFrame() {
|
||||
let current = performance.now();
|
||||
let diff = Math.min(current - last_update, 100);
|
||||
let remaining = Math.max((16 * Emulator.get_speed()) - diff, 0);
|
||||
//console.log(diff, remaining);
|
||||
last_update = current;
|
||||
|
||||
Emulator.run_system_for(system, diff * 1_000_000);
|
||||
if (Emulator.is_running()) {
|
||||
setTimeout(refreshFrame, remaining);
|
||||
}
|
||||
}, 0);
|
||||
/*
|
||||
setTimeout(function refreshFrame() {
|
||||
let run_time = run_system_for(system, 66_000_000);
|
||||
setTimeout(refreshFrame, 66 - run_time);
|
||||
}, 0);
|
||||
*/
|
||||
|
||||
Emulator.host_run_loop(host);
|
||||
}
|
||||
|
||||
document.getElementById("reset").addEventListener("click", () => {
|
||||
Emulator.request_stop();
|
||||
//start();
|
||||
});
|
||||
|
||||
document.getElementById("power").addEventListener("click", () => {
|
||||
if (Emulator.is_running())
|
||||
Emulator.request_stop();
|
||||
else
|
||||
initialize_emulator();
|
||||
});
|
||||
|
||||
document.getElementById("speed").addEventListener("change", (e) => {
|
||||
Emulator.set_speed(e.target.value);
|
||||
});
|
||||
|
||||
var file_input = document.getElementById("rom-file");
|
||||
var frame_rate_el = document.getElementById("frame-rate");
|
||||
var frame_rate = setInterval(function () {
|
||||
frame_rate_el.value = Emulator.get_frames_since();
|
||||
}, 1000);
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
Emulator.default();
|
||||
});
|
21
emulator/frontends/pixels/assets/moa-genesis/style.css
Normal file
21
emulator/frontends/pixels/assets/moa-genesis/style.css
Normal file
|
@ -0,0 +1,21 @@
|
|||
body {
|
||||
background-color: #000;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#config {
|
||||
background-color: #888;
|
||||
}
|
||||
|
||||
#metrics {
|
||||
float: right;
|
||||
color: #DDD;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#metrics input {
|
||||
width: 2em;
|
||||
color: #DDD;
|
||||
background-color: #000;
|
||||
}
|
|
@ -18,10 +18,18 @@ fn main() {
|
|||
mod web {
|
||||
use wasm_bindgen::prelude::*;
|
||||
use moa_genesis::utils;
|
||||
use moa_pixels::LoadSystemFnHandle;
|
||||
|
||||
use super::load_system;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn smd_to_bin(input: Vec<u8>) -> Vec<u8> {
|
||||
utils::smd_to_bin(input).unwrap()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_load_system_fn() -> LoadSystemFnHandle {
|
||||
LoadSystemFnHandle::new(load_system)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,41 @@
|
|||
|
||||
use std::rc::Rc;
|
||||
|
||||
use instant::Instant;
|
||||
use pixels::{Pixels, SurfaceTexture};
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event::{Event, VirtualKeyCode, WindowEvent, ElementState};
|
||||
use winit::event_loop::{ControlFlow, EventLoop};
|
||||
use winit::window::WindowBuilder;
|
||||
use instant::Instant;
|
||||
|
||||
use moa_core::{System, Error, Clock};
|
||||
use moa_core::{System, Error};
|
||||
use moa_core::host::{Host, WindowUpdater, ControllerDevice, ControllerEvent, ControllerUpdater, Audio, DummyAudio};
|
||||
use moa_core::host::gfx::Frame;
|
||||
use moa_common::audio::{AudioMixer, AudioSource, CpalAudioOutput};
|
||||
|
||||
use crate::settings;
|
||||
use crate::create_window;
|
||||
|
||||
const WIDTH: u32 = 320;
|
||||
const HEIGHT: u32 = 224;
|
||||
|
||||
pub const WIDTH: u32 = 320;
|
||||
pub const HEIGHT: u32 = 224;
|
||||
|
||||
pub type LoadSystemFn = fn (&mut PixelsFrontend, Vec<u8>) -> Result<System, Error>;
|
||||
|
||||
pub struct PixelsFrontend {
|
||||
updater: Option<Box<dyn WindowUpdater>>,
|
||||
controller: Option<Box<dyn ControllerUpdater>>,
|
||||
//mixer: Arc<Mutex<AudioMixer>>,
|
||||
//audio_output: CpalAudioOutput,
|
||||
}
|
||||
|
||||
impl PixelsFrontend {
|
||||
pub fn new() -> PixelsFrontend {
|
||||
settings::get().run = true;
|
||||
//let mixer = AudioMixer::with_default_rate();
|
||||
//let audio_output = CpalAudioOutput::create_audio_output(mixer.lock().unwrap().get_sink());
|
||||
|
||||
PixelsFrontend {
|
||||
controller: None,
|
||||
updater: None,
|
||||
//mixer,
|
||||
//audio_output,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -49,102 +56,17 @@ impl Host for PixelsFrontend {
|
|||
}
|
||||
|
||||
fn create_audio_source(&mut self) -> Result<Box<dyn Audio>, Error> {
|
||||
//let source = AudioSource::new(self.mixer.clone());
|
||||
//Ok(Box::new(source))
|
||||
Ok(Box::new(DummyAudio()))
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run(load: LoadSystemFn) {
|
||||
loop {
|
||||
let host = PixelsFrontend::new();
|
||||
run_loop(host, load).await
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn run_loop(mut host: PixelsFrontend, load: LoadSystemFn) {
|
||||
pub async fn run_loop(mut host: PixelsFrontend) {
|
||||
let event_loop = EventLoop::new();
|
||||
let window = {
|
||||
let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64);
|
||||
WindowBuilder::new()
|
||||
.with_title("Hello Pixels + Web")
|
||||
.with_inner_size(size)
|
||||
.with_min_inner_size(size)
|
||||
.build(&event_loop)
|
||||
.expect("WindowBuilder error")
|
||||
};
|
||||
|
||||
let window = Rc::new(window);
|
||||
let window = create_window(&event_loop);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
use wasm_bindgen::JsCast;
|
||||
use winit::platform::web::WindowExtWebSys;
|
||||
|
||||
// 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(),
|
||||
)
|
||||
};
|
||||
|
||||
let window = Rc::clone(&window);
|
||||
|
||||
// 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.body())
|
||||
.and_then(|body| {
|
||||
body.append_child(&web_sys::Element::from(window.canvas()))
|
||||
.ok()
|
||||
})
|
||||
.expect("couldn't append canvas to document body");
|
||||
|
||||
// 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<dyn FnMut(_)>);
|
||||
client_window
|
||||
.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
|
||||
/*
|
||||
let host = host.clone();
|
||||
let mut system = load(&mut host.lock().unwrap(), settings::get().rom_data.clone()).unwrap();
|
||||
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| {
|
||||
let run_timer = Instant::now();
|
||||
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock;
|
||||
if let Err(err) = system.run_for(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 mut settings = settings::get();
|
||||
if settings.reset {
|
||||
settings.reset = false;
|
||||
|
||||
match load(&mut host.lock().unwrap(), settings.rom_data.clone()) {
|
||||
Ok(s) => { system = s; },
|
||||
Err(err) => log::error!("{:?}", err),
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
client_window
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17)
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
*/
|
||||
}
|
||||
|
||||
//let mut input = WinitInputHelper::new();
|
||||
let mut pixels = {
|
||||
let window_size = window.inner_size();
|
||||
let surface_texture =
|
||||
|
@ -154,46 +76,43 @@ pub async fn run_loop(mut host: PixelsFrontend, load: LoadSystemFn) {
|
|||
.expect("Pixels error")
|
||||
};
|
||||
|
||||
let mut last_size = (WIDTH, HEIGHT);
|
||||
let mut last_frame = Frame::new(WIDTH, HEIGHT);
|
||||
let mut update_timer = Instant::now();
|
||||
let mut system = load(&mut host, settings::get().rom_data.clone()).unwrap();
|
||||
//let mut update_timer = Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
|
||||
// Draw the current frame
|
||||
if let Event::RedrawRequested(_) = event {
|
||||
settings::increment_frames();
|
||||
log::info!("updated after {:4}ms", update_timer.elapsed().as_millis());
|
||||
update_timer = Instant::now();
|
||||
|
||||
let run_timer = Instant::now();
|
||||
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock;
|
||||
if let Err(err) = system.run_for(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());
|
||||
//log::warn!("updated after {:4}ms", update_timer.elapsed().as_millis());
|
||||
//update_timer = Instant::now();
|
||||
|
||||
if let Some(updater) = host.updater.as_mut() {
|
||||
let buffer = pixels.get_frame();
|
||||
if let Ok(frame) = updater.take_frame() {
|
||||
last_frame = frame;
|
||||
}
|
||||
|
||||
for y in 0..last_frame.height {
|
||||
for x in 0..last_frame.width {
|
||||
let pixel = last_frame.bitmap[((y * last_frame.width) + x) as usize];
|
||||
|
||||
let i = ((y * WIDTH) + x) as usize;
|
||||
buffer[i * 4] = (pixel >> 16) as u8;
|
||||
buffer[i * 4 + 1] = (pixel >> 8) as u8;
|
||||
buffer[i * 4 + 2] = pixel as u8;
|
||||
buffer[i * 4 + 3] = 255;
|
||||
}
|
||||
if (last_frame.width, last_frame.height) != last_size {
|
||||
last_size = (last_frame.width, last_frame.height);
|
||||
pixels.resize_buffer(last_frame.width, last_frame.height);
|
||||
}
|
||||
|
||||
let buffer = pixels.get_frame();
|
||||
buffer
|
||||
.chunks_mut(4)
|
||||
.zip(last_frame.bitmap.iter())
|
||||
.for_each(|(dest, pixel)| {
|
||||
dest[0] = (pixel >> 16) as u8;
|
||||
dest[1] = (pixel >> 8) as u8;
|
||||
dest[2] = *pixel as u8;
|
||||
dest[3] = 255;
|
||||
});
|
||||
}
|
||||
|
||||
if pixels
|
||||
.render()
|
||||
.map_err(|e| println!("pixels.render() failed: {}", e))
|
||||
.map_err(|e| log::error!("pixels.render() failed: {}", e))
|
||||
.is_err()
|
||||
{
|
||||
*control_flow = ControlFlow::Exit;
|
||||
|
@ -203,34 +122,44 @@ pub async fn run_loop(mut host: PixelsFrontend, load: LoadSystemFn) {
|
|||
window.request_redraw();
|
||||
}
|
||||
|
||||
let mut key = None;
|
||||
// Process key inputs and pass them to the emulator's controller device
|
||||
if let Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. } = event {
|
||||
if let Some(keycode) = input.virtual_keycode {
|
||||
match input.state {
|
||||
let key = match input.state {
|
||||
ElementState::Pressed => {
|
||||
key = map_controller_a(keycode, true);
|
||||
map_controller_a(keycode, true)
|
||||
}
|
||||
ElementState::Released => {
|
||||
key = map_controller_a(keycode, false);
|
||||
map_controller_a(keycode, false)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(updater) = host.controller.as_mut() {
|
||||
if let Some(key) = key {
|
||||
updater.update_controller(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(updater) = host.controller.as_mut() {
|
||||
if let Some(key) = key {
|
||||
updater.update_controller(key);
|
||||
}
|
||||
}
|
||||
// Check if the run flag is no longer true, and exit the loop
|
||||
if !settings::get().run {
|
||||
// Clear the screen
|
||||
let buffer = pixels.get_frame();
|
||||
buffer.iter_mut().for_each(|byte| *byte = 0);
|
||||
|
||||
let mut settings = settings::get();
|
||||
if settings.reset {
|
||||
settings.reset = false;
|
||||
|
||||
match load(&mut host, settings.rom_data.clone()) {
|
||||
Ok(s) => { system = s; },
|
||||
Err(err) => log::error!("{:?}", err),
|
||||
if pixels
|
||||
.render()
|
||||
.map_err(|e| log::error!("pixels.render() failed: {}", e))
|
||||
.is_err()
|
||||
{
|
||||
*control_flow = ControlFlow::Exit;
|
||||
return;
|
||||
}
|
||||
|
||||
window.request_redraw();
|
||||
|
||||
*control_flow = ControlFlow::Exit;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -238,8 +167,8 @@ pub async fn run_loop(mut host: PixelsFrontend, load: LoadSystemFn) {
|
|||
pub fn map_controller_a(key: VirtualKeyCode, state: bool) -> Option<ControllerEvent> {
|
||||
match key {
|
||||
VirtualKeyCode::A => { Some(ControllerEvent::ButtonA(state)) },
|
||||
VirtualKeyCode::O => { Some(ControllerEvent::ButtonB(state)) },
|
||||
VirtualKeyCode::E => { Some(ControllerEvent::ButtonC(state)) },
|
||||
VirtualKeyCode::S => { Some(ControllerEvent::ButtonB(state)) },
|
||||
VirtualKeyCode::D => { Some(ControllerEvent::ButtonC(state)) },
|
||||
VirtualKeyCode::Up => { Some(ControllerEvent::DpadUp(state)) },
|
||||
VirtualKeyCode::Down => { Some(ControllerEvent::DpadDown(state)) },
|
||||
VirtualKeyCode::Left => { Some(ControllerEvent::DpadLeft(state)) },
|
||||
|
|
|
@ -6,10 +6,10 @@ pub use crate::frontend::{PixelsFrontend, LoadSystemFn};
|
|||
#[cfg(target_arch = "wasm32")]
|
||||
pub mod web;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub use crate::web::{start};
|
||||
pub use crate::web::{start, create_window, LoadSystemFnHandle};
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub mod native;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub use crate::native::{start};
|
||||
pub use crate::native::{start, create_window};
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
#![cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
use std::rc::Rc;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::{Window, WindowBuilder};
|
||||
|
||||
use crate::frontend::{self, LoadSystemFn};
|
||||
|
||||
pub fn start(load: LoadSystemFn) {
|
||||
|
@ -8,3 +13,16 @@ pub fn start(load: LoadSystemFn) {
|
|||
pollster::block_on(frontend::run(load));
|
||||
}
|
||||
|
||||
pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
||||
let size = LogicalSize::new(frontend::WIDTH as f64, frontend::HEIGHT as f64);
|
||||
let window = WindowBuilder::new()
|
||||
.with_title("Hello Pixels + Web")
|
||||
.with_inner_size(size)
|
||||
.with_min_inner_size(size)
|
||||
.build(event_loop)
|
||||
.expect("WindowBuilder error");
|
||||
|
||||
let window = Rc::new(window);
|
||||
window
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ static EMULATOR_OPTIONS: Mutex<EmulatorSettings> = Mutex::new(EmulatorSettings::
|
|||
pub struct EmulatorSettings {
|
||||
pub rom_data: Vec<u8>,
|
||||
pub run: bool,
|
||||
pub reset: bool,
|
||||
pub speed: f32,
|
||||
pub frames_since: usize,
|
||||
}
|
||||
|
@ -15,8 +14,7 @@ impl EmulatorSettings {
|
|||
const fn new() -> Self {
|
||||
Self {
|
||||
rom_data: vec![],
|
||||
run: true,
|
||||
reset: false,
|
||||
run: false,
|
||||
speed: 4.0,
|
||||
frames_since: 0,
|
||||
}
|
||||
|
@ -42,8 +40,8 @@ pub fn increment_frames() {
|
|||
get().frames_since += 1;
|
||||
}
|
||||
|
||||
pub fn request_reset() {
|
||||
get().reset = true;
|
||||
pub fn request_stop() {
|
||||
get().run = false;
|
||||
}
|
||||
|
||||
pub fn toggle_run() {
|
||||
|
@ -51,7 +49,3 @@ pub fn toggle_run() {
|
|||
options.run = !options.run;
|
||||
}
|
||||
|
||||
pub fn set_speed(speed: f32) {
|
||||
get().speed = speed;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,24 @@
|
|||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
use std::rc::Rc;
|
||||
use instant::Instant;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use winit::dpi::LogicalSize;
|
||||
use winit::event_loop::EventLoop;
|
||||
use winit::window::{Window, WindowBuilder};
|
||||
|
||||
use moa_core::{Clock, System};
|
||||
|
||||
use crate::settings;
|
||||
use crate::frontend::{self, LoadSystemFn};
|
||||
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::Info).expect("error initializing logger");
|
||||
console_log::init_with_level(log::Level::Warn).expect("error initializing logger");
|
||||
|
||||
wasm_bindgen_futures::spawn_local(frontend::run(load));
|
||||
//wasm_bindgen_futures::spawn_local(frontend::run(load));
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -20,8 +27,8 @@ pub fn set_rom_data(rom_data: Vec<u8>) {
|
|||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn request_reset() {
|
||||
settings::request_reset();
|
||||
pub fn request_stop() {
|
||||
settings::request_stop();
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -29,9 +36,19 @@ 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::set_speed(speed);
|
||||
settings::get().speed = speed;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn get_speed() -> f32 {
|
||||
settings::get().speed
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -39,3 +56,144 @@ pub fn get_frames_since() -> usize {
|
|||
settings::get_frames_since()
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct HostHandle(PixelsFrontend);
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn new_host() -> HostHandle {
|
||||
HostHandle(PixelsFrontend::new())
|
||||
}
|
||||
|
||||
#[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 system = load.0(&mut handle.0, settings::get().rom_data.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 = nanos as Clock;
|
||||
//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::warn!("ran simulation for {:?}ms in {:?}ms", nanoseconds_per_frame / 1_000_000, run_time);
|
||||
run_time as usize
|
||||
}
|
||||
|
||||
pub fn create_window<T>(event_loop: &EventLoop<T>) -> Rc<Window> {
|
||||
use web_sys::HtmlCanvasElement;
|
||||
use wasm_bindgen::JsCast;
|
||||
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::<web_sys::HtmlCanvasElement>().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("Hello Pixels + Web")
|
||||
.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(),
|
||||
)
|
||||
};
|
||||
|
||||
let window = Rc::clone(&window);
|
||||
|
||||
// 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<dyn FnMut(_)>);
|
||||
client_window
|
||||
.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
}
|
||||
|
||||
/*
|
||||
let mut update_timer = Instant::now();
|
||||
let mut system = load(&mut host, settings::get().rom_data.clone()).unwrap();
|
||||
let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_e: web_sys::Event| {
|
||||
let run_timer = Instant::now();
|
||||
let nanoseconds_per_frame = (16_600_000 as f32 * settings::get().speed) as Clock;
|
||||
if let Err(err) = system.run_for(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 settings = settings::get();
|
||||
if settings.run {
|
||||
//match load(&mut host.lock().unwrap(), settings.rom_data.clone()) {
|
||||
// Ok(s) => { system = s; },
|
||||
// Err(err) => log::error!("{:?}", err),
|
||||
//}
|
||||
}
|
||||
}) as Box<dyn FnMut(_)>);
|
||||
client_window
|
||||
.set_interval_with_callback_and_timeout_and_arguments_0(closure.as_ref().unchecked_ref(), 17)
|
||||
.unwrap();
|
||||
closure.forget();
|
||||
*/
|
||||
|
||||
window
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user