Switch back to Asyncify without Emscripten Main Loop callback

This commit is contained in:
2024-04-14 17:31:27 -05:00
parent 0d5ebaa7d9
commit 1bffbaeb3c
6 changed files with 118 additions and 124 deletions

View File

@@ -3,9 +3,9 @@
rustflags = [ rustflags = [
# "-O", "-C", "link-args=-O2 --profiling", # "-O", "-C", "link-args=-O2 --profiling",
#"-C", "link-args=-O3 --closure 1", #"-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=-sALLOW_MEMORY_GROWTH=1", # "-C", "link-args=-sALLOW_MEMORY_GROWTH=1",
"-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS=['png']", "-C", "link-args=-sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sSDL2_IMAGE_FORMATS=['png']",
# USE_OGG, USE_VORBIS for OGG/VORBIS usage # USE_OGG, USE_VORBIS for OGG/VORBIS usage
# "-C", "link-args=--preload-file ./assets/", "-C", "link-args=--preload-file assets/",
] ]

5
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"rust-analyzer.linkedProjects": [
"./Cargo.toml"
]
}

BIN
assets/fruit.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -59,8 +59,8 @@ cp assets/index.html dist
cp $output_folder/pacman.wasm dist cp $output_folder/pacman.wasm dist
cp $output_folder/pacman.js dist cp $output_folder/pacman.js dist
# only if .data file exists # only if .data file exists
if [ -f $output_folder/pacman.data ]; then if [ -f $output_folder/deps/pacman.data ]; then
cp $output_folder/pacman.data dist cp $output_folder/deps/pacman.data dist
fi fi
if [ "$serve" = 'true' ]; then if [ "$serve" = 'true' ]; then

View File

@@ -1,19 +1,9 @@
// 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.
pub mod emscripten {
use std::cell::RefCell; use std::cell::RefCell;
use std::os::raw::c_int; use std::os::raw::{c_float, c_int, c_void};
use std::ptr::null_mut;
// Declare our FFI to the Emscripten functions we need. These will be linked in when building for Emscripten targets.
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
type em_callback_func = unsafe extern "C" fn(); type em_callback_func = unsafe extern "C" fn();
@@ -24,53 +14,31 @@ extern "C" {
simulate_infinite_loop: c_int, simulate_infinite_loop: c_int,
); );
pub fn emscripten_cancel_main_loop(); pub fn emscripten_cancel_main_loop();
pub fn emscripten_get_now() -> c_float;
} }
thread_local! { thread_local!(static MAIN_LOOP_CALLBACK: RefCell<*mut c_void> = RefCell::new(null_mut()));
// 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. pub fn set_main_loop_callback<F>(callback: F)
// RefCell<..> - allows for mutable access from anywhere which we need to store and then terminate. Still borrow-checked in runtime. where
// Option<..> - we don't always have anything scheduled F: FnMut(),
// Box<dyn ...> - make it work generically for any closure passed in {
MAIN_LOOP_CALLBACK.with(|log| {
static MAIN_LOOP_CLOSURE: RefCell<Option<Box<dyn FnMut()>>> = RefCell::new(None); *log.borrow_mut() = &callback as *const _ as *mut c_void;
}
// 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<F: FnMut() + 'static>(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<F: FnMut()>() {
// 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 { unsafe {
emscripten_set_main_loop(wrapper::<F>, 0, 1); emscripten_set_main_loop(wrapper::<F>, 0, 1);
} }
}
// This is used to de-schedule the main loop function and destroy the kept closure object unsafe extern "C" fn wrapper<F>()
pub fn cancel_main_loop() { where
// De-schedule F: FnMut(),
unsafe { {
emscripten_cancel_main_loop(); MAIN_LOOP_CALLBACK.with(|z| {
} let closure = *z.borrow_mut() as *mut F;
(*closure)();
// Remove the stored closure object
MAIN_LOOP_CLOSURE.with(|d| {
*d.borrow_mut() = None;
}); });
} }
}
}

View File

