What is the correct way to handle global state in a wasm project?

I'm working on a wasm project where I have a canvas that fills the entire viewport, and there are three types of things I need my code to do:

  1. refresh the canvas contents every frame
  2. call a function regularly to update the global state
  3. listen for and respond to various kinds of events

I found a good way of doing the first part here, and it can do the second part too if I'm okay with the logic only running when the tab is visible. I've really hit a wall with the third part, though. I can hack it together by using unsafe references to mutable statics, but those seem awful and there must be a better way. Given a struct as defined below, what kinds of things would the boilerplate need to do to make the methods get called at the appropriate times?

#[derive(Default)]
pub struct State {
    //...
}

impl State {
    pub fn render(&self, now: f64) {
        //...
    }

    pub fn update(&mut self, now: f64) {
        //...
    }

    pub fn on_key_down(&mut self, event: web_sys::KeyboardEvent) {
        //...
    }
}

Handlers need to reference your state object, and since there can be multiple handlers, you end up with shared mutable ownership. The type for this is Arc<Mutex<State>>.

Alternatively, split the state it into two (or more) structs:

  1. one that stores inputs for the next frame
  2. rest of the state that is mutated from the inputs when needed

This way the first one may owned by your input handling and borrowed/copied by the state-mutating object when needed.

If you are using wasm-bindgen then you should be able to pass ownership of your State object to the JavaScript that is calling it.