Skip to main content

konjure_wasm/
lib.rs

1//! Batch browser exports. All authoring, geometry and behavior use `konjure-sdk`.
2use konjure_sdk::{Diagnostic, Event, Runtime, SceneDocument};
3use wasm_bindgen::prelude::*;
4
5mod language;
6pub use language::*;
7mod browser;
8pub use browser::*;
9mod interchange;
10pub use interchange::*;
11
12const MAX_DOCUMENT_BYTES: usize = 1024 * 1024;
13const FRAME_STRIDE: usize = 20;
14
15fn diagnostic(error: Diagnostic) -> JsValue {
16    JsValue::from_str(&serde_json::to_string(&error).unwrap_or_else(|_| error.to_string()))
17}
18fn json_error(error: impl std::fmt::Display) -> JsValue {
19    JsValue::from_str(&format!("{error}"))
20}
21fn encode(value: &impl serde::Serialize) -> Result<String, JsValue> {
22    serde_json::to_string(value).map_err(json_error)
23}
24
25/// Compile a source document without allocating a renderer or requesting permissions.
26#[wasm_bindgen]
27pub fn compile(source: &str) -> Result<String, JsValue> {
28    encode(&konjure_sdk::compile(source).map_err(diagnostic)?)
29}
30
31#[wasm_bindgen]
32pub fn sdk_version() -> String {
33    env!("CARGO_PKG_VERSION").into()
34}
35
36/// Opaque runtime; consumers must call `.free()` when disposing the view.
37#[wasm_bindgen]
38pub struct SdkRuntime {
39    inner: Runtime,
40}
41
42#[wasm_bindgen]
43impl SdkRuntime {
44    #[wasm_bindgen(constructor)]
45    pub fn new(source: &str) -> Result<SdkRuntime, JsValue> {
46        let scene = konjure_sdk::compile(source).map_err(diagnostic)?;
47        Ok(Self {
48            inner: Runtime::new(scene).map_err(diagnostic)?,
49        })
50    }
51
52    #[wasm_bindgen(js_name = fromDocument)]
53    pub fn from_document(document: &str) -> Result<SdkRuntime, JsValue> {
54        if document.len() > MAX_DOCUMENT_BYTES {
55            return Err(JsValue::from_str("document exceeds 1 MiB"));
56        }
57        let scene: SceneDocument = serde_json::from_str(document).map_err(json_error)?;
58        Ok(Self {
59            inner: Runtime::new(scene).map_err(diagnostic)?,
60        })
61    }
62
63    pub fn document_json(&self) -> Result<String, JsValue> {
64        encode(self.inner.scene())
65    }
66    pub fn meshes_json(&self) -> Result<String, JsValue> {
67        encode(self.inner.meshes())
68    }
69    pub fn sample_json(&self, time_seconds: f64) -> Result<String, JsValue> {
70        encode(&self.inner.sample(time_seconds).map_err(diagnostic)?)
71    }
72    /// Stable ID order, matching `frame()`. Meshes are keyed by these IDs.
73    pub fn ids_json(&self) -> Result<String, JsValue> {
74        encode(
75            &self
76                .inner
77                .sample(0.0)
78                .map_err(diagnostic)?
79                .draws
80                .iter()
81                .map(|d| &d.entity)
82                .collect::<Vec<_>>(),
83        )
84    }
85    /// One bulk buffer per frame: 16 column-major matrix floats, then RGBA in `[0, 1]`.
86    pub fn frame(&self, time_seconds: f64) -> Result<Vec<f32>, JsValue> {
87        let frame = self.inner.sample(time_seconds).map_err(diagnostic)?;
88        let mut packed = Vec::with_capacity(frame.draws.len() * FRAME_STRIDE);
89        for draw in frame.draws {
90            for value in draw.matrix {
91                let value = value as f32;
92                if !value.is_finite() {
93                    return Err(JsValue::from_str(
94                        "world transform exceeds the render float range",
95                    ));
96                }
97                packed.push(value);
98            }
99            packed.extend(
100                [draw.color.r, draw.color.g, draw.color.b, draw.color.a]
101                    .map(|v| f32::from(v) / 255.0),
102            );
103        }
104        Ok(packed)
105    }
106    pub fn dispatch(&mut self, event: &str) -> Result<String, JsValue> {
107        if event.len() > 4096 {
108            return Err(JsValue::from_str("event exceeds 4 KiB"));
109        }
110        let event: Event = serde_json::from_str(event).map_err(json_error)?;
111        encode(&self.inner.dispatch(event).map_err(diagnostic)?)
112    }
113}