I've solved this problem. It turns out it was probably an overly "unsafe" version of the "emscripten.rs" wrapper that called the "emscripten_set_main_loop()". This didn't allow me to see that I probably needed a "move" for the closure. When switching to a slightly different "emscripten.rs" wrapper, this became apparent. So here's the better "emscripten.rs":
// 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.
#[cfg(target_os = "emscripten")]
pub mod emscripten {
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<dyn ...> - make it work generically for any closure passed in
static MAIN_LOOP_CLOSURE: RefCell<Option<Box<dyn FnMut()>>> = 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<F: 'static>(callback: F)
where
F: FnMut(),
{
// 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>()
where
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 {
emscripten_set_main_loop(wrapper::<F>, 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;
});
}
}
...and here is the update "main.rs" that has "move" added before the main_loop closure:
//Original from: https://puddleofcode.com/story/definitive-guide-to-rust-sdl2-and-emscriptem
extern crate sdl2;
use std::process;
use sdl2::rect::{Rect};
use sdl2::event::{Event};
use sdl2::keyboard::Keycode;
#[cfg(target_os = "emscripten")]
pub mod emscripten;
fn main() {
let ctx = sdl2::init().unwrap();
let video_ctx = ctx.video().unwrap();
let window = match video_ctx
.window("updating SDL2 example with rust and asmjs...", 640, 480)
.position_centered()
//.opengl()
.build() {
Ok(window) => window,
Err(err) => panic!("failed to create window: {}", err)
};
let mut renderer = match window
// replace "renderer()" with "into_canvas()" since the "renderer()" method no
// longer seems available in the upgrade from rust-SDL2 v0.29.0 to v0.35.0.
// Compiling with v0.29.0 is apparently incompatible with my recent
// version of emscripten (2.0.31).
//.renderer()
.into_canvas()
.build() {
Ok(renderer) => renderer,
Err(err) => panic!("failed to create renderer: {}", err)
};
let mut rect = Rect::new(10, 10, 10, 10);
let black = sdl2::pixels::Color::RGB(0, 0, 0);
let white = sdl2::pixels::Color::RGB(255, 255, 255);
let mut events = ctx.event_pump().unwrap();
let mut main_loop = 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.x -= 10;
},
Event::KeyDown { keycode: Some(Keycode::Right), ..} => {
rect.x += 10;
},
Event::KeyDown { keycode: Some(Keycode::Up), ..} => {
rect.y -= 10;
},
Event::KeyDown { keycode: Some(Keycode::Down), ..} => {
rect.y += 10;
},
_ => {}
}
}
let _ = renderer.set_draw_color(black);
let _ = renderer.clear();
let _ = renderer.set_draw_color(white);
let _ = renderer.fill_rect(rect);
let _ = renderer.present();
};
#[cfg(target_os = "emscripten")]
use emscripten::{emscripten};
#[cfg(target_os = "emscripten")]
emscripten::set_main_loop_callback(main_loop);
#[cfg(not(target_os = "emscripten"))]
loop { main_loop(); }
}