Rust WASM Again

So, its easy to add Rust WASM callbacks to javascript components. Its simple to populate their values, it really is.

Unless you haven’t done it before, in which case its google hell of useless search results.

The best reference is the web-sys API.

Eg of a select onchange

So, there is a web-sys function set_onchange which is in this page. It kindly mentions the MDN Docs, which show its a wrapper for the JavaScript.

addEventListener("change", (event) => {});

Then the paint example shows adding a closure as the callback uses the incantation:

use wasm_bindgen::closure::Closure;
use web_sys::{console, HtmlSelectElement};
use wasm_bindgen::JsCast;

pub fn stuff() {
  let document = web_sys::window().unwrap().document().unwrap();
  let selector : &HtmlSelectElement = document
    .get_element_by_id("dataflows_dropdown")
    .unwrap()
    .dyn_ref::<HtmlSelectElement>()
    .unwrap();
}

Then the rest

wasm bindgen book - scroll down to the reference.

web-sys Closures - part of the reference above.

Paint example

Adding a button event listener

State of a Rust WASM app

Given the evolution of UI code we probably want something like this:

User interacts, which in the callback sends an event to a handler.

The handler then mutates the UIState, eg because they selected a new unit in a game, or a new item of data.

The animation frame then reads the UIState and updates accordingly.

Global state.

Given we want global state in a WASM project, well, err. The Rust object to use is an RwLock, because it has a const new operator which can be used to init the static. BUT, there is a pre-init state, so it should hold an Option.

static GOD_STATE: RwLock< Option<GodState>> = RwLock::new(None);

Then on initialising - eg load of data etc, you can assign it.

let mut w = GOD_STATE.write().unwrap();
let mut god_state = GodState {
  foo:String::from("bar"),
};
*w = Some(god_state);

Is GOD_STATE enough. Well, for large single page apps it basically is, unless your team is big and you don’t know how to work together. But, one thing that became apparent quickly is that GOD_STATE is useful for both static data, and more dynamic data. So split this in two, one for data that has to be loaded and left unchanged, and another set which reflects the users current activity.

pub static GUI_STATE: RwLock<GuiState> = RwLock::new(DEFAULT_GUI_STATE);

#[derive(Debug)]
pub struct GuiState {
    pub mouse_coord: MouseCoord,
    pub last_in_id: String,
    pub curr_in_id: String,
}
// etc

The event handler then looks a bit like

pub fn process_event(event: &GuiEvent) {
    if *event != AnimationFrameCalled {
        console_log(&(format!("gui_state::process_event with [{:?}]", event)));
    }
    let mut w = GUI_STATE.write().unwrap();
    (*w).process(event);
}
...
// I want the function to be part of GuiState, ie in the impl for GuiState
fn process(&mut self, event: &GuiEvent) {
    event_handler_process(self, event);
}
...
// Then I can have event handlers as pub functions.  ie when you have loads of them
// you probably want to start plitting into rust files by some sort of criteria
// rather than just having one mega file.
pub fn event_handler_process(gui_state: &mut GuiState, event: &GuiEvent) {
    if *event != GuiEvent::AnimationFrameCalled {
        console::log_1(&(format!("event_handler_process(event={:?})", event)).into());
    }
    match event {
        MouseMove(m) => {
            handle_mouse_moved(gui_state, m);
        }
        MousePressed(m) => {
            handle_mouse_pressed(gui_state, m);
        }
        MouseReleased(_m) => {}
        AnimationFrameCalled => {
            handleAnimationFrame(gui_state);
        }
    }
}

And then you can update the GuiState in each of the handlers.

So, your UI can now consist of different GuiState structs, one for each ‘window’ which lets the user load and mutate data.

What about RefCell, Rc, Arc, and the rest

Good question, so a quick review:

Derived from this page

Is your program possibly multi-threaded?  
 No: Use Cell<T> or RefCell<T>
    Are you storing references?
      Yes: use RefCell, and aquire a lock prior to mutating.
        You can get panics if you borrow while its borrowed.  Idiot.
      No: use Cell
 Yes: Use Mutex<T>, RwLock<T> or atomic types: Arc. <- its a UI use these.

Why wrap RefCell in an Rc or an Arc

Both Rc (not thread safe) and Arc (thread safe) do ref counting. So you can share data all over your code which maybe you keep in your GodState, or perhaps your GuiState and want to mutate it.

For instance, you async load up some drop down contents, and then hold these centrally for use in your UI, and periodically poll for new values.

So, maybe the animation frame periodically kicks off an async reload, which when it completes repopulates the list. Meanwhile a bunch of other screens use it to populate dropdowns.

I explored how String for instance can be cached and reused in an Rc here, the code is here.

Back to GOD_STATE design

Say we have initialised it to a RwLock<Some>. Now MyState has a map or vec of data in it, and that data stays the same, but we want to share it all over the code. Maybe not the collection but the items, say they are MyItem type.

What should the vec hold? Rather than clone this data I want to share references. So an Arc seems good. But, the lifetime of the the GodState is static, what of the contents of the vec which holds the MyItems? In order to share the references - ie clone the Arc, which isn’t a clone, its a ref inc operations.

Or, and this is another idea, every time you run the animation frame, grab the lock on the GodState and the GuiState you are playing with, then pass them into the stack which uses the data. Then you do not keep copies of state outside the state, you always use the data once you have the lock. So, instead of holding the MyItem, you have to hold an Id for the MyItem, and look it up each time you use it.

Obviously there are pros and cons, but we are in a modern browser, so its unlikely that CPU speed will be a problem.

I’m going to stop now, this post turned in a wierd direction, but it helped me design my data structures for my wasm project. Off to do some coding now.