diff --git a/js/.gitignore b/js/.gitignore new file mode 100644 index 0000000..4e30131 --- /dev/null +++ b/js/.gitignore @@ -0,0 +1,6 @@ +/target +**/*.rs.bk +Cargo.lock +bin/ +pkg/ +wasm-pack.log diff --git a/js/Cargo.toml b/js/Cargo.toml new file mode 100644 index 0000000..c7734ca --- /dev/null +++ b/js/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "flatbush-wasm" +version = "0.1.0" +authors = ["Kyle Barron "] +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = ["console_error_panic_hook"] + +[dependencies] +wasm-bindgen = "0.2.84" + +# The `console_error_panic_hook` crate provides better debugging of panics by +# logging them with `console.error`. This is great for development, but requires +# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for +# code size when deploying. +console_error_panic_hook = { version = "0.1.7", optional = true } +geo-index = { path = "../" } + +[dev-dependencies] +wasm-bindgen-test = "0.3.34" + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" diff --git a/js/README.md b/js/README.md new file mode 100644 index 0000000..3593e01 --- /dev/null +++ b/js/README.md @@ -0,0 +1,13 @@ +# WebAssembly bindings to geo-index + +Example WebAssembly bindings for the Rust [`geo-index`](https://github.com/kylebarron/geo-index) crate. + +This is not expected to be used _directly_ for JavaScript applications that only need an RTree or KDTree. In those cases, using [flatbush](https://github.com/mourner/flatbush) or [kdbush](https://github.com/mourner/kdbush) directly would probably give similar performance while being easier to use and smaller code size. + +However this is designed to show _how the underlying data can be used zero-copy_ between WebAssembly and JavaScript. A larger application could have some use for generating the RTree index in Rust in WebAssembly but then allowing JavaScript to view it and run queries on it. + +## Building + +``` +wasm-pack build --target web +``` diff --git a/js/src/lib.rs b/js/src/lib.rs new file mode 100644 index 0000000..22c6ace --- /dev/null +++ b/js/src/lib.rs @@ -0,0 +1,20 @@ +mod rtree; +mod utils; + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +extern "C" { + fn alert(s: &str); +} + +#[wasm_bindgen] +pub fn greet() { + alert("Hello, flatbush-wasm!"); +} + +/// Returns a handle to this wasm instance's `WebAssembly.Memory` +#[wasm_bindgen(js_name = wasmMemory)] +pub fn memory() -> JsValue { + wasm_bindgen::memory() +} diff --git a/js/src/rtree.rs b/js/src/rtree.rs new file mode 100644 index 0000000..d13f463 --- /dev/null +++ b/js/src/rtree.rs @@ -0,0 +1,73 @@ +use geo_index::rtree::sort::HilbertSort; +use geo_index::rtree::{RTree, RTreeBuilder}; +use wasm_bindgen::prelude::*; + +#[wasm_bindgen] +pub struct FlatbushBuilder(RTreeBuilder); + +#[wasm_bindgen] +impl FlatbushBuilder { + /// Construct a new FlatbushBuilder with number of items and node size + #[wasm_bindgen(constructor)] + pub fn new(num_items: u32, node_size: Option) -> Self { + let builder = if let Some(node_size) = node_size { + RTreeBuilder::new_with_node_size(num_items, node_size) + } else { + RTreeBuilder::new(num_items) + }; + Self(builder) + } + + /// Add a single box at a time to the builder + /// + /// This is less efficient than vectorized APIs. + pub fn add_single_box(&mut self, min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> u32 { + self.0.add(min_x, min_y, max_x, max_y) + } + + /// Add arrays of min_x, min_y, max_x, max_y to the builder + /// + /// Each array must be a Float64Array of the same length. + pub fn add_separated( + &mut self, + min_x: &[f64], + min_y: &[f64], + max_x: &[f64], + max_y: &[f64], + ) -> Vec { + assert_eq!(min_x.len(), min_y.len()); + assert_eq!(min_x.len(), max_x.len()); + assert_eq!(min_x.len(), max_y.len()); + + let mut out = Vec::with_capacity(min_x.len()); + for i in 0..min_x.len() { + out.push(self.0.add(min_x[i], min_y[i], max_x[i], max_y[i])); + } + out + } + + /// Finish this builder, sorting the index. + /// + /// This will consume this instance and convert it to a [Flatbush]. + pub fn finish(self) -> Flatbush { + Flatbush(self.0.finish::()) + } +} + +#[wasm_bindgen] +pub struct Flatbush(RTree); + +#[wasm_bindgen] +impl Flatbush { + /// The byte offset within WebAssembly memory where the Flatbush memory starts. + #[wasm_bindgen] + pub fn byte_offset(&self) -> *const u8 { + self.0.as_ref().as_ptr() + } + + /// The number of bytes in Flatbush memory + #[wasm_bindgen] + pub fn byte_length(&self) -> usize { + self.0.as_ref().len() + } +} diff --git a/js/src/utils.rs b/js/src/utils.rs new file mode 100644 index 0000000..b1d7929 --- /dev/null +++ b/js/src/utils.rs @@ -0,0 +1,10 @@ +pub fn set_panic_hook() { + // When the `console_error_panic_hook` feature is enabled, we can call the + // `set_panic_hook` function at least once during initialization, and then + // we will get better error messages if our code ever panics. + // + // For more details see + // https://github.com/rustwasm/console_error_panic_hook#readme + #[cfg(feature = "console_error_panic_hook")] + console_error_panic_hook::set_once(); +}