draco_decoder 0.0.21

a draco decoder on rust (wip)
Documentation
use js_sys::{Array, Object, Promise, Uint8Array};
use std::cell::RefCell;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::JsFuture;

use crate::{AttributeDataType, DracoDecodeConfig};

thread_local! {
    static DRACO_DECODE_FUNC_MODULE: RefCell<Option<JsValue>> = RefCell::new(None);
}

async fn get_js_module() -> Result<JsValue, JsValue> {
    if let Some(module) = DRACO_DECODE_FUNC_MODULE.with(|m| m.borrow().clone()) {
        return Ok(module);
    }

    let js_code = include_str!("../javascript/index.es.js");
    let escaped = js_code.replace("\\", "\\\\").replace("`", "\\`");

    let setup_code = format!(
        r#"
        (function() {{
            const code = `{escaped}`;
            const blob = new Blob([code], {{ type: "application/javascript" }});
            const url = URL.createObjectURL(blob);
            return import(url).then(mod => {{
                URL.revokeObjectURL(url);
                return mod;
            }});
        }})()
    "#
    );

    // Use eval to run the wrapper and return a promise of the module
    let js_module = js_sys::eval(&setup_code)?;
    let module_promise: Promise = js_module.dyn_into()?;
    let module = JsFuture::from(module_promise).await?;

    DRACO_DECODE_FUNC_MODULE.with(|m| m.replace(Some(module.clone())));

    Ok(module)
}

async fn decode_draco_mesh_from_embedded_js(
    data: &js_sys::Uint8Array,
    byte_length: usize,
) -> Result<js_sys::Uint8Array, JsValue> {
    let module = get_js_module().await?;

    // Call the decode function from the module
    let decode_fn = js_sys::Reflect::get(&module, &JsValue::from_str("decodeDracoMeshInWorker"))?
        .dyn_into::<js_sys::Function>()?;

    let this = JsValue::NULL;
    let result = decode_fn.call2(&this, data, &JsValue::from(byte_length))?;
    let decode_promise: Promise = result.dyn_into()?;
    let out_buf = JsFuture::from(decode_promise).await?;
    Ok(out_buf.dyn_into::<Uint8Array>()?)
}

pub async fn decode_mesh_wasm_worker(data: &[u8], config: &DracoDecodeConfig) -> Option<Vec<u8>> {
    let js_array = Uint8Array::from(data);
    let estimate_buffer_size = config.estimate_buffer_size();

    match decode_draco_mesh_from_embedded_js(&js_array, estimate_buffer_size).await {
        Ok(decoded) => Some(decoded.to_vec()),
        Err(err) => {
            web_sys::console::error_1(&err);
            None
        }
    }
}

async fn decode_draco_mesh_from_embedded_js_with_config(
    data: &js_sys::Uint8Array,
) -> Result<(Vec<u8>, DracoDecodeConfig), JsValue> {
    let module = get_js_module().await?;

    // Call the decode function with config from the module
    let decode_fn = js_sys::Reflect::get(
        &module,
        &JsValue::from_str("decodeDracoMeshInWorkerWithConfig"),
    )?
    .dyn_into::<js_sys::Function>()?;

    let this = JsValue::NULL;
    let result = decode_fn.call1(&this, data)?;
    let decode_promise: Promise = result.dyn_into()?;
    let out_obj = JsFuture::from(decode_promise).await?;

    // Parse the result: { decoded: Uint8Array, config: Object }
    let decoded_array =
        js_sys::Reflect::get(&out_obj, &JsValue::from_str("decoded"))?.dyn_into::<Uint8Array>()?;
    let config_obj = js_sys::Reflect::get(&out_obj, &JsValue::from_str("config"))?;

    // Convert config from JS to Rust
    let vertex_count = js_sys::Reflect::get(&config_obj, &JsValue::from_str("vertex_count"))?
        .as_f64()
        .unwrap_or(0.0) as u32;
    let index_count = js_sys::Reflect::get(&config_obj, &JsValue::from_str("index_count"))?
        .as_f64()
        .unwrap_or(0.0) as u32;

    let attributes_array =
        js_sys::Reflect::get(&config_obj, &JsValue::from_str("attributes"))?.dyn_into::<Array>()?;

    let mut config = DracoDecodeConfig::new(vertex_count, index_count);

    for i in 0..attributes_array.length() {
        let attr_obj = attributes_array.get(i).dyn_into::<Object>()?;

        let dim = js_sys::Reflect::get(&attr_obj, &JsValue::from_str("dim"))?
            .as_f64()
            .unwrap_or(0.0) as u32;
        let data_type = js_sys::Reflect::get(&attr_obj, &JsValue::from_str("data_type"))?
            .as_f64()
            .unwrap_or(0.0) as i32;

        let attr_data_type = match data_type {
            0 => AttributeDataType::Int8,
            1 => AttributeDataType::UInt8,
            2 => AttributeDataType::Int16,
            3 => AttributeDataType::UInt16,
            4 => AttributeDataType::Int32,
            5 => AttributeDataType::UInt32,
            6 => AttributeDataType::Float32,
            _ => AttributeDataType::Float32,
        };

        config.add_attribute(dim, attr_data_type);
    }

    Ok((decoded_array.to_vec(), config))
}

pub async fn decode_mesh_wasm_worker_with_config(data: &[u8]) -> Option<crate::MeshDecodeResult> {
    let js_array = Uint8Array::from(data);

    match decode_draco_mesh_from_embedded_js_with_config(&js_array).await {
        Ok((decoded, config)) => Some(crate::MeshDecodeResult {
            data: decoded,
            config,
        }),
        Err(err) => {
            web_sys::console::error_1(&err);
            None
        }
    }
}