feat: copied from github.com/seddonm1/quickjs
This commit is contained in:
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"crates/quickjs",
|
||||
"crates/quickjs-wasm",
|
||||
]
|
||||
|
||||
[profile.release]
|
||||
lto = true
|
||||
opt-level = 3
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) Mike Seddon 2023
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
57
README.md
57
README.md
@@ -1,3 +1,56 @@
|
||||
# seddonm1-quickjs
|
||||
This repository demonstrates how to use [quickjs-wasm-rs](https://github.com/Shopify/javy/tree/main/crates/quickjs-wasm-rs) with [wasmtime](https://github.com/bytecodealliance/wasmtime) to easily build a safe and isolated plugin system for Rust.
|
||||
|
||||
From: https://github.com/seddonm1/quickjs
|
||||
Code to accompany blog post: https://reorchestrate.com/posts/plugins-for-rust
|
||||
|
||||
First `build-wasm.sh` script which will download and build the `quickjs.wasm` module.
|
||||
|
||||
# Examples
|
||||
|
||||
Run a sequential executor:
|
||||
|
||||
```bash
|
||||
cargo run --example iter --release
|
||||
```
|
||||
|
||||
Run a parallel executor:
|
||||
|
||||
```bash
|
||||
cargo run --example par_iter --release
|
||||
```
|
||||
|
||||
Both accept additional arguments like:
|
||||
|
||||
```bash
|
||||
cargo run --release --example iter -- \
|
||||
--module ./quickjs.wasm \
|
||||
--script ./track_points.js \
|
||||
--data ./track_points.json \
|
||||
--iterations 1000 \
|
||||
--inherit-stdout \
|
||||
--inherit-stderr
|
||||
```
|
||||
|
||||
# Build
|
||||
|
||||
```bash
|
||||
cargo build --package quickjs --release
|
||||
```
|
||||
|
||||
# Test
|
||||
|
||||
```bash
|
||||
cargo test --package quickjs --release
|
||||
```
|
||||
|
||||
# Bench
|
||||
|
||||
```bash
|
||||
cargo bench --package quickjs
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
- Peter Malmgren https://github.com/pmalmgren/wasi-data-sharing
|
||||
- Shopify https://github.com/Shopify/javy
|
||||
- Bytecode Alliance https://github.com/bytecodealliance/wasmtime
|
||||
- Bytecode Alliance https://github.com/bytecodealliance/wizer
|
||||
|
||||
17
build-wasm.sh
Executable file
17
build-wasm.sh
Executable file
@@ -0,0 +1,17 @@
|
||||
export QUICKJS_WASM_SYS_WASI_SDK_PATH=$HOME/opt/wasi-sdk
|
||||
# Check that something is present where the user says the wasi-sdk is located
|
||||
if [ ! -d "$QUICKJS_WASM_SYS_WASI_SDK_PATH" ]; then
|
||||
wget https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-19/wasi-sdk-19.0-linux.tar.gz
|
||||
mkdir -p $QUICKJS_WASM_SYS_WASI_SDK_PATH
|
||||
tar xvf wasi-sdk-19.0-linux.tar.gz --strip-components=1 -C $QUICKJS_WASM_SYS_WASI_SDK_PATH
|
||||
rm wasi-sdk-19.0-linux.tar.gz
|
||||
fi
|
||||
# Build the base package
|
||||
cargo build --release --package quickjs-wasm --target wasm32-wasi
|
||||
# If wizer is not installed then install it
|
||||
if [ -z $(which wizer) ]
|
||||
then
|
||||
cargo install wizer --all-features
|
||||
fi
|
||||
# apply wizer optimisation
|
||||
wizer --allow-wasi target/wasm32-wasi/release/quickjs-wasm.wasm --wasm-bulk-memory true -o quickjs.wasm
|
||||
17
crates/quickjs-wasm/Cargo.toml
Normal file
17
crates/quickjs-wasm/Cargo.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[package]
|
||||
name = "quickjs-wasm"
|
||||
version = "0.1.0"
|
||||
authors = [""]
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
once_cell = "1.17.0"
|
||||
quickjs-wasm-rs = { version = "0.1.3", features = ["json"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
console = []
|
||||
36
crates/quickjs-wasm/src/context.rs
Normal file
36
crates/quickjs-wasm/src/context.rs
Normal file
@@ -0,0 +1,36 @@
|
||||
use anyhow::Result;
|
||||
use quickjs_wasm_rs::{Context, Value};
|
||||
use std::io::Write;
|
||||
|
||||
/// set quickjs globals
|
||||
pub fn set_quickjs_globals(context: &Context) -> anyhow::Result<()> {
|
||||
let global = context.global_object()?;
|
||||
let console_log_callback = context.wrap_callback(console_log_to(std::io::stdout()))?;
|
||||
let console_error_callback = context.wrap_callback(console_log_to(std::io::stderr()))?;
|
||||
let console_object = context.object_value()?;
|
||||
console_object.set_property("log", console_log_callback)?;
|
||||
console_object.set_property("error", console_error_callback)?;
|
||||
global.set_property("console", console_object)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// console_log_to is used to allow the javascript functions console.log and console.error to
|
||||
/// log to the stdout and stderr respectively.
|
||||
fn console_log_to<T>(mut stream: T) -> impl FnMut(&Context, &Value, &[Value]) -> Result<Value>
|
||||
where
|
||||
T: Write + 'static,
|
||||
{
|
||||
move |ctx: &Context, _this: &Value, args: &[Value]| {
|
||||
for (i, arg) in args.iter().enumerate() {
|
||||
if i != 0 {
|
||||
write!(stream, " ")?;
|
||||
}
|
||||
|
||||
stream.write_all(arg.as_str()?.as_bytes())?;
|
||||
}
|
||||
|
||||
writeln!(stream)?;
|
||||
ctx.undefined_value()
|
||||
}
|
||||
}
|
||||
68
crates/quickjs-wasm/src/io.rs
Normal file
68
crates/quickjs-wasm/src/io.rs
Normal file
@@ -0,0 +1,68 @@
|
||||
use anyhow::Result;
|
||||
use quickjs_wasm_rs::{json, Context, Value};
|
||||
|
||||
#[link(wasm_import_module = "host")]
|
||||
extern "C" {
|
||||
fn get_input(ptr: i32);
|
||||
fn get_input_size() -> i32;
|
||||
fn get_data(ptr: i32);
|
||||
fn get_data_size() -> i32;
|
||||
fn set_output(ptr: i32, size: i32);
|
||||
}
|
||||
|
||||
/// gets the input from the host as a string
|
||||
pub fn get_input_string() -> Result<Option<String>> {
|
||||
let input_size = unsafe { get_input_size() } as usize;
|
||||
|
||||
if input_size == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(input_size);
|
||||
let ptr = buf.as_mut_ptr();
|
||||
unsafe { get_input(ptr as i32) };
|
||||
|
||||
let input_buf = unsafe { Vec::from_raw_parts(ptr, input_size, input_size) };
|
||||
|
||||
Ok(Some(String::from_utf8(input_buf.to_vec())?))
|
||||
}
|
||||
}
|
||||
|
||||
/// gets the input from the host as a string
|
||||
pub fn get_input_value(context: &Context) -> Result<Option<Value>> {
|
||||
let input_size = unsafe { get_data_size() } as usize;
|
||||
|
||||
if input_size == 0 {
|
||||
Ok(None)
|
||||
} else {
|
||||
let mut buf: Vec<u8> = Vec::with_capacity(input_size);
|
||||
let ptr = buf.as_mut_ptr();
|
||||
unsafe { get_data(ptr as i32) };
|
||||
|
||||
let input_buf = unsafe { Vec::from_raw_parts(ptr, input_size, input_size) };
|
||||
|
||||
Ok(Some(json::transcode_input(context, &input_buf)?))
|
||||
}
|
||||
}
|
||||
|
||||
/// sets the output value on the host
|
||||
pub fn set_output_value(output: Option<Value>) -> Result<()> {
|
||||
match output {
|
||||
Some(output) if !output.is_undefined() => {
|
||||
let output = json::transcode_output(output)?;
|
||||
|
||||
let size = output.len() as i32;
|
||||
let ptr = output.as_ptr();
|
||||
|
||||
unsafe {
|
||||
set_output(ptr as i32, size);
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
unsafe {
|
||||
set_output(0, 0);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
43
crates/quickjs-wasm/src/main.rs
Normal file
43
crates/quickjs-wasm/src/main.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
#[cfg(feature = "console")]
|
||||
mod context;
|
||||
mod io;
|
||||
|
||||
use anyhow::Result;
|
||||
use once_cell::sync::OnceCell;
|
||||
use quickjs_wasm_rs::Context;
|
||||
|
||||
static mut JS_CONTEXT: OnceCell<Context> = OnceCell::new();
|
||||
static SCRIPT_NAME: &str = "script.js";
|
||||
|
||||
/// init() is executed by wizer to create a snapshot after the quickjs context has been initialized.
|
||||
///
|
||||
/// it also binds the console.log and console.error functions so they can be used for debugging in the
|
||||
/// user script.
|
||||
#[export_name = "wizer.initialize"]
|
||||
pub extern "C" fn init() {
|
||||
unsafe {
|
||||
let context = Context::default();
|
||||
|
||||
// add globals to the quickjs instance if enabled
|
||||
#[cfg(feature = "console")]
|
||||
context::set_quickjs_globals(&context).unwrap();
|
||||
|
||||
JS_CONTEXT.set(context).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
match io::get_input_string()? {
|
||||
Some(input) => {
|
||||
let context = unsafe { JS_CONTEXT.get_or_init(Context::default) };
|
||||
|
||||
if let Some(value) = io::get_input_value(context)? {
|
||||
context.global_object()?.set_property("data", value)?;
|
||||
}
|
||||
|
||||
let output = context.eval_global(SCRIPT_NAME, &input)?;
|
||||
io::set_output_value(Some(output))
|
||||
}
|
||||
None => io::set_output_value(None),
|
||||
}
|
||||
}
|
||||
20
crates/quickjs/Cargo.toml
Normal file
20
crates/quickjs/Cargo.toml
Normal file
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "quickjs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
wasi-common = "5.0.0"
|
||||
wasmtime = "5.0.0"
|
||||
wasmtime-wasi = "5.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
clap = { version = "4.1.2", features = ["derive"] }
|
||||
num_cpus = "1.15.0"
|
||||
rayon = "1.6.1"
|
||||
criterion = "0.4.0"
|
||||
|
||||
[[bench]]
|
||||
name = "benchmark"
|
||||
harness = false
|
||||
20
crates/quickjs/benches/benchmark.rs
Normal file
20
crates/quickjs/benches/benchmark.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use quickjs::QuickJS;
|
||||
|
||||
pub fn criterion_benchmark(c: &mut Criterion) {
|
||||
let quickjs = QuickJS::default();
|
||||
let script = include_str!("../../../track_points.js");
|
||||
let data = include_str!("../../../track_points.json");
|
||||
c.bench_function("try_execute", |b| {
|
||||
b.iter(|| {
|
||||
black_box(
|
||||
quickjs
|
||||
.try_execute(script, Some(data), false, false)
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
74
crates/quickjs/examples/iter.rs
Normal file
74
crates/quickjs/examples/iter.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
extern crate quickjs;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use quickjs::QuickJS;
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
|
||||
/// Simple program to demonstr
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the wasm module
|
||||
#[arg(short, long)]
|
||||
module: Option<PathBuf>,
|
||||
|
||||
/// Path to the input script
|
||||
#[arg(short, long)]
|
||||
script: Option<PathBuf>,
|
||||
|
||||
/// Path to the data json object
|
||||
#[arg(short, long)]
|
||||
data: Option<PathBuf>,
|
||||
|
||||
/// Number of iterations to execute
|
||||
#[arg(short, long, default_value_t = 1000)]
|
||||
iterations: usize,
|
||||
|
||||
/// Enable stdout (i.e. console.log) defualt false
|
||||
#[arg(short, long)]
|
||||
inherit_stdout: bool,
|
||||
|
||||
/// Enable stderr (i.e. console.error) default false
|
||||
#[arg(short, long)]
|
||||
inherit_stderr: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let quickjs = match args.module {
|
||||
Some(path) => QuickJS::try_from(path)?,
|
||||
None => QuickJS::default(),
|
||||
};
|
||||
|
||||
let script = match args.script {
|
||||
Some(path) => std::fs::read_to_string(path)?,
|
||||
None => include_str!("../../../track_points.js").to_string(),
|
||||
};
|
||||
|
||||
let data = match args.data {
|
||||
Some(path) => std::fs::read_to_string(path)?,
|
||||
None => include_str!("../../../track_points.json").to_string(),
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
for i in 0..args.iterations {
|
||||
let output = quickjs.try_execute(
|
||||
&script,
|
||||
Some(&data),
|
||||
args.inherit_stdout,
|
||||
args.inherit_stderr,
|
||||
)?;
|
||||
println!("{i} {}", output.unwrap_or_else(|| "None".to_string()));
|
||||
}
|
||||
|
||||
let duration = start.elapsed();
|
||||
println!(
|
||||
"elapsed: {:?}\niteration: {:?}",
|
||||
duration,
|
||||
duration.div_f32(args.iterations as f32)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
88
crates/quickjs/examples/par_iter.rs
Normal file
88
crates/quickjs/examples/par_iter.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
extern crate quickjs;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use quickjs::QuickJS;
|
||||
use rayon::prelude::*;
|
||||
use std::{path::PathBuf, time::Instant};
|
||||
|
||||
/// Simple program to demonstr
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
struct Args {
|
||||
/// Path to the wasm module
|
||||
#[arg(short, long)]
|
||||
module: Option<PathBuf>,
|
||||
|
||||
/// Path to the input script
|
||||
#[arg(short, long)]
|
||||
script: Option<PathBuf>,
|
||||
|
||||
/// Path to the data json object
|
||||
#[arg(short, long)]
|
||||
data: Option<PathBuf>,
|
||||
|
||||
/// Number of iterations to execute
|
||||
#[arg(short, long, default_value_t = 1000)]
|
||||
iterations: usize,
|
||||
|
||||
/// Enable stdout (i.e. console.log) default false
|
||||
#[arg(short, long)]
|
||||
inherit_stdout: bool,
|
||||
|
||||
/// Enable stderr (i.e. console.error) default false
|
||||
#[arg(short, long)]
|
||||
inherit_stderr: bool,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
let quickjs = match args.module {
|
||||
Some(path) => QuickJS::try_from(path)?,
|
||||
None => QuickJS::default(),
|
||||
};
|
||||
|
||||
let script = match args.script {
|
||||
Some(path) => std::fs::read_to_string(path)?,
|
||||
None => include_str!("../../../track_points.js").to_string(),
|
||||
};
|
||||
|
||||
let data = match args.data {
|
||||
Some(path) => std::fs::read_to_string(path)?,
|
||||
None => include_str!("../../../track_points.json").to_string(),
|
||||
};
|
||||
|
||||
let start = Instant::now();
|
||||
|
||||
(0..args.iterations)
|
||||
.collect::<Vec<_>>()
|
||||
.chunks(args.iterations / num_cpus::get())
|
||||
.collect::<Vec<_>>()
|
||||
.into_par_iter()
|
||||
.map(|chunk| {
|
||||
chunk
|
||||
.iter()
|
||||
.map(|i| {
|
||||
let output = quickjs.try_execute(
|
||||
&script,
|
||||
Some(&data),
|
||||
args.inherit_stdout,
|
||||
args.inherit_stderr,
|
||||
)?;
|
||||
println!("{i} {}", output.unwrap_or_else(|| "None".to_string()));
|
||||
Ok(())
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let duration = start.elapsed();
|
||||
println!(
|
||||
"elapsed: {:?}\niteration: {:?}",
|
||||
duration,
|
||||
duration.div_f32(args.iterations as f32)
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
173
crates/quickjs/src/lib.rs
Normal file
173
crates/quickjs/src/lib.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use wasi_common::WasiCtx;
|
||||
use wasmtime::*;
|
||||
use wasmtime_wasi::sync::WasiCtxBuilder;
|
||||
|
||||
pub struct QuickJS {
|
||||
engine: Engine,
|
||||
module: Module,
|
||||
}
|
||||
|
||||
impl Default for QuickJS {
|
||||
fn default() -> Self {
|
||||
let engine = Engine::default();
|
||||
let module = Module::from_binary(&engine, include_bytes!("../../../quickjs.wasm")).unwrap();
|
||||
Self { engine, module }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<PathBuf> for QuickJS {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(path: PathBuf) -> Result<Self, Self::Error> {
|
||||
let engine = Engine::default();
|
||||
let module = Module::from_file(&engine, path)?;
|
||||
Ok(Self { engine, module })
|
||||
}
|
||||
}
|
||||
|
||||
impl QuickJS {
|
||||
pub fn try_execute(
|
||||
&self,
|
||||
script: &str,
|
||||
data: Option<&str>,
|
||||
inherit_stdout: bool,
|
||||
inherit_stderr: bool,
|
||||
) -> Result<Option<String>> {
|
||||
let input = script.as_bytes().to_vec();
|
||||
let input_size = input.len() as i32;
|
||||
let data = data
|
||||
.map(|data| data.as_bytes().to_vec())
|
||||
.unwrap_or_default();
|
||||
let data_size = data.len() as i32;
|
||||
let output = Arc::new(Mutex::new(None));
|
||||
|
||||
let mut linker = Linker::new(&self.engine);
|
||||
wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
|
||||
|
||||
let mut wasi_ctx_builder = WasiCtxBuilder::new();
|
||||
if inherit_stdout {
|
||||
wasi_ctx_builder = wasi_ctx_builder.inherit_stdout();
|
||||
};
|
||||
if inherit_stderr {
|
||||
wasi_ctx_builder = wasi_ctx_builder.inherit_stderr();
|
||||
};
|
||||
|
||||
let wasi = wasi_ctx_builder.build();
|
||||
let mut store = Store::new(&self.engine, wasi);
|
||||
let memory_type = MemoryType::new(1, None);
|
||||
Memory::new(&mut store, memory_type)?;
|
||||
|
||||
linker.func_wrap(
|
||||
"host",
|
||||
"get_input_size",
|
||||
move |_: Caller<'_, WasiCtx>| -> Result<i32> { Ok(input_size) },
|
||||
)?;
|
||||
|
||||
linker.func_wrap(
|
||||
"host",
|
||||
"get_input",
|
||||
move |mut caller: Caller<'_, WasiCtx>, ptr: i32| -> Result<()> {
|
||||
let memory = match caller.get_export("memory") {
|
||||
Some(Extern::Memory(memory)) => memory,
|
||||
_ => return Err(anyhow!("failed to find host memory")),
|
||||
};
|
||||
let offset = ptr as u32 as usize;
|
||||
Ok(memory.write(&mut caller, offset, &input)?)
|
||||
},
|
||||
)?;
|
||||
|
||||
linker.func_wrap(
|
||||
"host",
|
||||
"get_data_size",
|
||||
move |_: Caller<'_, WasiCtx>| -> Result<i32> { Ok(data_size) },
|
||||
)?;
|
||||
|
||||
linker.func_wrap(
|
||||
"host",
|
||||
"get_data",
|
||||
move |mut caller: Caller<'_, WasiCtx>, ptr: i32| -> Result<()> {
|
||||
let memory = match caller.get_export("memory") {
|
||||
Some(Extern::Memory(memory)) => memory,
|
||||
_ => return Err(anyhow!("failed to find host memory")),
|
||||
};
|
||||
let offset = ptr as u32 as usize;
|
||||
Ok(memory.write(&mut caller, offset, &data)?)
|
||||
},
|
||||
)?;
|
||||
|
||||
let output_clone = output.clone();
|
||||
linker.func_wrap(
|
||||
"host",
|
||||
"set_output",
|
||||
move |mut caller: Caller<'_, WasiCtx>, ptr: i32, capacity: i32| -> Result<()> {
|
||||
let mut output = output_clone.lock().unwrap();
|
||||
|
||||
*output = if capacity == 0 {
|
||||
None
|
||||
} else {
|
||||
let memory = match caller.get_export("memory") {
|
||||
Some(Extern::Memory(memory)) => memory,
|
||||
_ => return Err(anyhow!("failed to find host memory")),
|
||||
};
|
||||
let offset = ptr as u32 as usize;
|
||||
let mut buffer: Vec<u8> = vec![0; capacity as usize];
|
||||
memory.read(&caller, offset, &mut buffer)?;
|
||||
Some(String::from_utf8(buffer)?)
|
||||
};
|
||||
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
linker.module(&mut store, "", &self.module)?;
|
||||
|
||||
// call the default function i.e. main()
|
||||
linker
|
||||
.get_default(&mut store, "")?
|
||||
.typed::<(), ()>(&store)?
|
||||
.call(&mut store, ())?;
|
||||
|
||||
let output = output.lock().unwrap();
|
||||
Ok(output.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn try_execute() {
|
||||
let quickjs = QuickJS::default();
|
||||
|
||||
let script = r#"
|
||||
'quickjs' + 'wasm'
|
||||
"#;
|
||||
|
||||
let result = quickjs.try_execute(script, None, false, false).unwrap();
|
||||
|
||||
assert_eq!(result, Some("\"quickjswasm\"".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn try_execute_data() {
|
||||
let quickjs = QuickJS::default();
|
||||
|
||||
let script = r#"
|
||||
'quickjs' + data.input
|
||||
"#;
|
||||
|
||||
let data = r#"{"input": "wasm"}"#;
|
||||
|
||||
let result = quickjs
|
||||
.try_execute(script, Some(data), false, false)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result, Some("\"quickjswasm\"".to_string()));
|
||||
}
|
||||
}
|
||||
BIN
quickjs.wasm
Normal file
BIN
quickjs.wasm
Normal file
Binary file not shown.
39
track_points.js
Normal file
39
track_points.js
Normal file
@@ -0,0 +1,39 @@
|
||||
// simple approximate distance function returning distance between two points in meters
|
||||
function distance(lat0, lon0, lat1, lon1) {
|
||||
if ((lat0 == lat1) && (lon0 == lon1)) {
|
||||
return 0;
|
||||
} else {
|
||||
const radlat0 = Math.PI * lat0 / 180;
|
||||
const radlat1 = Math.PI * lat1 / 180;
|
||||
const theta = lon0 - lon1;
|
||||
const radtheta = Math.PI * theta / 180;
|
||||
let dist = Math.sin(radlat0) * Math.sin(radlat1) + Math.cos(radlat0) * Math.cos(radlat1) * Math.cos(radtheta);
|
||||
if (dist > 1) {
|
||||
dist = 1;
|
||||
}
|
||||
dist = Math.acos(dist);
|
||||
dist = dist * 180 / Math.PI;
|
||||
return dist * 60 * 1853.159;
|
||||
}
|
||||
}
|
||||
|
||||
// calculate the total length of a set of input features in canadian football fields
|
||||
function calculate(data) {
|
||||
// canadian football fields are 140 meters
|
||||
const candadian_football_field = 140;
|
||||
|
||||
return data.features.reduce(
|
||||
(accumulator, currentValue, currentIndex, array) => {
|
||||
if (currentIndex == 0) {
|
||||
return 0
|
||||
} else {
|
||||
const previousValue = array[currentIndex - 1];
|
||||
const dist = distance(currentValue.geometry.coordinates[1], currentValue.geometry.coordinates[0], previousValue.geometry.coordinates[1], previousValue.geometry.coordinates[0]);
|
||||
return accumulator + dist / candadian_football_field
|
||||
}
|
||||
},
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
calculate(data)
|
||||
4432
track_points.json
Normal file
4432
track_points.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user