Calling web worker function from Emscripten


#1

I’m trying to create a web worker from Rust, calling a function in the worker file and passing some data to the main thread.

main.rs:

mod externs;
extern crate libc;
    
fn main() {
    println!("starting worker");
    let worker = externs::create_worker("./worker.js");
    externs::call_worker(worker, "worker_fn", "", 0);
    println!("worker called");
}

worker.rs:

#![feature(link_args)]
#[link_args = "-s EXPORTED_FUNCTIONS=['_worker_fn'] -s BUILD_AS_WORKER=1"]
    
extern crate libc;
    
mod externs;
    
extern {}
    
fn main() {
    println!("worker main");
}
    
#[no_mangle]
pub extern fn worker_fn() {
    println!("hello from the other side!");
}

When I compile the worker and main files, I’m able to see the message from main.rs and even the “worker main” message in the worker file. I can also see that the browser sends a request to worker.js, but it seems like the main thread does not call the worker_fn inside the worker file.

This is the externs file:

    use std::ffi::CString;
    use libc::*;
    use std::str::FromStr;

    /// Creating web worker
    pub fn create_worker(url: &str) -> ffi::worker_handle {
        let url = CString::new(url).unwrap();
        let ptr = url.as_ptr();
        unsafe { ffi::emscripten_create_worker(ptr) }
    }
    
    extern "C" fn do_something_handler(arg1: *mut c_char, arg2: c_int, arg3: *mut c_void) {
        println!("worker done!");
    }
    
    /// Creating web worker
    pub fn call_worker(worker: ffi::worker_handle, func_name: &str, data: &str, size: i32) {
        let func_name = CString::new(func_name).unwrap();
    
        let mut string = String::from_str(data).unwrap();
        let bytes = string.into_bytes();
        let mut cchar : Vec<c_char> = bytes.iter().map(|&w| w as c_char).collect();
        let data_slice = cchar.as_mut_slice();
    
        let mut state = 42;
        let state_ptr: *mut c_void = &mut state as *mut _ as *mut c_void;
    
        unsafe {
            ffi::emscripten_call_worker(
                worker,
                func_name.as_ptr(),
                data_slice.as_mut_ptr(),
                size as c_int,
                Some(do_something_handler),
                state_ptr
            )
        };
    }
    
    // This is mostly standard Rust-C FFI stuff.
    mod ffi {
        use libc::*;
        pub type worker_handle = c_int;
        pub type em_worker_callback_func = Option<unsafe extern "C" fn(arg1: *mut c_char,
                                                                       arg2: c_int,
                                                                       arg3: *mut c_void)>;
    
        extern "C" {
            pub fn emscripten_run_script_int(x: *const c_char) -> c_int;
            pub fn emscripten_create_worker(url: *const c_char) -> worker_handle;
            pub fn emscripten_call_worker(
                worker: worker_handle,
                funcname: *const c_char,
                data: *mut c_char,
                size: c_int,
                callback: em_worker_callback_func,
                arg: *mut c_void
            );
            pub fn emscripten_worker_respond(data: *mut c_char, size: c_int);
            pub fn emscripten_worker_respond_provisionally(data: *mut c_char, size: c_int);
        }
    }

I don’t understand what the problem is. Should I somehow change the worker file or maybe even the link_args?


#2

This is just an alternative, I’m using this stdweb library to call rust from javascript and vice-versa.
You can load the generated wasm and js file via webworker importScripts like these

This is how I load the rust components in the editor. Here is the working demo


#3

thanks, sounds interesting.

so here you are loading the wasm file but you don’t call any functions inside the the worker, right? so I assume the worker starts working automatically and sends the results back to the main thread?

also, can I have a look at the .rs files?


#4

Once loaded the wasm and the js file that is generated execute right away.
You main function will look like this:

  fn main(){
       stdweb::initialize();
       js!{
            self.addEventListener("message" (e) => {
                console.log("The main thread said something", e.data) ;
	}
       }
       stdweb::event_loop();
  }

In you main js , you can only communicate to the worker via postMessage.

var worker = new Worker("wasm_worker.js");
worker.postMessage({"cmd":"doSomething"});

#5

Ah, I see. thank you so much @ivanceras

I will try this and let you know the result.


#6

Awesome! yeah it worked. thank you @ivanceras