Calling web worker function from Emscripten

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?

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

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?

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"});
1 Like

Ah, I see. thank you so much @ivanceras

I will try this and let you know the result.

Awesome! yeah it worked. thank you @ivanceras