From d0b8ebfff59df564799c5650f5f1bb625b2ec987 Mon Sep 17 00:00:00 2001 From: Xevion Date: Sun, 14 Apr 2024 01:18:31 -0500 Subject: [PATCH] Latest emscripten attempt --- .cargo/config.toml | 6 +- .github/workflows/deploy.yaml | 2 +- Cargo.lock | 8 +-- Cargo.toml | 2 +- assets/index.html | 4 +- scripts/serve-wasm.sh | 8 +++ src/emscripten.rs | 76 +++++++++++++++++++++++ src/lib.rs | 50 +++++++++++++++ src/main.rs | 111 ++++++++++++++++++++-------------- 9 files changed, 209 insertions(+), 58 deletions(-) create mode 100755 scripts/serve-wasm.sh create mode 100644 src/emscripten.rs create mode 100644 src/lib.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 993021f..91cc6cc 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,10 +1,10 @@ [target.wasm32-unknown-emscripten] # TODO: Document what the fuck this is. rustflags = [ - "-O", "-C", "link-args=-O2 --profiling", + # "-O", "-C", "link-args=-O2 --profiling", #"-C", "link-args=-O3 --closure 1", - "-C", "link-args=-sASYNCIFY -sALLOW_MEMORY_GROWTH=1", + # "-C", "link-args=-sASYNCIFY -sALLOW_MEMORY_GROWTH=1", "-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS=['png']", # USE_OGG, USE_VORBIS for OGG/VORBIS usage - "-C", "link-args=--preload-file assets/", + # "-C", "link-args=--preload-file assets/", ] diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index 85915cc..c01786b 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -13,7 +13,7 @@ jobs: - uses: mymindstorm/setup-emsdk@v11 # setup emscripten toolchain with: - version: 1.39.20 + version: 3.1.57 - uses: actions-rs/toolchain@v1 # get rust toolchain for wasm with: diff --git a/Cargo.lock b/Cargo.lock index 8adc90f..5ae1f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -167,9 +167,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "sdl2" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7959277b623f1fb9e04aea73686c3ca52f01b2145f8ea16f4ff30d8b7623b1a" +checksum = "8356b2697d1ead5a34f40bcc3c5d3620205fe0c7be0a14656223bfeec0258891" dependencies = [ "bitflags", "lazy_static", @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "sdl2-sys" -version = "0.35.2" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3586be2cf6c0a8099a79a12b4084357aa9b3e0b0d7980e3b67aaf7a9d55f9f0" +checksum = "26bcacfdd45d539fb5785049feb0038a63931aa896c7763a2a12e125ec58bd29" dependencies = [ "cfg-if", "libc", diff --git a/Cargo.toml b/Cargo.toml index 3cf545c..1941064 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] colors-transform = "0.2.11" lazy_static = "1.4.0" -sdl2 = { version = "0.35", features = ["image", "ttf", "mixer"] } +sdl2 = { version = "0.36", features = ["image", "ttf", "mixer"] } spin_sleep = "1.1.1" tracing = { version = "0.1.37", features = ["max_level_debug", "release_max_level_warn"]} tracing-error = "0.2.0" diff --git a/assets/index.html b/assets/index.html index fccabb5..40e4add 100644 --- a/assets/index.html +++ b/assets/index.html @@ -17,11 +17,11 @@ - - + \ No newline at end of file diff --git a/scripts/serve-wasm.sh b/scripts/serve-wasm.sh new file mode 100755 index 0000000..c070980 --- /dev/null +++ b/scripts/serve-wasm.sh @@ -0,0 +1,8 @@ +#!/bin/sh +set -eu + +echo "Building WASM with Emscripten" +./scripts/build-wasm.sh + +echo "Serving WASM with Emscripten" +python3 -m http.server -d ./dist/ 8080 \ No newline at end of file diff --git a/src/emscripten.rs b/src/emscripten.rs new file mode 100644 index 0000000..949bd40 --- /dev/null +++ b/src/emscripten.rs @@ -0,0 +1,76 @@ +// SOURCE: https://users.rust-lang.org/t/sdl2-emscripten-asmjs-and-invalid-renderer-panic/66567/2 + +// Based on emscripten.rs from https://github.com/therocode/rust_emscripten_main_loop +// This file interacts with the Emscripten API to provide a scheduling mechanism for main looping. + +// Since Emscripten only schedules the looping to be executed later by the browser, we need to make sure that the +// data object looped upon lives as long as the looping is scheduled, as well as being properly destroyed afterwards. + +// The Emscripten function used for this is emscripten_set_main_loop which will do the scheduling as well as terminate the current code flow +// to prevent scopes from being exited which would cause objects to be destroyed prematurely. To be able to destroy the data object properly +// as looping is terminated, the object is stored in thread_local storage. + +use std::cell::RefCell; +use std::os::raw::c_int; + +// Declare our FFI to the Emscripten functions we need. These will be linked in when building for Emscripten targets. +#[allow(non_camel_case_types)] +type em_callback_func = unsafe extern "C" fn(); + +extern "C" { + pub fn emscripten_set_main_loop( + func: em_callback_func, + fps: c_int, + simulate_infinite_loop: c_int, + ); + pub fn emscripten_cancel_main_loop(); +} + +thread_local! { + // This is where the data object will be kept during the scheduled looping. The storage structure is justified as follows + + // thread_local - we need it outside of function scope. thread_local is enough since we only expect interactions from the same thread. + // RefCell<..> - allows for mutable access from anywhere which we need to store and then terminate. Still borrow-checked in runtime. + // Option<..> - we don't always have anything scheduled + // Box - make it work generically for any closure passed in + + static MAIN_LOOP_CLOSURE: RefCell>> = RefCell::new(None); +} + +// Schedules the given callback to be run over and over in a loop until it returns MainLoopEvent::Terminate. +// Retains ownership of the passed callback +pub fn set_main_loop_callback(callback: F) { + // Move the callback into the data storage for safe-keeping + MAIN_LOOP_CLOSURE.with(|d| { + *d.borrow_mut() = Some(Box::new(callback)); + }); + + // Define a wrapper function that is compatible with the emscripten_set_main_loop function. + // This function will take care of extracting and executing our closure. + unsafe extern "C" fn wrapper() { + // Access and run the stashed away closure + MAIN_LOOP_CLOSURE.with(|z| { + if let Some(closure) = &mut *z.borrow_mut() { + (*closure)(); + } + }); + } + + // Schedule the above wrapper function to be called regularly with Emscripten + unsafe { + emscripten_set_main_loop(wrapper::, 0, 1); + } +} + +// This is used to de-schedule the main loop function and destroy the kept closure object +pub fn cancel_main_loop() { + // De-schedule + unsafe { + emscripten_cancel_main_loop(); + } + + // Remove the stored closure object + MAIN_LOOP_CLOSURE.with(|d| { + *d.borrow_mut() = None; + }); +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b1d0724 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,50 @@ +use std::cell::RefCell; +use std::process; +use std::rc::Rc; + +use sdl2::event::Event; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::rect::Rect; +use sdl2::render::WindowCanvas; +use sdl2::Sdl; + +#[cfg(target_family = "wasm")] +pub mod emscripten; + +static BLACK: Color = Color::RGB(0, 0, 0); +static WHITE: Color = Color::RGB(255, 255, 255); + +pub fn main_loop(ctx: Rc>, rect: Rc>, canvas: Rc>) -> impl FnMut() { + let mut events = ctx.borrow_mut().event_pump().unwrap(); + + move || { + for event in events.poll_iter() { + match event { + Event::Quit {..} | Event::KeyDown {keycode: Some(Keycode::Escape), ..} => { + process::exit(1); + }, + Event::KeyDown { keycode: Some(Keycode::Left), ..} => { + rect.borrow_mut().x -= 10; + }, + Event::KeyDown { keycode: Some(Keycode::Right), ..} => { + rect.borrow_mut().x += 10; + }, + Event::KeyDown { keycode: Some(Keycode::Up), ..} => { + rect.borrow_mut().y -= 10; + }, + Event::KeyDown { keycode: Some(Keycode::Down), ..} => { + rect.borrow_mut().y += 10; + }, + _ => {} + } + } + + let _ = canvas.borrow_mut().set_draw_color(BLACK); + let _ = canvas.borrow_mut().clear(); + let _ = canvas.borrow_mut().set_draw_color(WHITE); + let _ = canvas.borrow_mut().fill_rect(rect.borrow().clone()); + let _ = canvas.borrow_mut().present(); + } + +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2d0b031..40f8749 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,9 +4,12 @@ use tracing::event; use tracing_error::ErrorLayer; use tracing_subscriber::layer::SubscriberExt; +#[cfg(target_family = "wasm")] +pub mod emscripten; + pub fn main() { - let sdl_context = sdl2::init().unwrap(); - let video_subsystem = sdl_context.video().unwrap(); + let ctx = sdl2::init().unwrap(); + let video = ctx.video().unwrap(); // Setup tracing let subscriber = tracing_subscriber::fmt() @@ -17,14 +20,16 @@ pub fn main() { tracing::subscriber::set_global_default(subscriber).expect("Could not set global default"); - let window = video_subsystem + let window = video .window("SDL2 Test", 800, 800) .position_centered() + .opengl() .build() .expect("Could not initialize window"); let mut canvas = window .into_canvas() + .present_vsync() .build() .expect("Could not build canvas"); @@ -34,7 +39,7 @@ pub fn main() { // let texture_creator = canvas.texture_creator(); - let mut event_pump = sdl_context + let mut event_pump = ctx .event_pump() .expect("Could not get SDL EventPump"); @@ -44,55 +49,59 @@ pub fn main() { // The start of a period of time over which we average the frame time. let mut sleep_time = Duration::ZERO; - let mut main_loop = || { + let mut main_loop = move || { let start = Instant::now(); // TODO: Fix key repeat delay issues by using VecDeque for instant key repeat - for event in event_pump.poll_iter() { - match event { - Event::Window { win_event, .. } => match win_event { - WindowEvent::Hidden => { - event!(tracing::Level::WARN, "Window hidden"); - shown = false; - } - WindowEvent::Shown => { - event!(tracing::Level::WARN, "Window shown"); - shown = true; - } - _ => {} - }, - // Handle quitting keys or window close - Event::Quit { .. } => { - event!(tracing::Level::INFO, "Exit requested. Exiting..."); - return false; - } - _ => { - event!(tracing::Level::WARN, "Unhandled event: {:?}", event); - } - } - } + // for event in event_pump.poll_iter() { + // match event { + // Event::Window { win_event, .. } => match win_event { + // WindowEvent::Hidden => { + // event!(tracing::Level::WARN, "Window hidden"); + // shown = false; + // } + // WindowEvent::Shown => { + // event!(tracing::Level::WARN, "Window shown"); + // shown = true; + // } + // _ => {} + // }, + // // Handle quitting keys or window close + // Event::KeyDown { + // keycode: Some(sdl2::keyboard::Keycode::Escape), + // .. + // } + // | Event::Quit { .. } => { + // event!(tracing::Level::INFO, "Exit requested. Exiting..."); + // return; + // } + // _ => { + // event!(tracing::Level::WARN, "Unhandled event: {:?}", event); + // } + // } + // } if shown { // Set background to black canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 0, 0)); canvas.clear(); - // Draw a square under the mouse - let mouse_state = event_pump.mouse_state(); - let mouse_x = mouse_state.x(); - let mouse_y = mouse_state.y(); - let square = sdl2::rect::Rect::new(mouse_x - 25, mouse_y - 25, 50, 50); + // // Draw a square under the mouse + // let mouse_state = event_pump.mouse_state(); + // let mouse_x = mouse_state.x(); + // let mouse_y = mouse_state.y(); + // let square = sdl2::rect::Rect::new(mouse_x - 25, mouse_y - 25, 50, 50); - canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 255, 255)); - canvas.fill_rect(square).unwrap(); + // canvas.set_draw_color(sdl2::pixels::Color::RGB(0, 255, 255)); + // canvas.fill_rect(square).unwrap(); - canvas.set_draw_color(sdl2::pixels::Color::RGBA(255, 255, 255, 50)); - canvas - .draw_line( - sdl2::rect::Point::new(0, 0), - sdl2::rect::Point::new(mouse_x, mouse_y), - ) - .unwrap(); + // canvas.set_draw_color(sdl2::pixels::Color::RGBA(255, 255, 255, 50)); + // canvas + // .draw_line( + // sdl2::rect::Point::new(0, 0), + // sdl2::rect::Point::new(mouse_x, mouse_y), + // ) + // .unwrap(); canvas.present(); @@ -124,13 +133,21 @@ pub fn main() { ); } } - - true }; - loop { - if !main_loop() { - break; + #[cfg(target_family = "wasm")] + use crate::emscripten; + + #[cfg(target_family = "wasm")] + emscripten::set_main_loop_callback(main_loop); + + #[cfg(not(target_family = "wasm"))] + { + use std::thread::sleep; + loop { + // main_loop(Rc::clone(&ctx), Rc::clone(&rect), Rc::clone(&canvas))(); + main_loop(); + sleep(Duration::from_millis(10)) } } }