Skip to content

ALEX11BR/emscripten-functions

Folders and files

NameName
Last commit message
Last commit date

Latest commit

45acf3f · Mar 6, 2025

History

39 Commits
Aug 26, 2023
Mar 6, 2025
Feb 1, 2025
Mar 6, 2025
Mar 6, 2025
Aug 25, 2023
Mar 6, 2025
Mar 6, 2025
Aug 26, 2023
Mar 6, 2025

Repository files navigation

Crates for emscripten targets in rust

This project contains 2 crates with functions (and types too) to help rust development for emscripten targets.

The crates

  • emscripten-functions-sys - Raw bindgen-generated rust bindings to emscripten’s system functions.
  • emscripten-functions - Various emscripten system functions that make programming in rust for emscripten targets easier.

Why emscripten for rust

If you want to write web apps in rust, the wasm32-unknown-unknown target is the top choice, with a quite mature ecosystem of functions that interact with the web ecosystem. That being said, if your project has parts or libraries written in C or C++, then the wasm32-unknown-unknown target doesn't work anymore. Also, you might be interested in using asm.js instead of WASM.

Thankfully, there is an alternative: wasm32-unknown-emscripten (and asmjs-unknown-emscripten too). Emscripten provides a ready-to-use libc for web apps, and a few other popular C libraries, like SDL. Using the 2 crates, interacting with the web environment of emscripten becomes easier.

Some tips

Functions that can be triggered dynamically

  • Make a function with #[no_mangle] pub extern "C" fn function_name ... (they can accept and return integers, floats, C-style strings, booleans, pointers or arrays of byte-sized numbers),
  • add -sEXPORTED_RUNTIME_METHODS=ccall,cwrap and -sEXPORTED_FUNCTIONS=_function_name,... (add a preceding underscore to your functions' names and separate them with commas) as link arguments,

... and you can call your functions using the ccall/cwrap emscripten javascript functions.

Example

The rust part:

use std::ffi::{CStr, CString};
use std::mem::ManuallyDrop;
use std::os::raw::c_char;

#[no_mangle]
pub extern "C" fn concat_str_int_float(s: *const c_char, i: i32, f: f64) -> *const c_char {
    // s (char pointer) => string (rust &str)
    let string = if s.is_null() {
        ""
    } else {
        (unsafe { CStr::from_ptr(s) }).to_str().unwrap()
    };

    let result = format!("{}_{}_{:.2}", string, i, f);

    // result (rust String) => return value (char pointer)
    // The return value will be `free`d by the caller
    let result_cstring = ManuallyDrop::new(CString::new(result.as_bytes()).unwrap());
    return result_cstring.as_ptr();
}

The javascript part (to be put in a <script> after <script src="your_project_name.js"></script>):

// As we need to `free` the pointer returned by our function, we need its raw address, so we'll consider the return type to be `number`.
let concat_str_int_float = Module.cwrap("concat_str_int_float", "number", ["string", "number", "number"])
// The function's signature decides if a `number` is an integer or a float.

let button = document.querySelector("button");
button.onclick = () => {
  let result_ptr = concat_str_int_float(document.title, performance.now(), performance.now())
  // `UTF8ToString` is an emscripten JS function which returns a proper JS garbage collected string.
  document.title = UTF8ToString(result_ptr)
  // We need to `free` our pointer so we don't cause memory leaks
  Module._free(result_ptr)
}

Run javascript from rust

Using the emscripten_functions::emscripten::run_script family of functions you can run the javascript you need in your web app.

Example

// The `.escape_unicode()` method makes it safe to pass untrusted user input.
run_script(
    format!(
        r##"
            document.querySelector("#this-is-secure").innerHTML = "{}"
        "##,
        "untrusted user input".escape_unicode()
    )
);

Calling JavaScript functions using the val API

Using the emscripten-val crate, you can make use of emscripten's val API to call JavaScript from the Rust side.

Example

Example taken from the emscripten-val README:

use emscripten_functions::emscripten::{run_script, run_script_int};
use emscripten_val::*;

fn main() {
    let a = Val::from_array(&[1, 2]);
    run_script(format!(
        r#"
        console.log(Emval.toValue({}));
    "#,
        a.as_handle() as i32
    ));

    a.call("push", argv![3]);
    run_script(format!(
        r#"
        console.log(Emval.toValue({}));
    "#,
        a.as_handle() as i32
    ));

    let handle = run_script_int("let n = new Number('123'); Emval.toHandle(n)");
    let number = Val::take_ownership(handle as EM_VAL);
    println!("{}", number.call("valueOf", &[]).as_i32());

    #[no_mangle]
    pub extern "C" fn event_handler(ev: EM_VAL) {
        let val = Val::take_ownership(ev);
        let target = val.get(&"target");
        target.set(&"textContent", &"Clicked");
    }

    let button = Val::take_ownership(run_script_int(
        r#"
        let button = document.createElement('BUTTON');
        button.addEventListener('click', (ev) => {
            _event_handler(Emval.toHandle(ev));
        });
        let body = document.getElementsByTagName('body')[0];
        body.appendChild(button);
        Emval.toHandle(button)
    "#,
    ) as EM_VAL);
    button.set(&"textContent", &"click");
}

Main loop control

If you need to run a loop function over and over, emscripten has its own main loop managing system. Using the emscripten_functions::emscripten::set_main_loop and emscripten_functions::emscripten::set_main_loop_with_arg functions you can run your rust functions as main loops, with full control over the main loop running parameters.

Example

struct GameData {
    level: u32,
    score: u32
}
let mut game_data = GameData {
    level: 1,
    score: 0
}

set_main_loop_with_arg(|data| {
    if data.score < data.level {
        data.score += 1;
    } else {
        data.score = 0;
        data.level += 1;
    }

    // Here you call your display to screen functions.
    // For demonstration purposes I chose `println!`.
    println!("Score {}, level {}", data.score, data.level);
}, game_data, 0, true);

An SDL game example

An SDL game example that has image handling can be found in examples/simple-game.

A websocket sample

A little websocket client application made using emscripten_functions::websocket can be found in examples/websocket-example.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages