Updating the browser within a WASM function prior to return

Hi Everyone, relatively new to Rust and I'm using it to build web assembly apps. Some of the functions of my app are quite computationally intensive and the user might assume that the page has crashed, when in reality it is just crunching through the data and the user just needs to wait a bit. So I would like to make some sort of progress bar or notification that the user can see.

Here's the problem: if I log information to the console, this appears in real time, as the function is running. However, if I call a JavaScript function that can update the user interface it doesn't update until the WASM function has completed (often after several minutes), so it is not useful for reassuring the user. I also tried overriding the console.log function in javascript to write to the page itself, and unfortunately this doesn't fix the problem.

I don't think this is really a rust issue per se, so I'm not sure if this is the right forum to post in, but if anybody has any ideas I would be really appreciative.

Here is some pseudo-code to show what I'm talking about.

in my lib.rs file:

// macro for writing to the console
#[macro_export]
macro_rules! log {
    ( $( $t:tt )* ) => {
        web_sys::console::log_1(&format!( $( $t )* ).into());
    }
}

// function for updating messages to the user

#[wasm_bindgen]
pub fn mod_element(message : String){
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");
    if let Some(val) = document.get_element_by_id("Status"){
        val.set_inner_html(message);
    }
 }
    
// here is the function that is called by the user click

#[wasm_bindgen]
impl MyApp {
   pub fn do_computationally_intensive_thing()->{
     self.big_piece_of_data.do_thing()
   } 
}

In a separate file

impl MyData{
  pub fn do_thing(){
    for each in self.big_array(){
      each.do_slow_thing();
      log!("I did a slow thing!");  // this writes to the console in real time
      mod_element("I did a slow thing!"); // this only updates the page when
                                          // do_computationally_intensive_thing() has returned
   }
 }
}

While any code (javascript or wasm) is running on the main thread, the rendering code can't run. You will need to use a web worker to run long computations outside of the main thread. This also prevents the browser from giving a warning to the user when the main thread is locked up for too long.

1 Like

Thanks, man, that's exactly what I needed. (all my experience is in embedded so maybe everybody knows this already...)

Another option: You can break up the expensive task into small parts that you schedule to run repeatedly (e.g. by calling JS setInterval with a closure which does part of the work) until the work is done. If each part takes only a few milliseconds to complete, the UI will still be responsive.

This will not complete the task quite as fast as splitting it off into a worker (because the page is using just a single thread split between doing the computation and updating the UI) but — depending on your data-flow requirements — it may be simpler because you are working with that same thread and you can e.g. update the UI with the in-progress data directly.

You mention you have embedded experience, so think of it as the same way you might code there on a device which is both interactive and has no threads: you have a main loop (here, the browser provides that loop) which checks various inputs and does tasks, but you can't have an individual task take very long (and you especially can't have an interrupt handler take very long) because then the device won't react to input as promptly as it should. And in this analogy, using a worker is like having two separate processors in your device with separate memory — more flexible, but requires explicit communication.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.