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