@@ -3,45 +3,51 @@ use std::process;
use std::rc::Rc; use std::rc::Rc;
use sdl2::event::Event; use sdl2::event::Event;
use sdl2::image::LoadTexture;
use sdl2::keyboard::Keycode; use sdl2::keyboard::Keycode;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::Rect; use sdl2::rect::{Point, Rect};
static BLACK: Color = Color::RGB(0, 0, 0); static BLACK: Color = Color::RGB(0, 0, 0);
static WHITE: Color = Color::RGB(255, 255, 255); static WHITE: Color = Color::RGB(255, 255, 255);
// export EMCC_CFLAGS="-s USE_SDL=2"
// cargo build --target asmjs-unknown-emscripten && open index.html
#[cfg(target_family = "wasm")] #[cfg(target_family = "wasm")]
pub mod emscripten; pub mod emscripten;
fn main() { fn main() {
// #[cfg(target_os = "emscripten")]
// sdl2::hint::set("SDL_EMSCRIPTEN_ASYNCIFY","1");
let ctx = sdl2::init().unwrap(); let ctx = sdl2::init().unwrap();
let video_ctx = ctx.video().unwrap(); let video_ctx = ctx.video().unwrap();
let window = match video_ctx let window = match video_ctx
.window("Hello, Rust / SDL2 / WASM!", 640, 480) .window("Hello, Rust / SDL2 / WASM!", 640, 480)
.position_centered() .position_centered()
.opengl() // .opengl()
.build() .build()
{ {
Ok(window) => window, Ok(window) => window,
Err(err) => panic!("failed to create window: {}", err), Err(err) => panic!("failed to create window: {}", err),
}; };
let canvas = match window.into_canvas().present_vsync().build() { let canvas = match window.into_canvas().accelerated().present_vsync().build() {
Ok(canvas) => canvas, Ok(canvas) => canvas,
Err(err) => panic!("failed to create canvas: {}", err), Err(err) => panic!("failed to create canvas: {}", err),
}; };
let rect = Rect::new(0, 0, 10, 10); let texture_creator = canvas.texture_creator();
let mut point = Point::new(0, 0);
let ctx = Rc::new(RefCell::new(ctx)); let ctx = Rc::new(RefCell::new(ctx));
let rect = Rc::new(RefCell::new(rect));
let canvas = Rc::new(RefCell::new(canvas)); let canvas = Rc::new(RefCell::new(canvas));
let texture_creator = Rc::new(texture_creator);
let main_loop = move || { let fruit_atlas = texture_creator
.load_texture("./assets/fruit.png")
.expect("could not load texture");
let mut main_loop = move || {
let mut moved = false; let mut moved = false;
for event in ctx.borrow_mut().event_pump().unwrap().poll_iter() { for event in ctx.borrow_mut().event_pump().unwrap().poll_iter() {
match event { match event {
@@ -53,67 +59,82 @@ fn main() {
process::exit(1); process::exit(1);
} }
Event::KeyDown { Event::KeyDown {
keycode: Some(key), keycode: Some(key), ..
.. } => match key {
} => {
match key {
Keycode::Left => { Keycode::Left => {
rect.borrow_mut().x -= 10; point.x -= 32;
moved = true; moved = true;
} }
Keycode::Right => { Keycode::Right => {
rect.borrow_mut().x += 10; point.x += 32;
moved = true; moved = true;
} }
Keycode::Up => { Keycode::Up => {
rect.borrow_mut().y -= 10; point.y -= 32;
moved = true; moved = true;
} }
Keycode::Down => { Keycode::Down => {
rect.borrow_mut().y += 10; point.y += 32;
moved = true; moved = true;
} }
_ => {} _ => {}
} },
}
_ => {} _ => {}
} }
} }
// Handle wrapping at the edges // Handle wrapping at the edges
if moved { if moved {
let mut rect = rect.borrow_mut();
let canvas_size = canvas.borrow().window().size(); let canvas_size = canvas.borrow().window().size();
if rect.x < 0 { if point.x < 0 {
rect.x = canvas_size.0 as i32 - rect.width() as i32; point.x = canvas_size.0 as i32 - 32;
} else if rect.x >= 640 { } else if point.x >= 640 {
rect.x = 0; point.x = 0;
} }
if rect.y < 0 { if point.y < 0 {
rect.y = canvas_size.1 as i32 - rect.height() as i32; point.y = canvas_size.1 as i32 - 32;
} else if rect.y >= canvas_size.1 as i32 { } else if point.y >= canvas_size.1 as i32 {
rect.y = 0; point.y = 0;
} }
} }
let mut canvas = canvas.borrow_mut(); let mut canvas = canvas.borrow_mut();
canvas.set_draw_color(BLACK); canvas.set_draw_color(BLACK);
canvas.clear(); canvas.clear();
canvas.set_draw_color(WHITE);
canvas.fill_rect(rect.borrow().clone()).expect("could not draw rectangle"); canvas
.copy_ex(
&fruit_atlas,
Rect::new(0, 0, 32, 32),
Rect::new(point.x, point.y, 32, 32),
0.0,
Some(Point::new(0, 0)),
false,
false,
)
.expect("could not draw texture");
canvas.present(); canvas.present();
}; };
#[cfg(target_family = "wasm")] // #[cfg(target_family = "wasm")]
emscripten::set_main_loop_callback(main_loop); // {
// use crate::emscripten::emscripten::set_main_loop_callback;
// set_main_loop_callback(main_loop);
// }
// #[cfg(not(target_family = "wasm"))]
// {
// use std::thread::sleep;
// use std::time::Duration;
// loop {
// main_loop();
// sleep(Duration::from_millis(10))
// }
// }
#[cfg(not(target_family = "wasm"))]
{
use std::thread::sleep;
use std::time::Duration;
loop { loop {
main_loop(); main_loop();
sleep(Duration::from_millis(10))
}
} }
} }