Skip to main content

treemaker_wasm/
lib.rs

1//! `wasm-bindgen` wrapper around `treemaker-core`.
2//!
3//! The wasm API stores loaded trees behind integer handles so JavaScript can
4//! call into the engine without copying the whole model for every operation.
5//! Error values are serialized objects with the same stable `code` values as
6//! native [`treemaker_core::TreeError`].
7
8use serde::Serialize;
9use std::cell::RefCell;
10use treemaker_core::{Tree, TreeError};
11use wasm_bindgen::prelude::*;
12
13thread_local! {
14    static TREES: RefCell<Vec<Option<Tree>>> = const { RefCell::new(Vec::new()) };
15}
16
17#[wasm_bindgen]
18pub fn load_tmd(text: &str) -> std::result::Result<u32, JsValue> {
19    let tree = Tree::from_tmd_str(text).map_err(to_js_error)?;
20    TREES.with(|trees| {
21        let mut trees = trees.borrow_mut();
22        if let Some((idx, slot)) = trees
23            .iter_mut()
24            .enumerate()
25            .find(|(_, slot)| slot.is_none())
26        {
27            *slot = Some(tree);
28            Ok(idx as u32)
29        } else {
30            trees.push(Some(tree));
31            Ok((trees.len() - 1) as u32)
32        }
33    })
34}
35
36#[wasm_bindgen]
37pub fn tree_summary(handle: u32) -> std::result::Result<JsValue, JsValue> {
38    with_tree(handle, |tree| {
39        serde_wasm_bindgen::to_value(&tree.summary()).map_err(to_js_value)
40    })
41}
42
43#[wasm_bindgen]
44pub fn check(handle: u32) -> std::result::Result<JsValue, JsValue> {
45    tree_summary(handle)
46}
47
48#[wasm_bindgen]
49pub fn cp_status_report(handle: u32) -> std::result::Result<JsValue, JsValue> {
50    with_tree(handle, |tree| {
51        serde_wasm_bindgen::to_value(&tree.cp_status_report()).map_err(to_js_value)
52    })
53}
54
55#[wasm_bindgen]
56pub fn optimize_scale(handle: u32) -> std::result::Result<JsValue, JsValue> {
57    with_tree_mut(handle, |tree| {
58        let report = tree.optimize_scale().map_err(to_js_error)?;
59        serde_wasm_bindgen::to_value(&report).map_err(to_js_value)
60    })
61}
62
63#[wasm_bindgen]
64pub fn optimize_edges(handle: u32) -> std::result::Result<JsValue, JsValue> {
65    with_tree_mut(handle, |tree| {
66        let report = tree.optimize_edges().map_err(to_js_error)?;
67        serde_wasm_bindgen::to_value(&report).map_err(to_js_value)
68    })
69}
70
71#[wasm_bindgen]
72pub fn optimize_strain(handle: u32) -> std::result::Result<JsValue, JsValue> {
73    with_tree_mut(handle, |tree| {
74        let report = tree.optimize_strain().map_err(to_js_error)?;
75        serde_wasm_bindgen::to_value(&report).map_err(to_js_value)
76    })
77}
78
79#[wasm_bindgen]
80pub fn build_crease_pattern(handle: u32) -> std::result::Result<JsValue, JsValue> {
81    with_tree_mut(handle, |tree| {
82        tree.build_polys_and_crease_pattern().map_err(to_js_error)?;
83        serde_wasm_bindgen::to_value(&tree.summary()).map_err(to_js_value)
84    })
85}
86
87#[wasm_bindgen]
88pub fn save_tmd5(handle: u32) -> std::result::Result<String, JsValue> {
89    with_tree(handle, |tree| Ok(tree.to_tmd5_string()))
90}
91
92#[wasm_bindgen]
93pub fn free_tree(handle: u32) -> std::result::Result<(), JsValue> {
94    TREES.with(|trees| {
95        let mut trees = trees.borrow_mut();
96        let slot = trees
97            .get_mut(handle as usize)
98            .ok_or_else(|| js_error("invalid_handle", "invalid TreeHandle"))?;
99        *slot = None;
100        Ok(())
101    })
102}
103
104fn with_tree<T>(
105    handle: u32,
106    f: impl FnOnce(&Tree) -> std::result::Result<T, JsValue>,
107) -> std::result::Result<T, JsValue> {
108    TREES.with(|trees| {
109        let trees = trees.borrow();
110        let tree = trees
111            .get(handle as usize)
112            .and_then(Option::as_ref)
113            .ok_or_else(|| js_error("invalid_handle", "invalid TreeHandle"))?;
114        f(tree)
115    })
116}
117
118fn with_tree_mut<T>(
119    handle: u32,
120    f: impl FnOnce(&mut Tree) -> std::result::Result<T, JsValue>,
121) -> std::result::Result<T, JsValue> {
122    TREES.with(|trees| {
123        let mut trees = trees.borrow_mut();
124        let tree = trees
125            .get_mut(handle as usize)
126            .and_then(Option::as_mut)
127            .ok_or_else(|| js_error("invalid_handle", "invalid TreeHandle"))?;
128        f(tree)
129    })
130}
131
132#[derive(Serialize)]
133struct JsErrorEnvelope {
134    code: &'static str,
135    message: String,
136}
137
138fn to_js_error(error: TreeError) -> JsValue {
139    js_error(error.code(), error.to_string())
140}
141
142fn to_js_value(error: impl std::fmt::Display) -> JsValue {
143    js_error("js_value", error.to_string())
144}
145
146fn js_error(code: &'static str, message: impl Into<String>) -> JsValue {
147    serde_wasm_bindgen::to_value(&JsErrorEnvelope {
148        code,
149        message: message.into(),
150    })
151    .unwrap_or_else(|_| JsValue::from_str(code))
152}