jsmpi 0.1.0

A browser-oriented MPI compatibility layer for Rust/WASM using Web Workers
Documentation
use crate::error::Result;
use crate::runtime::protocol::Envelope;
use crate::datatype::{Rank, Tag};

#[derive(Debug, Clone, Copy)]
pub struct BrowserEnv {
    pub rank: Rank,
    pub size: Rank,
}

#[cfg(target_arch = "wasm32")]
mod imp {
    use js_sys::{Function, Reflect};
    use wasm_bindgen::JsCast;
    use wasm_bindgen::JsValue;

    use super::{BrowserEnv, Envelope, Rank, Result, Tag};
    use crate::error::Error;

    pub fn detect_environment() -> Result<BrowserEnv> {
        console_error_panic_hook::set_once();

        let global = js_sys::global();
        let rank = Reflect::get(&global, &JsValue::from_str("__jsmpi_rank"))
            .ok()
            .and_then(|value| value.as_f64())
            .map(|value| value as Rank)
            .unwrap_or(0);
        let size = Reflect::get(&global, &JsValue::from_str("__jsmpi_size"))
            .ok()
            .and_then(|value| value.as_f64())
            .map(|value| value as Rank)
            .unwrap_or(1);

        Ok(BrowserEnv { rank, size })
    }

    pub fn post_envelope(envelope: &Envelope) -> Result<()> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_send"))
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let send_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;

        let js_envelope = serde_wasm_bindgen::to_value(envelope)
            .map_err(|err| Error::Serialization(err.to_string()))?;
        send_fn
            .call1(&global, &js_envelope)
            .map_err(|err| Error::Protocol(format!("send bridge failed: {err:?}")))?;
        Ok(())
    }

    pub fn recv_envelope(source: Option<Rank>, tag: Option<Tag>) -> Result<Option<Envelope>> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_recv"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return Ok(None);
        }

        let recv_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let js_value = recv_fn
            .call2(
                &global,
                &source
                    .map(|value| JsValue::from_f64(value as f64))
                    .unwrap_or(JsValue::NULL),
                &tag
                    .map(|value| JsValue::from_f64(value as f64))
                    .unwrap_or(JsValue::NULL),
            )
            .map_err(|err| Error::Protocol(format!("receive bridge failed: {err:?}")))?;

        if js_value.is_null() || js_value.is_undefined() {
            return Ok(None);
        }

        serde_wasm_bindgen::from_value(js_value)
            .map(Some)
            .map_err(|err| Error::Serialization(err.to_string()))
    }

    pub fn recv_envelope_blocking(source: Option<Rank>, tag: Option<Tag>) -> Result<Option<Envelope>> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_recv_blocking"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return recv_envelope(source, tag);
        }

        let recv_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let js_value = recv_fn
            .call2(
                &global,
                &source
                    .map(|value| JsValue::from_f64(value as f64))
                    .unwrap_or(JsValue::NULL),
                &tag
                    .map(|value| JsValue::from_f64(value as f64))
                    .unwrap_or(JsValue::NULL),
            )
            .map_err(|err| Error::Protocol(format!("receive bridge failed: {err:?}")))?;

        if js_value.is_null() || js_value.is_undefined() {
            return Ok(None);
        }

        serde_wasm_bindgen::from_value(js_value)
            .map(Some)
            .map_err(|err| Error::Serialization(err.to_string()))
    }

    pub fn barrier(rank: Rank, size: Rank, timeout_ms: Option<u32>) -> Result<bool> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_barrier_timeout"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return Ok(true);
        }

        let barrier_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let result = barrier_fn
            .call3(
                &global,
                &JsValue::from_f64(rank as f64),
                &JsValue::from_f64(size as f64),
                &timeout_ms
                    .map(|v| JsValue::from_f64(v as f64))
                    .unwrap_or(JsValue::NULL),
            )
            .map_err(|err| Error::Protocol(format!("barrier bridge failed: {err:?}")))?;
        Ok(result.as_bool().unwrap_or(true))
    }

    pub fn barrier_begin(rank: Rank, size: Rank) -> Result<i32> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_barrier_begin"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return Ok(0);
        }

        let barrier_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let result = barrier_fn
            .call2(
                &global,
                &JsValue::from_f64(rank as f64),
                &JsValue::from_f64(size as f64),
            )
            .map_err(|err| Error::Protocol(format!("barrier begin bridge failed: {err:?}")))?;
        Ok(result.as_f64().unwrap_or(0.0) as i32)
    }

    pub fn barrier_ready(version: i32) -> Result<bool> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_barrier_ready"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return Ok(true);
        }

        let barrier_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        let result = barrier_fn
            .call1(&global, &JsValue::from_f64(version as f64))
            .map_err(|err| Error::Protocol(format!("barrier ready bridge failed: {err:?}")))?;
        Ok(result.as_bool().unwrap_or(true))
    }

    pub fn mark_finished() -> Result<()> {
        let global = js_sys::global();
        let maybe_fn = Reflect::get(&global, &JsValue::from_str("__jsmpi_mark_finished"))
            .unwrap_or(JsValue::UNDEFINED);

        if maybe_fn.is_undefined() || maybe_fn.is_null() {
            return Ok(());
        }

        let finished_fn = maybe_fn
            .dyn_into::<Function>()
            .map_err(|_| Error::RuntimeNotConfigured)?;
        finished_fn
            .call0(&global)
            .map_err(|err| Error::Protocol(format!("finished bridge failed: {err:?}")))?;
        Ok(())
    }
}

#[cfg(not(target_arch = "wasm32"))]
mod imp {
    use super::{BrowserEnv, Envelope, Rank, Result, Tag};

    pub fn detect_environment() -> Result<BrowserEnv> {
        Ok(BrowserEnv { rank: 0, size: 1 })
    }

    pub fn post_envelope(_envelope: &Envelope) -> Result<()> {
        Ok(())
    }

    pub fn recv_envelope(_source: Option<Rank>, _tag: Option<Tag>) -> Result<Option<Envelope>> {
        Ok(None)
    }

    pub fn recv_envelope_blocking(
        _source: Option<Rank>,
        _tag: Option<Tag>,
    ) -> Result<Option<Envelope>> {
        Ok(None)
    }

    pub fn barrier(_rank: Rank, _size: Rank, _timeout_ms: Option<u32>) -> Result<bool> {
        Ok(true)
    }

    pub fn barrier_begin(_rank: Rank, _size: Rank) -> Result<i32> {
        Ok(0)
    }

    pub fn barrier_ready(_version: i32) -> Result<bool> {
        Ok(true)
    }

    pub fn mark_finished() -> Result<()> {
        Ok(())
    }
}

pub use imp::{
    barrier,
    barrier_begin,
    barrier_ready,
    detect_environment,
    mark_finished,
    post_envelope,
    recv_envelope,
    recv_envelope_blocking,
};