diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ade05d5..ff2af0b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -15,6 +15,9 @@ jobs: clippy: runs-on: ubuntu-latest + strategy: + matrix: + features: ["--all-features", "--no-default-features --features serde,codec-pot"] steps: - uses: actions/checkout@v4 @@ -22,17 +25,20 @@ jobs: with: components: clippy - uses: Swatinem/rust-cache@v2 - - run: cargo clippy --all-features -- -D warnings + - run: cargo clippy ${{ matrix.features }} -- -D warnings doc-test: runs-on: ubuntu-latest + strategy: + matrix: + features: ["--all-features", "--no-default-features --features serde,macros,codec-pot"] steps: - uses: actions/checkout@v4 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run doctest - run: cargo test --doc --all-features + run: cargo test --doc ${{ matrix.features }} test: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 1516d62..bd08f42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,6 +44,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "embedded-io" version = "0.4.0" @@ -158,6 +164,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "zerocopy", +] + [[package]] name = "hash32" version = "0.2.1" @@ -249,6 +266,17 @@ dependencies = [ "serde", ] +[[package]] +name = "pot" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf741fa415952eb20f27fbc210dc85f31cc7cdc80aa3ce81d5e27d28a6f45dc2" +dependencies = [ + "byteorder", + "half", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -530,6 +558,7 @@ dependencies = [ "js-sys", "log", "postcard", + "pot", "send_wrapper", "serde", "serde-wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index aa6bf70..5f312e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ keywords.workspace = true [dependencies] futures = "0.3" js-sys = { version = "0.3" } -postcard = { version = "1.1", features = ["alloc"] } send_wrapper = "0.6" serde = { version = "1.0", features = ["derive"] } serde_bytes = "0.11" @@ -40,6 +39,8 @@ tokio = { version = "1.4", features = ["sync"] } wasm-bindgen = "0.2" wasm-bindgen-futures = "0.4" log = "0.4" +postcard = { version = "1.1", features = ["alloc"], optional = true } +pot = { version = "3.0.1", optional = true } [dependencies.web-sys] features = [ @@ -63,8 +64,10 @@ version = "0.3" wasmworker-proc-macro = { workspace = true } [features] -default = ["serde"] +default = ["serde", "codec-postcard"] serde = [] +codec-postcard = ["dep:postcard"] +codec-pot = ["dep:pot"] macros = ["wasmworker-proc-macro"] [dependencies.wasmworker-proc-macro] diff --git a/README.md b/README.md index 2387f6f..e83fc98 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ In contrast to many other libraries like [wasm-bindgen-rayon](https://github.com - [Usage](#usage) - [Setting up](#setting-up) + - [Serialization codec](#serialization-codec) - [Outsourcing tasks](#outsourcing-tasks) - [WebWorker](#webworker) - [WebWorkerPool](#webworkerpool) @@ -38,6 +39,18 @@ The `wasmworker` crate comes with a default feature called `serde`, which allows Without the `serde` feature, only functions with the type `fn(Box<[u8]>) -> Box<[u8]>` can be run on a worker. This is useful for users that do not want a direct serde dependency. Internally, the library always uses serde, though. +#### Serialization codec +By default, `wasmworker` uses [postcard](https://crates.io/crates/postcard) for internal serialization. +Postcard is compact and fast, making it ideal for the typical WebWorker use case (passing `Vec`, structs, primitives). + +For complex types like `Rc` or cyclic structures, you can use [pot](https://crates.io/crates/pot) instead. +Note that pot has significantly higher serialization overhead and larger output sizes, so it should only be used when postcard cannot handle your data types. + +```toml +[dependencies] +wasmworker = { version = "0.3", default-features = false, features = ["serde", "macros", "codec-pot"] } +``` + You can then start using the library without further setup. If you plan on using the global `WebWorkerPool` (using the iterator extensions or `worker_pool()`), you can *optionally* configure this pool: ```rust diff --git a/src/channel_task.rs b/src/channel_task.rs index 1205f5b..23f26f2 100644 --- a/src/channel_task.rs +++ b/src/channel_task.rs @@ -34,7 +34,8 @@ pub struct ChannelTask { impl ChannelTask { /// Create a new `ChannelTask` from a channel and a result receiver. - pub(crate) fn new(channel: Channel, result_rx: oneshot::Receiver>) -> Self { + #[doc(hidden)] + pub fn new(channel: Channel, result_rx: oneshot::Receiver>) -> Self { Self { channel, result_rx, diff --git a/src/convert.rs b/src/convert.rs index 0b1be58..d525a64 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; /// This wrapper function encapsulates our internal serialization format. /// It is used internally to prepare values before sending them to a worker /// or back to the main thread via `postMessage`. +#[cfg(feature = "codec-postcard")] pub fn to_bytes(value: &T) -> Box<[u8]> { postcard::to_allocvec(value) .expect("WebWorker serialization failed") @@ -12,6 +13,34 @@ pub fn to_bytes(value: &T) -> Box<[u8]> { /// This wrapper function encapsulates our internal serialization format. /// It is used internally to prepare values after receiving them from a worker /// or the main thread via `postMessage`. +#[cfg(feature = "codec-postcard")] pub fn from_bytes<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> T { postcard::from_bytes(bytes).expect("WebWorker deserialization failed") } + +#[cfg(all(feature = "codec-pot", not(feature = "codec-postcard")))] +const POT_CONFIG: pot::Config = pot::Config::new().compatibility(pot::Compatibility::V4); + +/// This wrapper function encapsulates our internal serialization format. +/// It is used internally to prepare values before sending them to a worker +/// or back to the main thread via `postMessage`. +#[cfg(all(feature = "codec-pot", not(feature = "codec-postcard")))] +pub fn to_bytes(value: &T) -> Box<[u8]> { + POT_CONFIG + .serialize(value) + .expect("WebWorker serialization failed") + .into() +} + +/// This wrapper function encapsulates our internal serialization format. +/// It is used internally to prepare values after receiving them from a worker +/// or the main thread via `postMessage`. +#[cfg(all(feature = "codec-pot", not(feature = "codec-postcard")))] +pub fn from_bytes<'de, T: Deserialize<'de>>(bytes: &'de [u8]) -> T { + POT_CONFIG + .deserialize(bytes) + .expect("WebWorker deserialization failed") +} + +#[cfg(not(any(feature = "codec-postcard", feature = "codec-pot")))] +compile_error!("No codec selected. Enable `codec-postcard` (default) or `codec-pot`.");