From 82c81ad15348246999a133a3ecf67a956273f8e0 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 10 Sep 2026 10:56:00 -0700 Subject: [PATCH 1/6] Use released socket2 and wasm-streams rlib-only branch Drop the wasm-streams checkout and cdylib patch in favor of guybedford/wasm-streams (MattiasBuelens/wasm-streams PR pending). The socket2 git rev was exactly the v0.6.5 release. --- AGENTS.md | 2 +- Cargo.lock | 4 +++- Cargo.toml | 3 +-- docs/dependencies.md | 4 +--- patches/README.md | 1 - patches/wasm-streams-rlib.patch | 25 ------------------------- scripts/setup.sh | 2 -- 7 files changed, 6 insertions(+), 35 deletions(-) delete mode 100644 patches/wasm-streams-rlib.patch diff --git a/AGENTS.md b/AGENTS.md index a5418ab..c56eaa3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -19,7 +19,7 @@ process to free a port. ## Reproducibility and data -Changes to patched checkouts (Pumpkin, wasm-bindgen, workers-rs, wasm-streams) must +Changes to patched checkouts (Pumpkin, wasm-bindgen, workers-rs) must be reflected in `patches/`. Keep unpatched checkouts unmodified; update pins for upstream changes. Preserve the unified ticker and cooperative scheduler unless the task requires a runtime change. Do not reintroduce old Tokio/libc networking diff --git a/Cargo.lock b/Cargo.lock index 30dfaff..b0c9c8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2421,7 +2421,8 @@ checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" [[package]] name = "socket2" version = "0.6.5" -source = "git+https://github.com/rust-lang/socket2?rev=239dd83a4ced08e514d2c38942aab99791119f0d#239dd83a4ced08e514d2c38942aab99791119f0d" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", "windows-sys", @@ -2938,6 +2939,7 @@ dependencies = [ [[package]] name = "wasm-streams" version = "0.6.0" +source = "git+https://github.com/guybedford/wasm-streams?rev=115f0f27380a4f5fa33cbd9427a7107ec94cb557#115f0f27380a4f5fa33cbd9427a7107ec94cb557" dependencies = [ "futures-util", "js-sys", diff --git a/Cargo.toml b/Cargo.toml index ce459aa..513f051 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,11 +40,10 @@ wasm-bindgen-futures = { path = ".work/wasm-bindgen/crates/futures" } wasm-bindgen-macro-support = { path = ".work/wasm-bindgen/crates/macro-support" } wasm-bindgen-shared = { path = ".work/wasm-bindgen/crates/shared" } web-sys = { path = ".work/wasm-bindgen/crates/web-sys" } -wasm-streams = { path = ".work/wasm-streams" } +wasm-streams = { git = "https://github.com/guybedford/wasm-streams", rev = "115f0f27380a4f5fa33cbd9427a7107ec94cb557" } libc = { path = ".work/libc" } mio = { git = "https://github.com/guybedford/mio", rev = "a62c9e46833fc255c9217ab9aa362c6221ed4401" } ring = { path = ".work/ring" } -socket2 = { git = "https://github.com/rust-lang/socket2", rev = "239dd83a4ced08e514d2c38942aab99791119f0d" } tokio = { path = ".work/tokio/tokio" } tokio-macros = { path = ".work/tokio/tokio-macros" } diff --git a/docs/dependencies.md b/docs/dependencies.md index 6dbb09c..ee9c865 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -14,7 +14,6 @@ are ignored under `.work/`. Both Cargo and npm dependency graphs are locked. | ring | [emscripten](https://github.com/guybedford/ring) | `6671f7cfbb13f249b571ffa6326275a8596e0ca2` | Portable Emscripten crypto backend; unmodified | | pumpkin | [master](https://github.com/Pumpkin-MC/Pumpkin) | `b5b9b9d7010e793806a83c495af223c67e1d35ee` | Headless embedding, shared async scheduler, compact templates/generation chunks, and build-script fixes | | workers-rs | [connect-bindings](https://github.com/ThomasRubini/workers-rs) | `7db011ec97658a5d907f3e3102028ce86c044f19` | TCP ingress PR #1041; pinned ABI, Emscripten Tokio promise adapter, host-owned initialization, and socket shutdown flushing | -| wasm-streams | [v0.6.0](https://github.com/MattiasBuelens/wasm-streams) | `35665f7b1da830b5ac51b4c6c3ff13f5c1a09ccb` | Build only the Rust library; its standalone `cdylib` is incompatible with static Emscripten linking | Patches are relative to the pinned commits above. Setup checks reverse application before applying a patch and refuses to repin a modified checkout. @@ -29,7 +28,7 @@ commit. | Crate | Source | Commit | | --- | --- | --- | | mio | https://github.com/guybedford/mio | `a62c9e46833fc255c9217ab9aa362c6221ed4401` | -| socket2 | https://github.com/rust-lang/socket2 | `239dd83a4ced08e514d2c38942aab99791119f0d` | +| wasm-streams | https://github.com/guybedford/wasm-streams | `115f0f27380a4f5fa33cbd9427a7107ec94cb557` | The root Cargo.toml applies these overrides and the local checkouts. Cargo.lock pins the full application graph. `toolchain/wasm-bindgen.Cargo.lock` separately pins @@ -61,7 +60,6 @@ git -C .work/pumpkin diff b5b9b9d7010e793806a83c495af223c67e1d35ee -- \ crates/pumpkin-world/src/generation > .work/pumpkin-memory.diff git -C .work/wasm-bindgen diff 4b69f3b3ba4212c857be6854f77fa5aec8b62871 -- crates/cli-support/src/js/mod.rs > .work/wasm-bindgen-emscripten-closures.diff git -C .work/workers-rs diff 7db011ec97658a5d907f3e3102028ce86c044f19 > .work/workers-rs-emscripten-toolchain.diff -git -C .work/wasm-streams diff 35665f7b1da830b5ac51b4c6c3ff13f5c1a09ccb > .work/wasm-streams-rlib.diff ``` Replace the corresponding patch's contents from its first `diff --git` line onward diff --git a/patches/README.md b/patches/README.md index 945c45b..86af881 100644 --- a/patches/README.md +++ b/patches/README.md @@ -13,7 +13,6 @@ diff below it. Preserve the description when regenerating the diff. | `pumpkin-memory.patch` | `.work/pumpkin` | Shared indexed structure templates, immutable block-entity NBT, paletted generation chunks, and a temporary dense noise buffer | | `wasm-bindgen-emscripten-closures.patch` | `.work/wasm-bindgen` | Escape generated closure-finalizer postsets as JavaScript strings | | `workers-rs-emscripten-toolchain.patch` | `.work/workers-rs` | Match the pinned wasm-bindgen ABI, provide the Emscripten Tokio promise adapter, leave initialization/recovery to the host, and flush pending writes before socket shutdown | -| `wasm-streams-rlib.patch` | `.work/wasm-streams` | Omit the standalone cdylib when embedding the stream adapter in Emscripten | `pumpkin-emscripten.patch` includes the injected-stream connection entry point used by the DO. Setup applies it before `pumpkin-memory.patch`. The two patches use the diff --git a/patches/wasm-streams-rlib.patch b/patches/wasm-streams-rlib.patch deleted file mode 100644 index 8afa042..0000000 --- a/patches/wasm-streams-rlib.patch +++ /dev/null @@ -1,25 +0,0 @@ -Subject: [PATCH] Build wasm-streams as an rlib for Emscripten - -The embedded application links wasm-streams into its final WebAssembly -module. Declaring both rlib and cdylib also makes Cargo attempt a standalone -library link, which is incompatible with this static Emscripten build. - -Build only the rlib so the application supplies the final link and host -bindings. The Rust stream adapter API is unchanged. - -Base-commit: 35665f7b1da830b5ac51b4c6c3ff13f5c1a09ccb - ---- -diff --git a/Cargo.toml b/Cargo.toml -index c57b1c6..52f6805 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -16,7 +16,7 @@ exclude = [ - ] - - [lib] --crate-type = ["cdylib", "rlib"] -+crate-type = ["rlib"] - - [dependencies] - js-sys = "^0.3.85" diff --git a/scripts/setup.sh b/scripts/setup.sh index a640927..cdb584e 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -50,12 +50,10 @@ checkout libc https://github.com/guybedford/libc libc-0.2-emscripten 4091fe0b0dc checkout ring https://github.com/guybedford/ring emscripten 6671f7cfbb13f249b571ffa6326275a8596e0ca2 checkout pumpkin https://github.com/Pumpkin-MC/Pumpkin master b5b9b9d7010e793806a83c495af223c67e1d35ee checkout workers-rs https://github.com/ThomasRubini/workers-rs connect-bindings 7db011ec97658a5d907f3e3102028ce86c044f19 -checkout wasm-streams https://github.com/MattiasBuelens/wasm-streams main 35665f7b1da830b5ac51b4c6c3ff13f5c1a09ccb apply_patch wasm-bindgen wasm-bindgen-emscripten-closures.patch apply_patch pumpkin pumpkin-emscripten.patch apply_patch pumpkin pumpkin-memory.patch apply_patch workers-rs workers-rs-emscripten-toolchain.patch -apply_patch wasm-streams wasm-streams-rlib.patch if [ "${1:-}" = --sources-only ]; then exit 0; fi From 4cb27347a0478cdcb33f7788db975cde37127dd9 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 10 Sep 2026 19:32:49 -0700 Subject: [PATCH 2/6] Run Pumpkin bottom-up on stock Tokio under JSPI One perpetual `#[wasm_bindgen(jspi)]` export builds a current-thread runtime and `block_on`s the whole server lifetime; every park suspends the Wasm stack on `epoll_wait`, so the hosted runtime adapter is gone. Pumpkin binds its stock `TcpListener` on 25565 inside the Durable Object's port table, and the object routes each inbound socket to it with `handleAsNodeConnection`, replacing the injected-stream entry point, wasm-streams, and the workers-rs dependency. `stop` cancels the server and the run promise settling is the checkpoint signal. `-sREENTRANT_JSPI` gives each activation its own shadow stack, so other entries into the module while the server is suspended cannot clobber its frames. Toolchain: Rust beta; emscripten main plus the JSPI hooks, reentrant JSPI and epoll listener PRs, with the paired emscripten-releases LLVM and the jspi-hooks Binaryen branch built by setup; wasm-bindgen 0.2.128 CLI via `-sWASM_BINDGEN`; exnref exception handling throughout; tokio `emscripten-epoll`, mio tokio-rs/mio#1969, libc `libc-0.2`. rustc needs a larger compile-thread stack for pumpkin-data. The wasm-bindgen and workers-rs patches and the CLI lockfile are gone; the Pumpkin patch drops the injected-stream entry point. Requires a workerd with per-Durable-Object port tables and `net.Server` inbound routing (`MINIFLARE_WORKERD_PATH`). CI moves to Linux. --- .cargo/config.toml | 15 +- .github/workflows/ci.yml | 13 +- AGENTS.md | 7 +- Cargo.lock | 446 +- Cargo.toml | 25 +- README.md | 24 +- docs/architecture.md | 45 +- docs/dependencies.md | 62 +- docs/development.md | 3 +- patches/README.md | 5 +- patches/pumpkin-emscripten.patch | 481 +- .../wasm-bindgen-emscripten-closures.patch | 30 - patches/workers-rs-emscripten-toolchain.patch | 184 - rust-toolchain.toml | 2 +- scripts/common.sh | 22 +- scripts/setup.sh | 75 +- src/config.rs | 5 +- src/main.rs | 108 +- tests/fixtures/filesystem.rs | 53 +- tests/fixtures/worker.mjs | 20 +- tests/integration.mjs | 2 +- toolchain/wasm-bindgen.Cargo.lock | 4510 ----------------- worker/index.ts | 3 +- worker/modules.d.ts | 12 +- worker/pumpkin.ts | 21 +- worker/world.ts | 1 + 26 files changed, 559 insertions(+), 5615 deletions(-) delete mode 100644 patches/wasm-bindgen-emscripten-closures.patch delete mode 100644 patches/workers-rs-emscripten-toolchain.patch delete mode 100644 toolchain/wasm-bindgen.Cargo.lock diff --git a/.cargo/config.toml b/.cargo/config.toml index aceda7e..ddb88b3 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -4,16 +4,23 @@ target-dir = "target/workers" [target.wasm32-unknown-emscripten] rustflags = [ - # Static linking and unwind semantics are required by the hosted runtime. "-Crelocation-model=static", - "--cfg=tokio_unstable", - "-Clink-arg=-sWASM_BINDGEN=auto", + # exnref exception handling throughout: the C side gets `-fwasm-exceptions + # -sWASM_LEGACY_EXCEPTIONS=0` from EMCC_CFLAGS (scripts/common.sh), emcc + # translates the prebuilt std at link, and wasm-bindgen's JSPI wrappers use + # try_table. V8 rejects a module mixing the two. + "-Cllvm-args=-wasm-use-legacy-eh=false", + # Link-only settings stay out of EMCC_CFLAGS so they do not reach C compiles. + "-Clink-arg=-sWASM_BINDGEN", + "-Clink-arg=-sJSPI", + # Each promising activation runs on its own shadow stack, so a request or + # timer entering the module while the server is suspended cannot clobber it. + "-Clink-arg=-sREENTRANT_JSPI", "-Clink-arg=-sEXPORT_ES6", # Generated host glue uses Workers' Node compatibility APIs. "-Clink-arg=-sENVIRONMENT=node", "-Clink-arg=-sNODERAWSOCKETS", "-Clink-arg=-sNODERAWFS", - "-Clink-arg=-sJSPI", "-Clink-arg=-sASSERTIONS=0", "-Clink-arg=-sSTACK_SIZE=8MB", "-Clink-arg=-sALLOW_MEMORY_GROWTH=1", diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3bb185c..4778200 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,8 +15,8 @@ concurrency: jobs: smoke: - runs-on: macos-14 - timeout-minutes: 75 + runs-on: ubuntu-latest + timeout-minutes: 90 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 @@ -25,18 +25,21 @@ jobs: - uses: actions/setup-python@v5 with: python-version: '3.13' - - name: Install Emscripten backend - run: brew install emscripten + - run: sudo apt-get install -y ninja-build - uses: actions/cache@v4 with: path: | ~/.cargo/registry ~/.cargo/git - key: minecraft-cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml') }} + .work/emsdk + .work/binaryen/build + key: minecraft-toolchain-${{ runner.os }}-${{ hashFiles('Cargo.lock', 'rust-toolchain.toml', 'scripts/setup.sh') }} - name: Setup pinned sources and toolchain run: bash scripts/setup.sh - name: Install Worker dependencies run: npm ci + # Inbound net.Server routing inside Durable Objects is not yet in Wrangler's + # bundled workerd; set MINIFLARE_WORKERD_PATH to a build that has it. - name: Worker TCP, shared world, and SQLite restart run: | set -o pipefail diff --git a/AGENTS.md b/AGENTS.md index c56eaa3..1096947 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,13 +5,13 @@ Read [README.md](README.md), [architecture](docs/architecture.md), and ## Project layout -- `src/`: Pumpkin entry points and configuration. The hosted Tokio bridge lives in workers-rs. +- `src/`: Pumpkin entry points and configuration. - `worker/`: TCP ingress, Durable Object lifecycle, and SQLite filesystem. - `tests/`: protocol clients, integration tests, and isolated filesystem fixtures. - `scripts/setup.sh`: provision pinned sources and build the toolchain. - `scripts/{build,serve,test}.sh`: build, run, and validate the Workers server. -Use Node 24+ (26 recommended) and the pinned Rust nightly. Run `npm test` after +Use Node 24+ (26 recommended) and the pinned Rust toolchain. Run `npm test` after build/runtime changes; it must print `PUMPKIN-DO-SQLITE-RESTART-OK` after verifying player and chunk restoration. Keep test fixtures out of the production bundle. Scripts bind to loopback and fail if their ports are occupied. Do not kill another @@ -19,8 +19,7 @@ process to free a port. ## Reproducibility and data -Changes to patched checkouts (Pumpkin, wasm-bindgen, workers-rs) must -be reflected in `patches/`. Keep unpatched checkouts unmodified; update pins for +Changes to the patched Pumpkin checkout must be reflected in `patches/`. Keep unpatched checkouts unmodified; update pins for upstream changes. Preserve the unified ticker and cooperative scheduler unless the task requires a runtime change. Do not reintroduce old Tokio/libc networking patches. diff --git a/Cargo.lock b/Cargo.lock index b0c9c8a..da0cad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -21,9 +21,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.4" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" dependencies = [ "memchr", ] @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.44" +version = "0.4.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "515a1f282e33d55983c499d7e9e87082e81cbc32974825bf9032f928392d5844" +checksum = "4f10dafd0c8d2e51ae9a748805777613ed0bbe17bf586b76c8311f45c020a32f" dependencies = [ "compression-codecs", "compression-core", @@ -51,9 +51,9 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.91" +version = "0.1.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" dependencies = [ "proc-macro2", "quote", @@ -107,9 +107,9 @@ checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bitflags" -version = "2.13.1" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "block-buffer" @@ -164,9 +164,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.3.0" +version = "1.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" dependencies = [ "find-msvc-tools", "shlex", @@ -195,26 +195,15 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" -version = "0.10.1" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" dependencies = [ "cfg-if", "cpufeatures", "rand_core", ] -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "js-sys", - "num-traits", - "wasm-bindgen", -] - [[package]] name = "cipher" version = "0.5.2" @@ -261,9 +250,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.39" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fe67f2944eef52fc7b106b8c9450d243a88701a0c065f7f57235e76abaed7df" +checksum = "58a6d0db8759036a783bc7c3f7a07f8cef3bf9470eb1db3bc86e8bcd1c5d0fe8" dependencies = [ "compression-core", "flate2", @@ -305,9 +294,9 @@ checksum = "15b85f9c39137c3a891689859392b1bd49812121d0d61c9caf00d46ed5ce06ae" [[package]] name = "cpufeatures" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" dependencies = [ "libc", ] @@ -324,9 +313,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" dependencies = [ "cfg-if", ] @@ -339,9 +328,9 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" [[package]] name = "crossbeam" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" +checksum = "e71406cd8807725f7ac2f999a4cdd32e98f829fdf65f528343cebf945e41df1e" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -352,18 +341,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.16" +version = "0.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" +checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -371,27 +360,27 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.20" +version = "0.9.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" -version = "0.3.13" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26" +checksum = "03e8bd762f7479489c70ed6c768ddca99d7296857de437a68dcb2a94365b3fae" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.22" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" [[package]] name = "crypto-bigint" @@ -493,9 +482,9 @@ dependencies = [ [[package]] name = "der" -version = "0.8.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" +checksum = "a878c850e9e421b20262e9b41f9c860e4785fa07541c266b62ff9d1ef998a80a" dependencies = [ "const-oid", "pem-rfc7468", @@ -532,13 +521,13 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -579,9 +568,9 @@ dependencies = [ [[package]] name = "either" -version = "1.16.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" [[package]] name = "elliptic-curve" @@ -654,9 +643,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" [[package]] name = "flate2" @@ -686,9 +675,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -701,9 +690,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -711,15 +700,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -728,38 +717,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -807,9 +796,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.15" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +checksum = "ef8e5e5a340588f4452631496976cf8636d4a7ecf600239fdc27615d2530bc16" dependencies = [ "atomic-waker", "bytes", @@ -882,9 +871,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" dependencies = [ "bytes", "itoa", @@ -902,9 +891,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -921,9 +910,9 @@ checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "hybrid-array" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +checksum = "27f864f10dfb56725ce5ce5472bc52252c8f93a4ab86327122cebf62c5f59a17" dependencies = [ "subtle", "typenum", @@ -932,9 +921,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" dependencies = [ "atomic-waker", "bytes", @@ -991,9 +980,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -1005,9 +994,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -1018,9 +1007,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -1032,16 +1021,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -1052,15 +1042,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" dependencies = [ "displaydoc", "icu_locale_core", @@ -1107,9 +1097,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.14.0" +version = "2.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" dependencies = [ "equivalent", "hashbrown 0.17.1", @@ -1126,9 +1116,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.12.1" +version = "2.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a756c3fac73139e83f14c2d742155dd2b78d3ee56597b419a0579b7bdd6dd78" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" [[package]] name = "itertools" @@ -1196,7 +1186,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.103" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -1212,12 +1204,13 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.189" +source = "git+https://github.com/rust-lang/libc?branch=libc-0.2#31503352774deadf65cc53257b59ee4f3299f644" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "lock_api" @@ -1230,9 +1223,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.33" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" [[package]] name = "lz4-java-wrc" @@ -1259,12 +1252,6 @@ dependencies = [ "regex-automata", ] -[[package]] -name = "matchit" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" - [[package]] name = "md-5" version = "0.10.6" @@ -1496,26 +1483,6 @@ dependencies = [ "siphasher", ] -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1569,9 +1536,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -1845,7 +1812,6 @@ dependencies = [ "rayon", "tokio", "wasm-bindgen", - "worker", ] [[package]] @@ -1950,9 +1916,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.16" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" dependencies = [ "aho-corasick", "memchr", @@ -1967,11 +1933,11 @@ checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" -version = "0.13.4" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" dependencies = [ - "base64 0.22.1", + "base64 0.23.1", "bytes", "futures-core", "h2", @@ -2014,7 +1980,8 @@ dependencies = [ [[package]] name = "ring" -version = "0.17.14" +version = "0.17.16000" +source = "git+https://github.com/guybedford/ring?branch=emscripten#a1930ce8d9b8006e5060eda14cacf7cf6f7401c1" dependencies = [ "cc", "cfg-if", @@ -2069,9 +2036,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.42" +version = "0.23.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" dependencies = [ "log", "once_cell", @@ -2096,9 +2063,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" dependencies = [ "zeroize", ] @@ -2132,9 +2099,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" dependencies = [ "ring", "rustls-pki-types", @@ -2153,12 +2120,6 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888" -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - [[package]] name = "same-file" version = "1.0.6" @@ -2236,17 +2197,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - [[package]] name = "serde_core" version = "1.0.229" @@ -2300,18 +2250,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - [[package]] name = "serdect" version = "0.4.3" @@ -2414,9 +2352,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.2" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" [[package]] name = "socket2" @@ -2465,27 +2403,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strum" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" -dependencies = [ - "strum_macros", -] - -[[package]] -name = "strum_macros" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - [[package]] name = "subtle" version = "2.6.1" @@ -2536,18 +2453,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "2.0.19" +version = "2.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" dependencies = [ "proc-macro2", "quote", @@ -2565,9 +2482,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.54" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" dependencies = [ "deranged", "libc", @@ -2597,9 +2514,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -2607,7 +2524,7 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.3" +version = "1.53.1" dependencies = [ "bytes", "libc", @@ -2620,11 +2537,11 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.7.0" +version = "2.7.2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] @@ -2654,9 +2571,9 @@ dependencies = [ [[package]] name = "toml" -version = "1.1.5+spec-1.1.0" +version = "1.1.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" dependencies = [ "serde_core", "serde_spanned", @@ -2849,9 +2766,9 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "uuid" -version = "1.26.0" +version = "1.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" dependencies = [ "getrandom", "js-sys", @@ -2893,7 +2810,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.126" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -2904,7 +2823,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.76" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", "wasm-bindgen", @@ -2912,7 +2833,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.126" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2920,37 +2843,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.126" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.126" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] -[[package]] -name = "wasm-streams" -version = "0.6.0" -source = "git+https://github.com/guybedford/wasm-streams?rev=115f0f27380a4f5fa33cbd9427a7107ec94cb557#115f0f27380a4f5fa33cbd9427a7107ec94cb557" -dependencies = [ - "futures-util", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - [[package]] name = "web-sys" -version = "0.3.103" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -3007,64 +2924,11 @@ dependencies = [ "primefield", ] -[[package]] -name = "worker" -version = "0.8.5" -dependencies = [ - "async-trait", - "bytes", - "chrono", - "futures-channel", - "futures-util", - "http", - "http-body", - "js-sys", - "matchit", - "pin-project", - "serde", - "serde-wasm-bindgen", - "serde_json", - "serde_urlencoded", - "strum", - "tokio", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-streams", - "web-sys", - "worker-macros", - "worker-sys", -] - -[[package]] -name = "worker-macros" -version = "0.8.5" -dependencies = [ - "async-trait", - "proc-macro2", - "quote", - "strum", - "syn 3.0.5", - "wasm-bindgen", - "wasm-bindgen-macro-support", - "worker-sys", -] - -[[package]] -name = "worker-sys" -version = "0.8.5" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "xxhash-rust" @@ -3124,9 +2988,9 @@ checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -3135,9 +2999,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" dependencies = [ "yoke", "zerofrom", @@ -3146,13 +3010,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.5", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 513f051..1e7f0ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,31 +25,26 @@ name = "pumpkin-do" path = "src/main.rs" [dependencies] -wasm-bindgen = { path = ".work/wasm-bindgen" } -js-sys = { path = ".work/wasm-bindgen/crates/js-sys" } +wasm-bindgen = "0.2.128" +js-sys = "0.3.105" tokio = { version = "1", default-features = false, features = ["rt", "macros", "sync", "time", "net", "io-util"] } pumpkin = { path = ".work/pumpkin/crates/pumpkin", default-features = false, features = ["single-threaded"] } pumpkin-config = { path = ".work/pumpkin/crates/pumpkin-config", default-features = false } rayon = "1.12" -worker = { path = ".work/workers-rs/worker", default-features = false } +# wasm32-unknown-emscripten support pending upstream releases. [patch.crates-io] -js-sys = { path = ".work/wasm-bindgen/crates/js-sys" } -wasm-bindgen = { path = ".work/wasm-bindgen" } -wasm-bindgen-futures = { path = ".work/wasm-bindgen/crates/futures" } -wasm-bindgen-macro-support = { path = ".work/wasm-bindgen/crates/macro-support" } -wasm-bindgen-shared = { path = ".work/wasm-bindgen/crates/shared" } -web-sys = { path = ".work/wasm-bindgen/crates/web-sys" } -wasm-streams = { git = "https://github.com/guybedford/wasm-streams", rev = "115f0f27380a4f5fa33cbd9427a7107ec94cb557" } -libc = { path = ".work/libc" } -mio = { git = "https://github.com/guybedford/mio", rev = "a62c9e46833fc255c9217ab9aa362c6221ed4401" } -ring = { path = ".work/ring" } tokio = { path = ".work/tokio/tokio" } tokio-macros = { path = ".work/tokio/tokio-macros" } +# tokio-rs/mio#1969; its emscripten epoll bindings come from libc's unreleased libc-0.2 branch. +mio = { git = "https://github.com/guybedford/mio", rev = "a62c9e46833fc255c9217ab9aa362c6221ed4401" } +libc = { git = "https://github.com/rust-lang/libc", branch = "libc-0.2" } +# getrandom-backed SystemRandom on emscripten. +ring = { git = "https://github.com/guybedford/ring", branch = "emscripten" } +# mio's own libc source, unified with the crates.io users above. [patch."https://github.com/guybedford/libc"] -libc = { path = ".work/libc" } - +libc = { git = "https://github.com/rust-lang/libc", branch = "libc-0.2" } [profile.release] opt-level = "s" diff --git a/README.md b/README.md index 3fc6595..3a03ec5 100644 --- a/README.md +++ b/README.md @@ -13,20 +13,25 @@ Minecraft → Workers TCP ingress → MinecraftWorld → Pumpkin + SQLite ## Try it -The supported build host is macOS with Homebrew. Install Git, -[rustup](https://rustup.rs/), Python 3.11+, and Node 24+ (26 recommended). -From a checkout of this repository: +Linux and macOS build hosts are supported. Install Git, +[rustup](https://rustup.rs/), Python 3.11+, CMake and Ninja, and Node 24+ (26 +recommended). From a checkout of this repository: ```sh -brew install emscripten npm ci bash scripts/setup.sh npm run dev ``` -Setup installs the pinned Rust toolchain, fetches dependency sources, and builds -the matching wasm-bindgen CLI. `npm run dev` compiles Pumpkin and starts Wrangler. -The first build takes several minutes; later builds use Cargo's cache. +Setup installs the pinned Rust toolchain, fetches dependency sources and the +Emscripten toolchain under `.work/` (building Binaryen), and installs the +wasm-bindgen CLI. `npm run +dev` compiles Pumpkin and starts Wrangler. The first build takes several +minutes; later builds use Cargo's cache. + +Until Wrangler's bundled workerd routes inbound sockets to `net.Server` listeners +inside Durable Objects, point Miniflare at a workerd build that does: +`MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. Connect **Minecraft Java 26.2** to **`localhost:25565`**. Status is available at **http://localhost:8787/**. @@ -72,9 +77,8 @@ Provision the public TCP endpoint separately and route it to this Worker's ## Credits and license Built on [Pumpkin](https://github.com/Pumpkin-MC/Pumpkin), -[Guy Bedford's Rust/Emscripten work](https://github.com/guybedford), -[workers-rs](https://github.com/cloudflare/workers-rs), Thomas Rubini's TCP ingress -work, and [worker-fs-mount](https://github.com/danlapid/worker-fs-mount). +[Guy Bedford's Rust/Emscripten work](https://github.com/guybedford), and +[worker-fs-mount](https://github.com/danlapid/worker-fs-mount). Licensed under [GPL-3.0-only](LICENSE), consistent with Pumpkin. The [original MIT notice](LICENSES/rust-workers-minecraft-MIT.txt) is retained for diff --git a/docs/architecture.md b/docs/architecture.md index e41d442..1427a9b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -3,17 +3,31 @@ ## Runtime and connections The Worker forwards TCP streams to a named Durable Object using the platform's -stream-piping API. The object lazily creates an Emscripten module and passes a -workers-rs `Socket` to Pumpkin's injected connection entry point. +stream-piping API. The object lazily creates an Emscripten module and calls its +single `pumpkin_run` export. That export is a `#[wasm_bindgen(jspi)]` function: it +builds a current-thread Tokio runtime and `block_on`s the whole server lifetime, +and every park (`epoll_wait`, timers) suspends the Wasm stack on the host event +loop. There is no host-driven scheduling and no Rust promise adapter; the run +promise settles only when the server has stopped and saved. `-sREENTRANT_JSPI` +gives each promising activation its own shadow stack, so status calls and +connection routing enter the module while the server is suspended without +touching its frames. + +Pumpkin binds its stock `TcpListener` on port 25565. Emscripten's Node socket +backend implements that with `net.BoundSocket`/`net.Server`, which workerd scopes +to the Durable Object's own port table, so every object binds the same port. The +object's `connect` handler routes each inbound socket to that listener with +`handleAsNodeConnection`; the accepted connection then surfaces through epoll +readiness and Pumpkin's normal accept loop. Accepted sockets report the bound +address and an unspecified peer. Each object owns separate wasm memory, Rust statics, filesystem descriptors, and -one hosted Tokio runtime, supplied by workers-rs. Pumpkin's `single-threaded` feature selects cooperative -inline chunk generation and the async ticker. The same scheduler loop serves -native builds, which dispatch generation onto Rayon. Save batches await queue -capacity; shutdown drains results with an elapsed-time timeout before the final -flush. Bounded CPU tasks use -regular Tokio tasks because the pinned fork's public `spawn_blocking` still -requires OS threads. JSPI supplies stack suspension, not threads. +its Tokio runtime. Pumpkin's `single-threaded` feature selects cooperative inline +chunk generation and the async ticker. The same scheduler loop serves native +builds, which dispatch generation onto Rayon. Save batches await queue capacity; +shutdown drains results with an elapsed-time timeout before the final flush. +Bounded CPU tasks use regular Tokio tasks because `spawn_blocking` requires OS +threads. JSPI supplies stack suspension, not threads. Structure templates store palette indices and share their block arrays between the placement and query APIs. Block-entity NBT is shared until a placement needs @@ -53,8 +67,8 @@ The upstream `entries` table stores metadata, and `file_pages` stores contents i sparse files, and file writes and native rename use SQLite transactions. Whole-file reads still require a buffer large enough for the result. -After the final connection leaves, Pumpkin saves and shuts down, and the object -awaits `storage.sync()`. New connections wait for this checkpoint, then create a +After the final connection leaves, the object requests a stop; Pumpkin saves, +`pumpkin_run` resolves, and the object awaits `storage.sync()`. New connections wait for this checkpoint, then create a fresh runtime from the stored files. Checkpoint failures remain visible in status. This preserves issued writes; terminating a server with active clients can still lose changes in Pumpkin's in-memory caches. @@ -81,14 +95,13 @@ storage under `.data/probes/` and restart Wrangler to verify restoration. The ec and large-file test fixture has its own Worker configuration and wasm binary; it is excluded from the application bundle. -Current workerd versions can report `Network connection lost` when TCP clients -disconnect. Rust panics, Rust error logs, and runtime crashes fail the integration -test. +Rust panics, Rust error logs, and runtime crashes fail the integration test. ## Build constraints -- Use the pinned Rust nightly and matching wasm-bindgen CLI from setup. -- Keep static relocation, unwind semantics, the 8 MiB stack, and memory growth. +- Use the pinned Rust toolchain, Emscripten, and wasm-bindgen CLI from setup. +- Keep static relocation, exnref exception handling on both the C and Rust + sides, the 8 MiB stack, and memory growth. - Pass Emscripten link settings through rustc so they do not affect C compilation. - Keep the compatibility initializer linked even when Rust does not call `fd_sync`; `build.rs` configures this and tracks the library as a build input. diff --git a/docs/dependencies.md b/docs/dependencies.md index ee9c865..a2d9ed5 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -1,19 +1,18 @@ # Dependency sources and pins A clean checkout is self-contained after `bash scripts/setup.sh`. Dependency sources -are ignored under `.work/`. Both Cargo and npm dependency graphs are locked. +and toolchains are ignored under `.work/`. Both Cargo and npm dependency graphs are +locked. ## Direct checkouts | Component | Source / branch | Commit | Local changes or purpose | | --- | --- | --- | --- | -| emscripten | [cf](https://github.com/guybedford/emscripten) | `21166256c4c4d73d39b3685c8973d7cbe427ce8c` | Fork frontend: epoll, JSPI, wasm-bindgen post-link | -| wasm-bindgen | [emscripten-non-identifier-names](https://github.com/guybedford/wasm-bindgen) | `4b69f3b3ba4212c857be6854f77fa5aec8b62871` | Closure-finalizer string escaping | -| tokio | [emscripten-layering](https://github.com/guybedford/tokio) | `7c1d4977c510866775ed6164b58b2218a6a2955b` | HostedRuntime and normal Tokio TCP/UDP APIs; unmodified | -| libc | [libc-0.2-emscripten](https://github.com/guybedford/libc) | `4091fe0b0dc5f9c1a27bed75be1ff02bb27e756d` | Emscripten epoll and socket support; unmodified | -| ring | [emscripten](https://github.com/guybedford/ring) | `6671f7cfbb13f249b571ffa6326275a8596e0ca2` | Portable Emscripten crypto backend; unmodified | | pumpkin | [master](https://github.com/Pumpkin-MC/Pumpkin) | `b5b9b9d7010e793806a83c495af223c67e1d35ee` | Headless embedding, shared async scheduler, compact templates/generation chunks, and build-script fixes | -| workers-rs | [connect-bindings](https://github.com/ThomasRubini/workers-rs) | `7db011ec97658a5d907f3e3102028ce86c044f19` | TCP ingress PR #1041; pinned ABI, Emscripten Tokio promise adapter, host-owned initialization, and socket shutdown flushing | +| tokio | [emscripten-epoll](https://github.com/guybedford/tokio) | `8d0a2a845c546e93a4cf0e8ff2c21ac50a3d1931` | JSPI parking and `net` over epoll on Emscripten (tokio-rs/tokio#8281 follow-ons); unmodified | +| emscripten | [cf-final](https://github.com/guybedford/emscripten) | `a5013dd597ec9d48856370f5707d438937a7b4e8` | Upstream main plus emscripten-core/emscripten#27698, #27699 (`REENTRANT_JSPI`), #27547, and two fixes pending for #27699; unmodified | +| binaryen | [jspi-hooks](https://github.com/guybedford/binaryen) | `d6483a04d7dab0ef83e5c43f87343061d97a3b8d` | WebAssembly/binaryen#9102, the `--jspi-hooks` pass; built by setup | +| emsdk | [main](https://github.com/emscripten-core/emsdk) | `5eb0bde7585670252e8ba05e9d361627bffd08b5` | LLVM from emscripten-releases build `e8579ea489b44a6792f5abf95377a6ee38a16cce`, paired with the frontend's main | Patches are relative to the pinned commits above. Setup checks reverse application before applying a patch and refuses to repin a modified checkout. @@ -25,26 +24,34 @@ commit. ## Cargo-managed forks -| Crate | Source | Commit | +| Crate | Source | Purpose | | --- | --- | --- | -| mio | https://github.com/guybedford/mio | `a62c9e46833fc255c9217ab9aa362c6221ed4401` | -| wasm-streams | https://github.com/guybedford/wasm-streams | `115f0f27380a4f5fa33cbd9427a7107ec94cb557` | +| mio | https://github.com/guybedford/mio `a62c9e46833fc255c9217ab9aa362c6221ed4401` | Emscripten epoll selector (tokio-rs/mio#1969) | +| libc | https://github.com/rust-lang/libc branch `libc-0.2` | Emscripten epoll bindings, unreleased | +| ring | https://github.com/guybedford/ring branch `emscripten` | getrandom-backed `SystemRandom` on Emscripten | The root Cargo.toml applies these overrides and the local checkouts. Cargo.lock -pins the full application graph. `toolchain/wasm-bindgen.Cargo.lock` separately pins -the host CLI graph, since that upstream workspace does not commit a lockfile. +pins the full application graph, including the branch commits. ## Host tools -- Rust: `nightly-2026-07-20`, target `wasm32-unknown-emscripten`; setup installs both through rustup. +- Rust: `beta` channel (1.99; `OwnedFd::try_clone` on Emscripten), target + `wasm32-unknown-emscripten`; setup installs both through rustup. rustc needs a + larger compile-thread stack for pumpkin-data's generated tables; the scripts + set `RUST_MIN_STACK`. +- wasm-bindgen CLI: the release matching the `wasm-bindgen` crate version in + Cargo.toml, installed by setup into `.work/bin/` (or `WASM_BINDGEN_BIN`). emcc + runs it as a post-link step under `-sWASM_BINDGEN`. - Node: 24+; 26 recommended and selected in CI. Used for build tools and tests. - Python: 3.11+ (Emscripten scripts and TOML parsing). -- Backend: Homebrew `emscripten`, detected with `brew --prefix emscripten`. - Homebrew backend versions are external prerequisites, not pinned source files. - -Setup deliberately uses the fork frontend with Homebrew's LLVM/binaryen backend; -emsdk is not needed. It writes a machine-local `.emscripten_cf`, builds wasm-bindgen -explicitly for the host target, and places the CLI in `.work/bin/`. +- Emscripten backend: LLVM through emsdk under `.work/emsdk` (or an activated + emsdk selected with `EMSDK`), and Binaryen built from the checkout above with + CMake and Ninja. Setup writes the frontend's machine-local `.emscripten_cf` + pointing at both. Homebrew's release cannot be used while the frontend is + ahead of the tagged releases. +- workerd: Wrangler must run a workerd with per-Durable-Object port tables and + `net.Server` inbound routing (`handleAsNodeConnection`). Until that ships in + Wrangler's bundled version, set `MINIFLARE_WORKERD_PATH`. ## Updating a patch @@ -53,13 +60,12 @@ upstream base from the repository root. Write it under `.work/` to preserve the commit description at the top of the maintained patch: ```sh +git -C .work/pumpkin add -N crates/pumpkin/src/net/bedrock/nethernet_stub.rs git -C .work/pumpkin diff b5b9b9d7010e793806a83c495af223c67e1d35ee -- \ . ':(exclude)Cargo.lock' ':(exclude)crates/pumpkin-world/src/generation' \ > .work/pumpkin-emscripten.diff git -C .work/pumpkin diff b5b9b9d7010e793806a83c495af223c67e1d35ee -- \ crates/pumpkin-world/src/generation > .work/pumpkin-memory.diff -git -C .work/wasm-bindgen diff 4b69f3b3ba4212c857be6854f77fa5aec8b62871 -- crates/cli-support/src/js/mod.rs > .work/wasm-bindgen-emscripten-closures.diff -git -C .work/workers-rs diff 7db011ec97658a5d907f3e3102028ce86c044f19 > .work/workers-rs-emscripten-toolchain.diff ``` Replace the corresponding patch's contents from its first `diff --git` line onward @@ -70,14 +76,14 @@ The Pumpkin path filters reflect the current separation: all memory-patch files are under `crates/pumpkin-world/src/generation/`. Adjust the filters if that scope changes, keeping platform support and memory optimizations in their own patches. -`git diff` omits untracked files, including files created by existing patches. -Preserve those added-file diffs when regenerating (for example, -`nethernet_stub.rs` in the compatibility patch). If you add a file in a dependency -checkout, include it explicitly and verify application on a fresh copy of the base. -Run `bash scripts/test.sh` after runtime changes. Keep unpatched checkouts unmodified. -Setup refuses to repin a modified checkout rather than discarding local work. +`git diff` omits untracked files, including files created by existing patches; +`add -N` above includes `nethernet_stub.rs`. If you add a file in a dependency +checkout, include it the same way and verify application on a fresh copy of the +base. Run `bash scripts/test.sh` after runtime changes. Keep unpatched checkouts +unmodified. Setup refuses to repin a modified checkout rather than discarding +local work. -For source/patch validation without rebuilding the toolchain: +For source/patch validation without provisioning the toolchain: ```sh bash scripts/setup.sh --sources-only diff --git a/docs/development.md b/docs/development.md index f27b4bb..f05481d 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,7 +1,8 @@ # Development Follow the [README setup instructions](../README.md#try-it) to install the pinned -Rust toolchain, provision dependency sources, and build the matching wasm-bindgen CLI. +Rust toolchain, provision dependency sources and Emscripten, and install the +wasm-bindgen CLI. The initial Pumpkin compilation takes several minutes. Later builds use Cargo's cache. `build.rs` owns the Emscripten library arguments and tracks changes to the compatibility library. diff --git a/patches/README.md b/patches/README.md index 86af881..882110c 100644 --- a/patches/README.md +++ b/patches/README.md @@ -11,11 +11,8 @@ diff below it. Preserve the description when regenerating the diff. | --- | --- | --- | | `pumpkin-emscripten.patch` | `.work/pumpkin` | Headless optional subsystems, single-threaded runtime and blocking-task helper, shared async chunk scheduler with backpressure and timed draining, unified ticker, and platform guards | | `pumpkin-memory.patch` | `.work/pumpkin` | Shared indexed structure templates, immutable block-entity NBT, paletted generation chunks, and a temporary dense noise buffer | -| `wasm-bindgen-emscripten-closures.patch` | `.work/wasm-bindgen` | Escape generated closure-finalizer postsets as JavaScript strings | -| `workers-rs-emscripten-toolchain.patch` | `.work/workers-rs` | Match the pinned wasm-bindgen ABI, provide the Emscripten Tokio promise adapter, leave initialization/recovery to the host, and flush pending writes before socket shutdown | -`pumpkin-emscripten.patch` includes the injected-stream connection entry point used -by the DO. Setup applies it before `pumpkin-memory.patch`. The two patches use the +Setup applies `pumpkin-emscripten.patch` before `pumpkin-memory.patch`. The two patches use the same pinned base and currently modify separate files, so each can also be applied and checked independently. diff --git a/patches/pumpkin-emscripten.patch b/patches/pumpkin-emscripten.patch index ce284ef..fc4bfd6 100644 --- a/patches/pumpkin-emscripten.patch +++ b/patches/pumpkin-emscripten.patch @@ -1,17 +1,16 @@ Subject: [PATCH] Support headless Pumpkin on a cooperative runtime -Embedding Pumpkin in a Durable Object requires accepting host-provided -streams and running without OS worker threads or native plugin runtimes. -Add an injected-stream connection entry point and optional subsystems for -NetherNet, plugin loading, the console, and system crash reporting. Native -builds retain these subsystems and thread-backed execution by default. +Embedding Pumpkin in a Durable Object requires running without OS worker +threads or native plugin runtimes. Add optional subsystems for NetherNet, +plugin loading, the console, and system crash reporting. Native builds +retain these subsystems and thread-backed execution by default. Share asynchronous ticker and chunk-scheduler loops between native and single-threaded builds. Route Rayon and blocking jobs through helpers that can run on the current thread, await scheduler notifications and results, -and track cooperative tasks through shutdown. Match the Tokio requirement -to the hosted-runtime fork and guard unsupported Emscripten system calls -and synchronous HTTP lookups. +and track cooperative tasks through shutdown. Drop the Tokio features that +need OS threads and signals from single-threaded builds and guard +unsupported Emscripten system calls and synchronous HTTP lookups. Reserve save-queue capacity before transferring chunks or marking writes pending, and widen pending-write counters to usize. Drain late generation @@ -27,7 +26,7 @@ Base-commit: b5b9b9d7010e793806a83c495af223c67e1d35ee --- diff --git a/Cargo.toml b/Cargo.toml -index 143ba8a36..57314ca35 100644 +index 143ba8a3..57314ca3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -126,7 +126,7 @@ strip = false @@ -40,7 +39,7 @@ index 143ba8a36..57314ca35 100644 thiserror = { version = "2.0", default-features = false } diff --git a/crates/pumpkin-util/Cargo.toml b/crates/pumpkin-util/Cargo.toml -index f4fb121fb..0a4a30761 100644 +index f4fb121f..0a4a3076 100644 --- a/crates/pumpkin-util/Cargo.toml +++ b/crates/pumpkin-util/Cargo.toml @@ -6,11 +6,13 @@ rust-version.workspace = true @@ -67,7 +66,7 @@ index f4fb121fb..0a4a30761 100644 [[bench]] diff --git a/crates/pumpkin-util/src/lib.rs b/crates/pumpkin-util/src/lib.rs -index 5119797bc..a1ae947a2 100644 +index 5119797b..a1ae947a 100644 --- a/crates/pumpkin-util/src/lib.rs +++ b/crates/pumpkin-util/src/lib.rs @@ -310,3 +310,42 @@ impl TryFrom for Hand { @@ -114,7 +113,7 @@ index 5119797bc..a1ae947a2 100644 + } +} diff --git a/crates/pumpkin-world/Cargo.toml b/crates/pumpkin-world/Cargo.toml -index f1675a8f2..82c219179 100644 +index f1675a8f..82c21917 100644 --- a/crates/pumpkin-world/Cargo.toml +++ b/crates/pumpkin-world/Cargo.toml @@ -17,11 +17,9 @@ bytes.workspace = true @@ -145,7 +144,7 @@ index f1675a8f2..82c219179 100644 tempfile.workspace = true criterion = { workspace = true, features = ["html_reports", "async_tokio"] } diff --git a/crates/pumpkin-world/src/chunk/format/anvil.rs b/crates/pumpkin-world/src/chunk/format/anvil.rs -index 38de745f9..60cedf53d 100644 +index 38de745f..60cedf53 100644 --- a/crates/pumpkin-world/src/chunk/format/anvil.rs +++ b/crates/pumpkin-world/src/chunk/format/anvil.rs @@ -798,7 +798,7 @@ impl ChunkSerializer for AnvilChunkFile< @@ -168,7 +167,7 @@ index 38de745f9..60cedf53d 100644 }); }); diff --git a/crates/pumpkin-world/src/chunk/format/linear.rs b/crates/pumpkin-world/src/chunk/format/linear.rs -index 3927c66fc..fd76019bf 100644 +index 3927c66f..fd76019b 100644 --- a/crates/pumpkin-world/src/chunk/format/linear.rs +++ b/crates/pumpkin-world/src/chunk/format/linear.rs @@ -599,7 +599,7 @@ impl ChunkSerializer for LinearV2File @@ -191,7 +190,7 @@ index 3927c66fc..fd76019bf 100644 }); }); diff --git a/crates/pumpkin-world/src/chunk/format/pump.rs b/crates/pumpkin-world/src/chunk/format/pump.rs -index f1c44adc7..b5a8dd1d7 100644 +index f1c44adc..b5a8dd1d 100644 --- a/crates/pumpkin-world/src/chunk/format/pump.rs +++ b/crates/pumpkin-world/src/chunk/format/pump.rs @@ -147,7 +147,7 @@ where @@ -214,7 +213,7 @@ index f1c44adc7..b5a8dd1d7 100644 }); }); diff --git a/crates/pumpkin-world/src/chunk/io/mod.rs b/crates/pumpkin-world/src/chunk/io/mod.rs -index c4daf5bb5..78271c344 100644 +index c4daf5bb..78271c34 100644 --- a/crates/pumpkin-world/src/chunk/io/mod.rs +++ b/crates/pumpkin-world/src/chunk/io/mod.rs @@ -13,7 +13,7 @@ where @@ -227,7 +226,7 @@ index c4daf5bb5..78271c344 100644 /// The result of loading a chunk data. diff --git a/crates/pumpkin-world/src/chunk_system/channel.rs b/crates/pumpkin-world/src/chunk_system/channel.rs -index 190d578fa..c138b1f50 100644 +index 190d578f..c138b1f5 100644 --- a/crates/pumpkin-world/src/chunk_system/channel.rs +++ b/crates/pumpkin-world/src/chunk_system/channel.rs @@ -14,6 +14,7 @@ pub type LevelChange = ( @@ -281,7 +280,7 @@ index 190d578fa..c138b1f50 100644 } } diff --git a/crates/pumpkin-world/src/chunk_system/mod.rs b/crates/pumpkin-world/src/chunk_system/mod.rs -index f670ad37b..b9d9a8671 100644 +index f670ad37..b9d9a867 100644 --- a/crates/pumpkin-world/src/chunk_system/mod.rs +++ b/crates/pumpkin-world/src/chunk_system/mod.rs @@ -13,7 +13,7 @@ pub type HashSetType = rustc_hash::FxHashSet; @@ -294,7 +293,7 @@ index f670ad37b..b9d9a8671 100644 )>; diff --git a/crates/pumpkin-world/src/chunk_system/schedule.rs b/crates/pumpkin-world/src/chunk_system/schedule.rs -index 145e15de8..2942a1d31 100644 +index 145e15de..2942a1d3 100644 --- a/crates/pumpkin-world/src/chunk_system/schedule.rs +++ b/crates/pumpkin-world/src/chunk_system/schedule.rs @@ -69,13 +69,14 @@ pub struct GenerationSchedule { @@ -863,7 +862,7 @@ index 145e15de8..2942a1d31 100644 + } +} diff --git a/crates/pumpkin-world/src/chunk_system/worker_logic.rs b/crates/pumpkin-world/src/chunk_system/worker_logic.rs -index b50ac05c9..281fa41f0 100644 +index b50ac05c..281fa41f 100644 --- a/crates/pumpkin-world/src/chunk_system/worker_logic.rs +++ b/crates/pumpkin-world/src/chunk_system/worker_logic.rs @@ -90,7 +90,7 @@ fn process_loaded_chunk(chunk: Arc, level: &Level) -> C @@ -876,7 +875,7 @@ index b50ac05c9..281fa41f0 100644 lock: IOLock, ) { diff --git a/crates/pumpkin-world/src/level.rs b/crates/pumpkin-world/src/level.rs -index fbc69749a..533b09d21 100644 +index fbc69749..533b09d2 100644 --- a/crates/pumpkin-world/src/level.rs +++ b/crates/pumpkin-world/src/level.rs @@ -316,7 +316,7 @@ impl Level { @@ -969,7 +968,7 @@ index fbc69749a..533b09d21 100644 } diff --git a/crates/pumpkin-world/src/world_info/anvil.rs b/crates/pumpkin-world/src/world_info/anvil.rs -index ebbcb49b6..46975c6c5 100644 +index ebbcb49b..46975c6c 100644 --- a/crates/pumpkin-world/src/world_info/anvil.rs +++ b/crates/pumpkin-world/src/world_info/anvil.rs @@ -425,6 +425,11 @@ impl WorldInfoWriter for AnvilLevelInfo { @@ -985,7 +984,7 @@ index ebbcb49b6..46975c6c5 100644 let path = level_folder.join(LEVEL_DAT_FILE_NAME); let path_new = level_folder.join("level.dat_new"); diff --git a/crates/pumpkin/Cargo.toml b/crates/pumpkin/Cargo.toml -index 6ade6bfcb..c9230c03a 100644 +index 6ade6bfc..c9230c03 100644 --- a/crates/pumpkin/Cargo.toml +++ b/crates/pumpkin/Cargo.toml @@ -17,6 +17,14 @@ LegalCopyright = "Copyright © 2026 Aleksander Medvedev" @@ -1109,7 +1108,7 @@ index 6ade6bfcb..c9230c03a 100644 [lints] diff --git a/crates/pumpkin/build.rs b/crates/pumpkin/build.rs -index 5314180a1..a6accb815 100644 +index 5314180a..a6accb81 100644 --- a/crates/pumpkin/build.rs +++ b/crates/pumpkin/build.rs @@ -23,8 +23,21 @@ fn main() { @@ -1137,7 +1136,7 @@ index 5314180a1..a6accb815 100644 println!("cargo::rustc-env=GIT_HASH_FULL={git_hash_full}"); } diff --git a/crates/pumpkin/src/command/commands/pumpkin.rs b/crates/pumpkin/src/command/commands/pumpkin.rs -index 0045511a9..9586e5dc5 100644 +index 0045511a..9586e5dc 100644 --- a/crates/pumpkin/src/command/commands/pumpkin.rs +++ b/crates/pumpkin/src/command/commands/pumpkin.rs @@ -56,6 +56,7 @@ fn fetch_all_contributors_cached() -> Vec { @@ -1176,7 +1175,7 @@ index 0045511a9..9586e5dc5 100644 if let Ok(mut guard) = DONATORS_CACHE.lock() { *guard = Some(DonatorCache { diff --git a/crates/pumpkin/src/crash.rs b/crates/pumpkin/src/crash.rs -index 6f88670e3..9969598c2 100644 +index 6f88670e..9969598c 100644 --- a/crates/pumpkin/src/crash.rs +++ b/crates/pumpkin/src/crash.rs @@ -13,6 +13,7 @@ use pumpkin_util::text::{ @@ -1204,7 +1203,7 @@ index 6f88670e3..9969598c2 100644 writeln_output!(output); let cpus = sys.cpus(); diff --git a/crates/pumpkin/src/data/datapack/mod.rs b/crates/pumpkin/src/data/datapack/mod.rs -index 2f8f72349..eefa123d0 100644 +index 2f8f7234..eefa123d 100644 --- a/crates/pumpkin/src/data/datapack/mod.rs +++ b/crates/pumpkin/src/data/datapack/mod.rs @@ -253,7 +253,7 @@ impl DatapackManager { @@ -1217,7 +1216,7 @@ index 2f8f72349..eefa123d0 100644 format!("Failed to open structure '{display_path}': {error}") })?; diff --git a/crates/pumpkin/src/data/player_server.rs b/crates/pumpkin/src/data/player_server.rs -index 0899fa9c1..733e66780 100644 +index 0899fa9c..733e6678 100644 --- a/crates/pumpkin/src/data/player_server.rs +++ b/crates/pumpkin/src/data/player_server.rs @@ -87,7 +87,7 @@ impl ServerPlayerData { @@ -1230,7 +1229,7 @@ index 0899fa9c1..733e66780 100644 if let Err(e) = storage.save_player_data(&uuid, nbt) { error!("Failed to save player data for {uuid}: {e}"); diff --git a/crates/pumpkin/src/entity/mod.rs b/crates/pumpkin/src/entity/mod.rs -index 7d64f9b1c..ca28e83b4 100644 +index 7d64f9b1..ca28e83b 100644 --- a/crates/pumpkin/src/entity/mod.rs +++ b/crates/pumpkin/src/entity/mod.rs @@ -2361,7 +2361,7 @@ impl Entity { @@ -1243,7 +1242,7 @@ index 7d64f9b1c..ca28e83b4 100644 let Some(entity_arc) = world_clone.get_entity_by_id(entity_id) else { return; diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs -index a19825bc2..45d6783e8 100644 +index a19825bc..45d6783e 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -572,6 +572,7 @@ impl Player { @@ -1277,7 +1276,7 @@ index a19825bc2..45d6783e8 100644 || { self.chunk_sender diff --git a/crates/pumpkin/src/lib.rs b/crates/pumpkin/src/lib.rs -index 7110bc9e4..3438afb65 100644 +index 7110bc9e..060225ea 100644 --- a/crates/pumpkin/src/lib.rs +++ b/crates/pumpkin/src/lib.rs @@ -8,12 +8,14 @@ extern crate pumpkin_macros; @@ -1415,141 +1414,7 @@ index 7110bc9e4..3438afb65 100644 if let Some((wrapper, _, _)) = LOGGER_IMPL.wait() && let Some(rl) = wrapper.take_readline() { -@@ -534,6 +568,72 @@ impl PumpkinServer { - } - } - -+ /// Run the normal Java client lifecycle for a host-provided byte stream. -+ pub async fn serve_connection(&self, connection: S, client_addr: SocketAddr, client_id: u64) -+ where -+ S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Sync + Unpin + 'static, -+ { -+ let server_clone = self.server.clone(); -+ let packet_limiter = PacketRateLimiter::from_config( -+ &server_clone.advanced_config.networking.java.packet_limiter, -+ ); -+ let mut pending = -+ PendingConnection::from_stream(connection, client_addr, client_id, packet_limiter); -+ let login_result = pending.handle_login_sequence(&server_clone).await; -+ -+ match login_result { -+ PacketHandlerResult::Stop => { -+ pending.close(); -+ } -+ PacketHandlerResult::ReadyToPlay(profile, config) => { -+ let mut java_client = -+ JavaClient::from_pending(pending, profile.clone(), config.clone()); -+ java_client.start_outgoing_packet_task(); -+ -+ if let Some((player, world)) = server_clone.add_player( -+ Arc::new(ClientPlatform::Java(java_client)), -+ profile, -+ Some(config), -+ ) { -+ if let ClientPlatform::Java(client) = player.client.as_ref() { -+ client.set_player(player.clone()); -+ } -+ world -+ .spawn_java_player(&server_clone.basic_config, &player, &server_clone) -+ .await; -+ -+ if let ClientPlatform::Java(client) = player.client.as_ref() { -+ client.progress_player_packets(&player, &server_clone).await; -+ -+ // Close when done -+ client.close(); -+ client.await_tasks().await; -+ } -+ player.remove().await; -+ server_clone.remove_player(&player); -+ if let Err(e) = server_clone -+ .player_data_storage -+ .handle_player_leave(&player) -+ { -+ error!("Failed to save player data on disconnect: {e}"); -+ } -+ if let Err(e) = server_clone.advancement_manager.save_player(&player).await { -+ error!("Failed to save player advancement on disconnect: {e}"); -+ } -+ } -+ } -+ } -+ } -+ -+ fn clone_for_connection(&self) -> Self { -+ Self { -+ server: self.server.clone(), -+ tcp_listener: None, -+ bedrock_status: None, -+ nethernet_listener: None, -+ } -+ } -+ - #[allow(clippy::too_many_lines)] - pub async fn unified_listener_task( - &self, -@@ -559,66 +659,13 @@ impl PumpkinServer { - format!("{client_addr}") - }; - debug!("Accepted connection from Java Edition: {formatted_address} (id {client_id})"); -- let server_clone = self.server.clone(); -- -+ let server = self.clone_for_connection(); - tasks.spawn(async move { -- let packet_limiter = PacketRateLimiter::from_config( -- &server_clone.advanced_config.networking.java.packet_limiter, -- ); -- let mut pending = PendingConnection::new( -- connection, -- client_addr, -- client_id, -- packet_limiter, -- ); -- let login_result = pending.handle_login_sequence(&server_clone).await; -- -- match login_result { -- PacketHandlerResult::Stop => { -- pending.close(); -- }, -- PacketHandlerResult::ReadyToPlay(profile, config) => { -- let mut java_client = JavaClient::from_pending(pending, profile.clone(), config.clone()); -- java_client.start_outgoing_packet_task(); -- -- if let Some((player, world)) = server_clone -- .add_player(Arc::new(ClientPlatform::Java(java_client)), profile, Some(config)) -- { -- -- if let ClientPlatform::Java(client) = player.client.as_ref() { -- client.set_player(player.clone()); -- } -- world -- .spawn_java_player(&server_clone.basic_config, &player, &server_clone) -- .await; -- -- if let ClientPlatform::Java(client) = player.client.as_ref() { -- client.progress_player_packets(&player, &server_clone).await; -- -- // Close when done -- client.close(); -- client.await_tasks().await; -- } -- player.remove().await; -- server_clone.remove_player(&player); -- if let Err(e) = server_clone -- .player_data_storage -- .handle_player_leave(&player) -- { -- error!("Failed to save player data on disconnect: {e}"); -- } -- if let Err(e) = server_clone.advancement_manager -- .save_player(&player) -- .await { -- error!("Failed to save player advancement on disconnect: {e}"); -- } -- } -- }, -- } -+ server.serve_connection(connection, client_addr, client_id).await; +@@ -618,7 +652,7 @@ impl PumpkinServer { }); } Err(e) => { @@ -1558,7 +1423,7 @@ index 7110bc9e4..3438afb65 100644 if e.raw_os_error() == Some(libc::EMFILE) { error!( "Too many open files! Server reached file descriptor limit. \ -@@ -719,6 +766,7 @@ impl PumpkinServer { +@@ -719,6 +753,7 @@ impl PumpkinServer { } } @@ -1566,7 +1431,7 @@ index 7110bc9e4..3438afb65 100644 fn setup_stdin_console(server: &Arc) { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let rt = tokio::runtime::Handle::current(); -@@ -759,6 +807,7 @@ fn setup_stdin_console(server: &Arc) { +@@ -759,6 +794,7 @@ fn setup_stdin_console(server: &Arc) { }); } @@ -1574,7 +1439,7 @@ index 7110bc9e4..3438afb65 100644 fn setup_console(mut rl: Editor, server: Arc) { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let (tx_reply, mut rx_reply) = tokio::sync::mpsc::channel(1); -@@ -839,7 +888,7 @@ fn scrub_address(ip: &str) -> String { +@@ -839,7 +875,7 @@ fn scrub_address(ip: &str) -> String { .collect() } @@ -1584,7 +1449,7 @@ index 7110bc9e4..3438afb65 100644 let mut rlim = libc::rlimit { rlim_cur: 0, diff --git a/crates/pumpkin/src/logging.rs b/crates/pumpkin/src/logging.rs -index 85b51a45a..4006f6eca 100644 +index 85b51a45..4006f6ec 100644 --- a/crates/pumpkin/src/logging.rs +++ b/crates/pumpkin/src/logging.rs @@ -2,11 +2,17 @@ @@ -1741,7 +1606,7 @@ index 85b51a45a..4006f6eca 100644 type Candidate = String; diff --git a/crates/pumpkin/src/net/authentication.rs b/crates/pumpkin/src/net/authentication.rs -index f344d1978..b32c3bc2a 100644 +index f344d197..b32c3bc2 100644 --- a/crates/pumpkin/src/net/authentication.rs +++ b/crates/pumpkin/src/net/authentication.rs @@ -311,6 +311,7 @@ pub async fn lookup_profile_by_name( @@ -1771,7 +1636,7 @@ index f344d1978..b32c3bc2a 100644 uuid: Uuid, auth_config: &AuthenticationConfig, diff --git a/crates/pumpkin/src/net/bedrock/mod.rs b/crates/pumpkin/src/net/bedrock/mod.rs -index 5ea3e41ac..fd913a72b 100644 +index 5ea3e41a..fd913a72 100644 --- a/crates/pumpkin/src/net/bedrock/mod.rs +++ b/crates/pumpkin/src/net/bedrock/mod.rs @@ -1,3 +1,7 @@ @@ -1782,8 +1647,70 @@ index 5ea3e41ac..fd913a72b 100644 pub mod nethernet; pub mod play; pub mod status; +diff --git a/crates/pumpkin/src/net/bedrock/nethernet_stub.rs b/crates/pumpkin/src/net/bedrock/nethernet_stub.rs +new file mode 100644 +index 00000000..90f0989c +--- /dev/null ++++ b/crates/pumpkin/src/net/bedrock/nethernet_stub.rs +@@ -0,0 +1,56 @@ ++//! WebRTC-free stub of the NetherNet transport, compiled when the `nethernet` ++//! feature is off (e.g. the emscripten/embedded Java-only build). The types ++//! exist so the always-compiled Bedrock client code type-checks, but no Bedrock ++//! connection is ever created (the listener is never bound), so the method ++//! bodies are unreachable. ++use std::net::SocketAddr; ++use std::sync::Arc; ++ ++use bytes::Bytes; ++use pumpkin_util::p384::PublicKey; ++ ++pub type IncomingSession = (Arc, SocketAddr); ++ ++/// Stub listener: never bound when `nethernet` is off, so `accept` never yields. ++pub struct NetherNetListener { ++ _private: (), ++} ++ ++impl NetherNetListener { ++ pub async fn accept(&self) -> Option { ++ None ++ } ++ ++ pub const fn local_addr(&self) -> SocketAddr { ++ SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0) ++ } ++} ++ ++/// Stub session. Never constructed when `nethernet` is off. ++pub struct NetherNetSession { ++ _private: (), ++} ++ ++impl NetherNetSession { ++ pub async fn recv(&self) -> Option { ++ None ++ } ++ ++ pub async fn send(&self, _data: Bytes) -> Result<(), String> { ++ Err("NetherNet transport is disabled in this build".to_string()) ++ } ++ ++ pub async fn send_unreliable(&self, _data: Bytes) -> Result<(), String> { ++ Err("NetherNet transport is disabled in this build".to_string()) ++ } ++ ++ pub const fn is_closed(&self) -> bool { ++ true ++ } ++ ++ pub async fn close(&self) {} ++ ++ pub const fn client_public_key(&self) -> Option<&PublicKey> { ++ None ++ } ++} diff --git a/crates/pumpkin/src/net/java/config/known_packs.rs b/crates/pumpkin/src/net/java/config/known_packs.rs -index 7e45c3db9..8a4d5af4d 100644 +index 7e45c3db..8a4d5af4 100644 --- a/crates/pumpkin/src/net/java/config/known_packs.rs +++ b/crates/pumpkin/src/net/java/config/known_packs.rs @@ -14,7 +14,7 @@ impl JavaClient { @@ -1796,67 +1723,10 @@ index 7e45c3db9..8a4d5af4d 100644 let mut packets = Vec::new(); let mut sent_dimension_type = false; diff --git a/crates/pumpkin/src/net/java/mod.rs b/crates/pumpkin/src/net/java/mod.rs -index fb0e9c901..96e3b1101 100644 +index fb0e9c90..6583eb12 100644 --- a/crates/pumpkin/src/net/java/mod.rs +++ b/crates/pumpkin/src/net/java/mod.rs -@@ -42,7 +42,6 @@ use pumpkin_util::text::TextComponent; - use pumpkin_util::version::JavaMinecraftVersion; - use tokio::{ - io::{BufReader, BufWriter}, -- net::tcp::{OwnedReadHalf, OwnedWriteHalf}, - sync::oneshot, - }; - use tokio::{ -@@ -53,6 +52,9 @@ use tokio_util::sync::CancellationToken; - use tokio_util::task::TaskTracker; - use tracing::{debug, error, warn}; - -+pub type ConnectionReader = Box; -+pub type ConnectionWriter = Box; -+ - pub mod config; - pub mod handshake; - pub mod login; -@@ -103,9 +105,9 @@ pub struct JavaClient { - /// A high-priority queue of serialized packets to send to the network. - outgoing_packet_priority_recv: Option>, - /// The packet encoder for outgoing packets. -- network_writer: std::sync::Mutex>>>, -+ network_writer: std::sync::Mutex>>>, - /// The packet decoder for incoming packets. -- network_reader: std::sync::Mutex>>>, -+ network_reader: std::sync::Mutex>>>, - /// Keep Alive: - /// - /// Whether we are waiting for a response after sending a keep alive packet. -@@ -155,10 +157,10 @@ fn take_frame_batch(packets: &mut VecDeque) -> Vec>, -+ mut writer: TCPNetworkEncoder>, - batch: &[OutgoingPacket], - ) -> ( -- TCPNetworkEncoder>, -+ TCPNetworkEncoder>, - Vec, - Option, - ) { -@@ -174,11 +176,11 @@ fn frame_packet_batch( - } - - async fn frame_batch_maybe_offload( -- writer: TCPNetworkEncoder>, -+ writer: TCPNetworkEncoder>, - packet_batch: Vec, - ) -> Result< - ( -- TCPNetworkEncoder>, -+ TCPNetworkEncoder>, - Vec, - Vec, - Option, -@@ -190,7 +192,7 @@ async fn frame_batch_maybe_offload( +@@ -190,7 +190,7 @@ async fn frame_batch_maybe_offload( .any(|packet| writer.is_compressing_packet(&packet.data)); if needs_offload { @@ -1865,7 +1735,7 @@ index fb0e9c901..96e3b1101 100644 let (writer, frame, frame_err) = frame_packet_batch(writer, &packet_batch); (writer, packet_batch, frame, frame_err) }) -@@ -406,7 +408,7 @@ impl JavaClient { +@@ -406,7 +406,7 @@ impl JavaClient { let version = self.version.load(); let (tx, rx) = oneshot::channel(); @@ -1874,77 +1744,8 @@ index fb0e9c901..96e3b1101 100644 let mut serialized = Vec::with_capacity(valid_chunks.len()); for chunk in valid_chunks { let mut buf = Vec::with_capacity(32 * 1024); -@@ -532,7 +534,7 @@ impl JavaClient { - - pub async fn get_packet_with_reader( - &self, -- network_reader: &mut TCPNetworkDecoder>, -+ network_reader: &mut TCPNetworkDecoder>, - ) -> Option { - tokio::select! { - () = self.await_close_interrupt() => { -diff --git a/crates/pumpkin/src/net/java/pending.rs b/crates/pumpkin/src/net/java/pending.rs -index 2522573e0..a627b07b8 100644 ---- a/crates/pumpkin/src/net/java/pending.rs -+++ b/crates/pumpkin/src/net/java/pending.rs -@@ -23,10 +23,7 @@ use pumpkin_protocol::{ - use pumpkin_util::{Hand, text::TextComponent, version::JavaMinecraftVersion}; - use tokio::{ - io::{BufReader, BufWriter}, -- net::{ -- TcpStream, -- tcp::{OwnedReadHalf, OwnedWriteHalf}, -- }, -+ net::TcpStream, - }; - use tokio_util::sync::CancellationToken; - use tracing::{debug, error, warn}; -@@ -40,7 +37,7 @@ use crate::{ - server::Server, - }; - --use super::JavaClient; -+use super::{ConnectionReader, ConnectionWriter, JavaClient}; - - const BRAND_CHANNEL_PREFIX: &str = "minecraft:brand"; - -@@ -61,8 +58,8 @@ pub struct PendingConnection { - pub version: AtomicCell, - pub connection_state: AtomicCell, - pub close_token: CancellationToken, -- pub network_writer: TCPNetworkEncoder>, -- pub network_reader: TCPNetworkDecoder>, -+ pub network_writer: TCPNetworkEncoder>, -+ pub network_reader: TCPNetworkDecoder>, - pub gameprofile: Option, - pub config: Option, - pub brand: Option, -@@ -78,7 +75,22 @@ impl PendingConnection { - id: u64, - packet_limiter: PacketRateLimiter, - ) -> Self { -- let (read, write) = tcp_stream.into_split(); -+ Self::from_stream(tcp_stream, address, id, packet_limiter) -+ } -+ -+ /// Accept an injected byte stream while retaining the native TCP constructor. -+ pub fn from_stream( -+ stream: S, -+ address: SocketAddr, -+ id: u64, -+ packet_limiter: PacketRateLimiter, -+ ) -> Self -+ where -+ S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Send + Sync + Unpin + 'static, -+ { -+ let (read, write) = tokio::io::split(stream); -+ let read: ConnectionReader = Box::new(read); -+ let write: ConnectionWriter = Box::new(write); - Self { - id, - address, diff --git a/crates/pumpkin/src/net/java/play/chat_message.rs b/crates/pumpkin/src/net/java/play/chat_message.rs -index 589277311..ba28c7450 100644 +index 58927731..ba28c745 100644 --- a/crates/pumpkin/src/net/java/play/chat_message.rs +++ b/crates/pumpkin/src/net/java/play/chat_message.rs @@ -266,7 +266,7 @@ impl JavaClient { @@ -1957,7 +1758,7 @@ index 589277311..ba28c7450 100644 let verifying_key = VerifyingKey::::new(key.clone()); verifying_key.verify(&signable, &key_signature).is_ok() diff --git a/crates/pumpkin/src/plugin/loader/mod.rs b/crates/pumpkin/src/plugin/loader/mod.rs -index fa7f8c5ab..61bbe63f1 100644 +index fa7f8c5a..61bbe63f 100644 --- a/crates/pumpkin/src/plugin/loader/mod.rs +++ b/crates/pumpkin/src/plugin/loader/mod.rs @@ -1,8 +1,12 @@ @@ -1983,7 +1784,7 @@ index fa7f8c5ab..61bbe63f1 100644 WasmInitializationError(#[from] PluginInitError), } diff --git a/crates/pumpkin/src/plugin/mod.rs b/crates/pumpkin/src/plugin/mod.rs -index b686898d0..33ed73b36 100644 +index b686898d..33ed73b3 100644 --- a/crates/pumpkin/src/plugin/mod.rs +++ b/crates/pumpkin/src/plugin/mod.rs @@ -1,6 +1,9 @@ @@ -2181,7 +1982,7 @@ index b686898d0..33ed73b36 100644 #[expect(clippy::result_unit_err)] diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs -index 28690a867..5f58b8b58 100644 +index 28690a86..5f58b8b5 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -64,6 +64,7 @@ pub mod ticker; @@ -2235,7 +2036,7 @@ index 28690a867..5f58b8b58 100644 self.scheduled_functions.tick( self, diff --git a/crates/pumpkin/src/server/scheduler.rs b/crates/pumpkin/src/server/scheduler.rs -index e2056b48a..f6221d64a 100644 +index e2056b48..f6221d64 100644 --- a/crates/pumpkin/src/server/scheduler.rs +++ b/crates/pumpkin/src/server/scheduler.rs @@ -1,3 +1,4 @@ @@ -2300,7 +2101,7 @@ index e2056b48a..f6221d64a 100644 #[must_use] pub fn new() -> Self { diff --git a/crates/pumpkin/src/server/ticker.rs b/crates/pumpkin/src/server/ticker.rs -index ae45c2b05..1f35b272b 100644 +index ae45c2b0..1f35b272 100644 --- a/crates/pumpkin/src/server/ticker.rs +++ b/crates/pumpkin/src/server/ticker.rs @@ -16,9 +16,14 @@ use tracing::debug; @@ -2386,7 +2187,7 @@ index ae45c2b05..1f35b272b 100644 if STOP_INTERRUPT.is_cancelled() { break 'ticker; diff --git a/crates/pumpkin/src/world/portal/mod.rs b/crates/pumpkin/src/world/portal/mod.rs -index aeadba752..85bf10aff 100644 +index aeadba75..85bf10af 100644 --- a/crates/pumpkin/src/world/portal/mod.rs +++ b/crates/pumpkin/src/world/portal/mod.rs @@ -92,6 +92,7 @@ impl PortalType { @@ -2405,65 +2206,3 @@ index aeadba752..85bf10aff 100644 if let Ok(handle) = tokio::runtime::Handle::try_current() { tokio::task::block_in_place(|| { handle.block_on(async { -diff --git a/crates/pumpkin/src/net/bedrock/nethernet_stub.rs b/crates/pumpkin/src/net/bedrock/nethernet_stub.rs -new file mode 100644 -index 000000000..90f0989c4 ---- /dev/null -+++ b/crates/pumpkin/src/net/bedrock/nethernet_stub.rs -@@ -0,0 +1,56 @@ -+//! WebRTC-free stub of the NetherNet transport, compiled when the `nethernet` -+//! feature is off (e.g. the emscripten/embedded Java-only build). The types -+//! exist so the always-compiled Bedrock client code type-checks, but no Bedrock -+//! connection is ever created (the listener is never bound), so the method -+//! bodies are unreachable. -+use std::net::SocketAddr; -+use std::sync::Arc; -+ -+use bytes::Bytes; -+use pumpkin_util::p384::PublicKey; -+ -+pub type IncomingSession = (Arc, SocketAddr); -+ -+/// Stub listener: never bound when `nethernet` is off, so `accept` never yields. -+pub struct NetherNetListener { -+ _private: (), -+} -+ -+impl NetherNetListener { -+ pub async fn accept(&self) -> Option { -+ None -+ } -+ -+ pub const fn local_addr(&self) -> SocketAddr { -+ SocketAddr::new(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), 0) -+ } -+} -+ -+/// Stub session. Never constructed when `nethernet` is off. -+pub struct NetherNetSession { -+ _private: (), -+} -+ -+impl NetherNetSession { -+ pub async fn recv(&self) -> Option { -+ None -+ } -+ -+ pub async fn send(&self, _data: Bytes) -> Result<(), String> { -+ Err("NetherNet transport is disabled in this build".to_string()) -+ } -+ -+ pub async fn send_unreliable(&self, _data: Bytes) -> Result<(), String> { -+ Err("NetherNet transport is disabled in this build".to_string()) -+ } -+ -+ pub const fn is_closed(&self) -> bool { -+ true -+ } -+ -+ pub async fn close(&self) {} -+ -+ pub const fn client_public_key(&self) -> Option<&PublicKey> { -+ None -+ } -+} diff --git a/patches/wasm-bindgen-emscripten-closures.patch b/patches/wasm-bindgen-emscripten-closures.patch deleted file mode 100644 index 88700c3..0000000 --- a/patches/wasm-bindgen-emscripten-closures.patch +++ /dev/null @@ -1,30 +0,0 @@ -Subject: [PATCH] Escape Emscripten closure finalizer postsets - -The closure finalizer is emitted inside an Emscripten JavaScript string. -Interpolating its destructor expression directly can leave quotes and -backslashes unescaped, producing invalid generated JavaScript. - -Build the complete postset first, then format it as an escaped string -literal. This preserves the finalizer expression and the fallback used -when FinalizationRegistry is unavailable. - -Base-commit: 4b69f3b3ba4212c857be6854f77fa5aec8b62871 - ---- -diff --git a/crates/cli-support/src/js/mod.rs b/crates/cli-support/src/js/mod.rs -index 4486b980..06d4a28a 100644 ---- a/crates/cli-support/src/js/mod.rs -+++ b/crates/cli-support/src/js/mod.rs -@@ -4326,10 +4326,11 @@ if (require('worker_threads').isMainThread) {{ - }; - - if matches!(self.config.mode, OutputMode::Emscripten) { -+ let postset = format!("CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? {{ register: () => {{}}, unregister: () => {{}} }} : new FinalizationRegistry({prevent_stale});"); - format!( - "addToLibrary({{ - $CLOSURE_DTORS: {{}}, -- $CLOSURE_DTORS__postset: \"CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined') ? {{ register: () => {{}}, unregister: () => {{}} }} : new FinalizationRegistry({prevent_stale});\" -+ $CLOSURE_DTORS__postset: {postset:?} - }});\n" - ) - } else { diff --git a/patches/workers-rs-emscripten-toolchain.patch b/patches/workers-rs-emscripten-toolchain.patch deleted file mode 100644 index 425e670..0000000 --- a/patches/workers-rs-emscripten-toolchain.patch +++ /dev/null @@ -1,184 +0,0 @@ -Subject: [PATCH] Embed workers-rs with the hosted Emscripten runtime - -The embedding host creates each Emscripten module and owns its lifetime. -The SDK's standalone wasm-bindgen loader and recovery hook cannot -reinitialize those instances, so leave initialization and panic handling -to the host on this target. - -Expose an Emscripten-only adapter that drives promise exports on one hosted -Tokio runtime per module and converts injected sockets into SDK streams. -It uses the hosted-runtime Tokio fork selected by the application. Align -the wasm-bindgen, js-sys, and web-sys version family with the pinned -Emscripten-enabled wasm-bindgen CLI so their generated bindings agree. - -Flush outstanding socket writes before closing the WritableStream. This -satisfies AsyncWrite's shutdown contract and releases the writer lock -before close, including when a pending write completes asynchronously. - -Base-commit: 7db011ec97658a5d907f3e3102028ce86c044f19 - ---- -diff --git a/Cargo.toml b/Cargo.toml -index 4e08234..9888881 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -28,7 +28,7 @@ chrono = { version = "0.4.41", default-features = false, features = [ - futures-channel = "0.3.31" - futures-util = { version = "0.3.31", default-features = false } - http = "1.3" --js-sys = { version = "0.3.104" } -+js-sys = { version = "0.3.103" } - serde = { version = "1.0.164", features = ["derive"] } - strum = { version = "0.28", features = ["derive"] } - serde_json = "1.0.140" -@@ -37,14 +37,14 @@ syn = "3.0.3" - trybuild = "1.0" - proc-macro2 = "1.0.60" - quote = "1.0.28" --wasm-bindgen = { version = "0.2.127" } --wasm-bindgen-cli-support = { version = "0.2.127" } --wasm-bindgen-futures = { version = "0.4.77" } --wasm-bindgen-macro-support = { version = "0.2.127" } --wasm-bindgen-shared = { version = "0.2.127" } --wasm-bindgen-test = { version = "0.3.77" } -+wasm-bindgen = { version = "0.2.126" } -+wasm-bindgen-cli-support = { version = "0.2.126" } -+wasm-bindgen-futures = { version = "0.4.76" } -+wasm-bindgen-macro-support = { version = "0.2.126" } -+wasm-bindgen-shared = { version = "0.2.126" } -+wasm-bindgen-test = { version = "0.3.76" } - wasm-streams = { version = "0.6.0" } --web-sys = { version = "0.3.104", features = [ -+web-sys = { version = "0.3.103", features = [ - "AbortController", - "AbortSignal", - "BinaryType", -@@ -106,11 +106,11 @@ opt-level = "z" - # These are local patches we use to test against local wasm bindgen - # We always align on the exact stable wasm bindgen version for releases - [patch.crates-io] --js-sys = { version = "0.3.104", path = './wasm-bindgen/crates/js-sys' } --wasm-bindgen = { version = "0.2.127", path = './wasm-bindgen' } --wasm-bindgen-cli-support = { version = "0.2.127", path = "./wasm-bindgen/crates/cli-support" } --wasm-bindgen-futures = { version = "0.4.77", path = './wasm-bindgen/crates/futures' } --wasm-bindgen-macro-support = { version = "0.2.127", path = "./wasm-bindgen/crates/macro-support" } --wasm-bindgen-shared = { version = "0.2.127", path = "./wasm-bindgen/crates/shared" } --wasm-bindgen-test = { version = "0.3.77", path = "./wasm-bindgen/crates/test" } --web-sys = { version = "0.3.104", path = './wasm-bindgen/crates/web-sys' } -+js-sys = { version = "0.3.103", path = './wasm-bindgen/crates/js-sys' } -+wasm-bindgen = { version = "0.2.126", path = './wasm-bindgen' } -+wasm-bindgen-cli-support = { version = "0.2.126", path = "./wasm-bindgen/crates/cli-support" } -+wasm-bindgen-futures = { version = "0.4.76", path = './wasm-bindgen/crates/futures' } -+wasm-bindgen-macro-support = { version = "0.2.126", path = "./wasm-bindgen/crates/macro-support" } -+wasm-bindgen-shared = { version = "0.2.126", path = "./wasm-bindgen/crates/shared" } -+wasm-bindgen-test = { version = "0.3.76", path = "./wasm-bindgen/crates/test" } -+web-sys = { version = "0.3.103", path = './wasm-bindgen/crates/web-sys' } -diff --git a/worker/Cargo.toml b/worker/Cargo.toml -index 573a8f2..d286f02 100644 ---- a/worker/Cargo.toml -+++ b/worker/Cargo.toml -@@ -45,6 +45,9 @@ web-sys.workspace = true - worker-macros.workspace = true - worker-sys.workspace = true - -+[target.'cfg(target_os = "emscripten")'.dependencies] -+tokio = { version = "1.28", default-features = false, features = ["rt", "time", "net"] } -+ - [features] - queue = ["worker-macros/queue", "worker-sys/queue"] - d1 = ["worker-sys/d1"] -diff --git a/worker/src/lib.rs b/worker/src/lib.rs -index bc2cdc9..72097fe 100644 ---- a/worker/src/lib.rs -+++ b/worker/src/lib.rs -@@ -223,6 +223,11 @@ mod container; - mod context; - mod cors; - pub mod crypto; -+#[cfg(target_os = "emscripten")] -+pub mod emscripten; -+// The embedding host owns Emscripten module instances and panic handling. -+// SDK reinitialization targets wasm-bindgen's standalone loader instead. -+#[cfg(not(target_os = "emscripten"))] - mod init; - // Require pub module for macro export - #[cfg(feature = "d1")] -diff --git a/worker/src/socket.rs b/worker/src/socket.rs -index bc46cd4..a32d2bd 100644 ---- a/worker/src/socket.rs -+++ b/worker/src/socket.rs -@@ -289,6 +289,12 @@ impl AsyncWrite for Socket { - } - - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { -+ // An outstanding write owns the stream's writer lock. Finish it before -+ // closing the WritableStream, as required by AsyncWrite::poll_shutdown. -+ match self.as_mut().poll_flush(cx) { -+ Poll::Ready(Ok(())) => {} -+ pending_or_error => return pending_or_error, -+ } - fn handle_future(cx: &mut Context<'_>, mut fut: JsFuture) -> (Closing, Poll>) { - match fut.poll_unpin(cx) { - Poll::Pending => (Closing::Pending(fut), Poll::Pending), -diff --git a/worker/src/emscripten.rs b/worker/src/emscripten.rs -new file mode 100644 -index 0000000..e3516ed ---- /dev/null -+++ b/worker/src/emscripten.rs -@@ -0,0 +1,56 @@ -+//! Promise exports driven by the Emscripten hosted Tokio runtime. -+//! -+//! Each modularized Wasm instance owns its runtime. This adapter keeps Tokio -+//! timers and I/O on that executor when JavaScript invokes an async Rust export. -+//! It requires the Emscripten-enabled Tokio toolchain. -+ -+use std::future::Future; -+use wasm_bindgen::prelude::*; -+ -+thread_local! { -+ static RUNTIME: tokio::runtime::HostedRuntime = tokio::runtime::Builder::new_current_thread() -+ .enable_all() -+ .build_hosted_event_loop_runtime() -+ .expect("hosted runtime"); -+} -+ -+/// All exports in one wasm instance use the same runtime. Emscripten's module -+/// factory gives each world its own wasm memory, thread locals, and callbacks. -+pub fn future_to_promise(future: F) -> js_sys::Promise -+where -+ F: Future> + 'static, -+{ -+ // Tokio catches future panics and delivers JoinError to the completion callback. -+ let mut future = std::panic::AssertUnwindSafe(Some(future)); -+ js_sys::Promise::new(&mut move |resolve, reject| { -+ let future = future.take().expect("Promise executor runs once"); -+ RUNTIME.with(|runtime| { -+ runtime.schedule(future, move |result| { -+ let result = result -+ .unwrap_or_else(|error| Err(js_sys::Error::new(&error.to_string()).into())); -+ match result { -+ Ok(value) => { -+ let _ = resolve.call1(&JsValue::UNDEFINED, &value); -+ } -+ Err(error) => { -+ let _ = reject.call1(&JsValue::UNDEFINED, &error); -+ } -+ } -+ }); -+ runtime.drive(); -+ }); -+ }) -+} -+ -+pub fn js_error(error: impl std::fmt::Display) -> JsValue { -+ js_sys::Error::new(&error.to_string()).into() -+} -+ -+/// Convert the socket passed to an Emscripten export into the SDK's async stream. -+pub fn socket_from_value(value: JsValue) -> Result { -+ use crate::FromSocket; -+ crate::Socket::from_raw(value.unchecked_into()).map_err(|error| { -+ let error: Box = error.into(); -+ js_error(error) -+ }) -+} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 7fced56..6f67d1c 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2026-07-20" +channel = "beta" profile = "minimal" targets = ["wasm32-unknown-emscripten"] diff --git a/scripts/common.sh b/scripts/common.sh index acc92a8..8a7f652 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -4,6 +4,10 @@ set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WORK="$REPO/.work" NODE="${NODE:-node}" +# Setup provisions the frontend checkout, compiler backend config, and the +# wasm-bindgen CLI under .work/; WASM_BINDGEN_BIN may point at another CLI directory. +EMSCRIPTEN="$WORK/emscripten" +WASM_BINDGEN_BIN="${WASM_BINDGEN_BIN:-$WORK/bin}" require_node() { local major @@ -15,17 +19,23 @@ require_node() { } require_toolchain() { - if [ ! -f "$WORK/emscripten/.emscripten_cf" ] || - [ ! -x "$WORK/bin/wasm-bindgen" ] || + if [ ! -f "$EMSCRIPTEN/.emscripten_cf" ] || + [ ! -x "$EMSCRIPTEN/emcc" ] || + [ ! -x "$WASM_BINDGEN_BIN/wasm-bindgen" ] || [ ! -f "$WORK/pumpkin/crates/pumpkin/Cargo.toml" ] || - [ ! -f "$WORK/workers-rs/worker/Cargo.toml" ]; then + [ ! -f "$WORK/tokio/tokio/Cargo.toml" ]; then echo "error: run bash scripts/setup.sh first." >&2 exit 1 fi - export EM_CONFIG="$WORK/emscripten/.emscripten_cf" - export PATH="$WORK/emscripten:$WORK/bin:$PATH" - export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER="$WORK/emscripten/emcc" + export EM_CONFIG="$EMSCRIPTEN/.emscripten_cf" + export PATH="$EMSCRIPTEN:$WASM_BINDGEN_BIN:$PATH" + export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER="$EMSCRIPTEN/emcc" + # Uniform exnref exception handling for C and Rust objects (see .cargo/config.toml). + export EMCC_CFLAGS="${EMCC_CFLAGS:-} -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0" export CARGO_TARGET_DIR="$REPO/target/workers" + # rustc's LLVM const emission recurses deeply on pumpkin-data's generated tables + # and overflows its default 8 MiB compile-thread stack on current toolchains. + export RUST_MIN_STACK="${RUST_MIN_STACK:-268435456}" } require_packages() { diff --git a/scripts/setup.sh b/scripts/setup.sh index cdb584e..6faeb84 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Clone pinned sources, apply dependency patches, and build the host toolchain. +# Clone pinned sources, apply dependency patches, and provision the toolchain. set -euo pipefail source "$(dirname "${BASH_SOURCE[0]}")/common.sh" @@ -12,7 +12,7 @@ mkdir -p "$WORK" checkout() { # name url branch commit local name="$1" url="$2" branch="$3" commit="$4" dir="$WORK/$1" - if [ ! -d "$dir/.git" ]; then + if [ ! -d "$dir/.git" ] && [ ! -f "$dir/.git" ]; then if [ -e "$dir" ]; then echo "error: $dir exists but is not a Git checkout." >&2 exit 1 @@ -43,51 +43,60 @@ apply_patch() { } echo "==> Pinned dependencies" -checkout emscripten https://github.com/guybedford/emscripten cf 21166256c4c4d73d39b3685c8973d7cbe427ce8c -checkout wasm-bindgen https://github.com/guybedford/wasm-bindgen emscripten-non-identifier-names 4b69f3b3ba4212c857be6854f77fa5aec8b62871 -checkout tokio https://github.com/guybedford/tokio emscripten-layering 7c1d4977c510866775ed6164b58b2218a6a2955b -checkout libc https://github.com/guybedford/libc libc-0.2-emscripten 4091fe0b0dc5f9c1a27bed75be1ff02bb27e756d -checkout ring https://github.com/guybedford/ring emscripten 6671f7cfbb13f249b571ffa6326275a8596e0ca2 checkout pumpkin https://github.com/Pumpkin-MC/Pumpkin master b5b9b9d7010e793806a83c495af223c67e1d35ee -checkout workers-rs https://github.com/ThomasRubini/workers-rs connect-bindings 7db011ec97658a5d907f3e3102028ce86c044f19 -apply_patch wasm-bindgen wasm-bindgen-emscripten-closures.patch +checkout tokio https://github.com/guybedford/tokio emscripten-epoll 8d0a2a845c546e93a4cf0e8ff2c21ac50a3d1931 apply_patch pumpkin pumpkin-emscripten.patch apply_patch pumpkin pumpkin-memory.patch -apply_patch workers-rs workers-rs-emscripten-toolchain.patch if [ "${1:-}" = --sources-only ]; then exit 0; fi -echo "==> Pinned Rust toolchain" +echo "==> Rust toolchain" RUST_CHANNEL="$(python3 -c 'import tomllib,sys; print(tomllib.load(open(sys.argv[1],"rb"))["toolchain"]["channel"])' "$REPO/rust-toolchain.toml")" rustup toolchain install "$RUST_CHANNEL" --profile minimal --target wasm32-unknown-emscripten --no-self-update -echo "==> Emscripten frontend and Homebrew compiler backend" -EM_PREFIX="$(brew --prefix emscripten)" || { - echo "error: install the backend with 'brew install emscripten'." >&2 - exit 1 -} -# Resolve NODE before entering the checkout (it may be a relative path). +echo "==> wasm-bindgen CLI" +WASM_BINDGEN_VERSION="$(python3 -c 'import tomllib,sys; print(tomllib.load(open(sys.argv[1],"rb"))["dependencies"]["wasm-bindgen"])' "$REPO/Cargo.toml")" +if [ "$("$WASM_BINDGEN_BIN/wasm-bindgen" --version 2>/dev/null || true)" != "wasm-bindgen $WASM_BINDGEN_VERSION" ]; then + cargo "+$RUST_CHANNEL" install --force --locked wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --root "$(dirname "$WASM_BINDGEN_BIN")" +fi + +echo "==> Emscripten" +# Frontend: upstream main plus the pending JSPI hooks, reentrant JSPI, and epoll +# listener PRs. LLVM comes from the emscripten-releases build paired with that +# main, and Binaryen from the branch carrying the jspi-hooks pass the frontend +# needs; setup builds it. EMSDK selects an activated emsdk for LLVM instead. +EMSDK_RELEASE=e8579ea489b44a6792f5abf95377a6ee38a16cce +checkout emscripten https://github.com/guybedford/emscripten cf-final a5013dd597ec9d48856370f5707d438937a7b4e8 NODE_PATH="$("$NODE" -p 'process.execPath')" -export PATH="$(dirname "$NODE_PATH"):$PATH" -(cd "$WORK/emscripten" && npm ci --no-audit --no-fund && python3 bootstrap.py) -python3 - "$WORK/emscripten/.emscripten_cf" "$EM_PREFIX" "$NODE_PATH" <<'PY' +(cd "$EMSCRIPTEN" && PATH="$(dirname "$NODE_PATH"):$PATH" npm ci --no-audit --no-fund && python3 bootstrap.py) +if [ -n "${EMSDK:-}" ]; then + LLVM_ROOT="$EMSDK/upstream/bin" +else + checkout emsdk https://github.com/emscripten-core/emsdk main 5eb0bde7585670252e8ba05e9d361627bffd08b5 + if [ "$(cat "$WORK/emsdk/upstream/.emsdk_version" 2>/dev/null)" != "releases-$EMSDK_RELEASE-64bit" ]; then + (cd "$WORK/emsdk" && ./emsdk install "$EMSDK_RELEASE" && ./emsdk activate "$EMSDK_RELEASE") + fi + LLVM_ROOT="$WORK/emsdk/upstream/bin" +fi +checkout binaryen https://github.com/guybedford/binaryen jspi-hooks d6483a04d7dab0ef83e5c43f87343061d97a3b8d +BINARYEN_ROOT="$WORK/binaryen/build" +if [ ! -x "$BINARYEN_ROOT/bin/wasm-opt" ] || [ "$(cat "$BINARYEN_ROOT/.pinned" 2>/dev/null)" != "$(git -C "$WORK/binaryen" rev-parse HEAD)" ]; then + cmake -S "$WORK/binaryen" -B "$BINARYEN_ROOT" -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF >/dev/null + cmake --build "$BINARYEN_ROOT" --target wasm-opt wasm-metadce wasm-emscripten-finalize wasm-split wasm-as wasm-dis wasm2js wasm-merge >/dev/null + git -C "$WORK/binaryen" rev-parse HEAD > "$BINARYEN_ROOT/.pinned" +fi +if [ ! -x "$LLVM_ROOT/clang" ] || [ ! -x "$BINARYEN_ROOT/bin/wasm-opt" ]; then + echo "error: no Emscripten backend at LLVM_ROOT=$LLVM_ROOT BINARYEN_ROOT=$BINARYEN_ROOT." >&2 + exit 1 +fi +python3 - "$EMSCRIPTEN/.emscripten_cf" "$LLVM_ROOT" "$BINARYEN_ROOT" "$NODE_PATH" <<'PY' from pathlib import Path import sys -config, prefix, node = sys.argv[1:] +config, llvm, binaryen, node = sys.argv[1:] Path(config).write_text( - f"LLVM_ROOT={prefix + '/libexec/llvm/bin'!r}\n" - f"BINARYEN_ROOT={prefix + '/libexec/binaryen'!r}\n" + f"LLVM_ROOT={llvm!r}\n" + f"BINARYEN_ROOT={binaryen!r}\n" f"NODE_JS={node!r}\n" ) PY - -echo "==> Patched wasm-bindgen CLI" -# Upstream does not commit this workspace lockfile. Preserve the proven CLI resolution. -cp "$REPO/toolchain/wasm-bindgen.Cargo.lock" "$WORK/wasm-bindgen/Cargo.lock" -# Override nested dependency toolchain files with the repository's dated nightly. -HOST_TARGET="$(rustc "+$RUST_CHANNEL" -vV | sed -n 's/^host: //p')" -(cd "$WORK/wasm-bindgen" && cargo "+$RUST_CHANNEL" build --locked --release -p wasm-bindgen-cli \ - --target "$HOST_TARGET" --target-dir "$WORK/wasm-bindgen/target") -mkdir -p "$WORK/bin" -cp "$WORK/wasm-bindgen/target/$HOST_TARGET/release/wasm-bindgen" "$WORK/bin/wasm-bindgen" echo "Setup complete. Run: bash scripts/test.sh or bash scripts/serve.sh" diff --git a/src/config.rs b/src/config.rs index 22360a8..e9106a7 100644 --- a/src/config.rs +++ b/src/config.rs @@ -13,8 +13,9 @@ pub fn configuration() -> (BasicConfiguration, AdvancedConfiguration) { } let mut adv = AdvancedConfiguration::default(); - // Offline Java server with a bounded view distance. - adv.networking.java.enabled = false; // Connections are supplied by the Durable Object. + // Offline Java server with a bounded view distance. The listener binds the + // default 0.0.0.0:25565 in the Durable Object's port table; the host routes + // inbound sockets to it by port. adv.networking.java.online_mode = false; adv.networking.java.encryption = false; adv.networking.java.view_distance = NonZero::new(3).unwrap(); diff --git a/src/main.rs b/src/main.rs index 5bee898..93bf34e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,71 +1,70 @@ +// `#[wasm_bindgen(jspi)]` is experimental and warns as deprecated. +#![allow(deprecated)] + mod config; mod memory; -use pumpkin::{data::VanillaData, PumpkinServer}; +use pumpkin::{data::VanillaData, server::Server, PumpkinServer}; use std::{cell::RefCell, sync::atomic::Ordering, sync::Arc}; use wasm_bindgen::prelude::*; -use worker::emscripten::{future_to_promise, js_error, socket_from_value}; fn main() {} thread_local! { static FAILURE: RefCell> = const { RefCell::new(None) }; - static SERVER: RefCell>> = const { RefCell::new(None) }; - static NEXT_CLIENT: RefCell = const { RefCell::new(0) }; + static SERVER: RefCell>> = const { RefCell::new(None) }; +} + +fn js_error(error: impl std::fmt::Display) -> JsValue { + js_sys::Error::new(&error.to_string()).into() } -fn server() -> Result, JsValue> { +fn server() -> Result, JsValue> { if let Some(error) = FAILURE.with(|failure| failure.borrow().clone()) { return Err(js_error(error)); } SERVER .with(|server| server.borrow().clone()) - .ok_or_else(|| js_error("Server is not started")) + .ok_or_else(|| js_error("Server is not running")) } -#[wasm_bindgen] -pub fn pumpkin_start() -> js_sys::Promise { - future_to_promise(async { - if SERVER.with(|server| server.borrow().is_some()) { - return Ok(JsValue::UNDEFINED); - } - std::panic::set_hook(Box::new(|info| { - FAILURE.with(|failure| { - failure.borrow_mut().get_or_insert_with(|| info.to_string()); - }); - eprintln!("RUST PANIC: {info}"); - })); - rayon::ThreadPoolBuilder::new() - .num_threads(1) - .use_current_thread() - .build_global() - .map_err(js_error)?; - let (basic, advanced) = config::configuration(); - pumpkin::init_logger(&advanced); - let server = Arc::new(PumpkinServer::new(basic, advanced, VanillaData::load()).await); +/// Runs the server on a Tokio runtime that parks by suspending this activation. +/// `ready` is called once the listener is bound. Resolves after `pumpkin_stop` +/// once the final save completes. +#[wasm_bindgen(jspi)] +pub fn pumpkin_run(ready: js_sys::Function) -> Result<(), JsValue> { + std::panic::set_hook(Box::new(|info| { + FAILURE.with(|failure| { + failure.borrow_mut().get_or_insert_with(|| info.to_string()); + }); + eprintln!("RUST PANIC: {info}"); + })); + rayon::ThreadPoolBuilder::new() + .num_threads(1) + .use_current_thread() + .build_global() + .map_err(js_error)?; + let (basic, advanced) = config::configuration(); + pumpkin::init_logger(&advanced); + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(js_error)?; + runtime.block_on(async { + let server = PumpkinServer::new(basic, advanced, VanillaData::load()).await; server.init_plugins().await; - SERVER.with(|slot| *slot.borrow_mut() = Some(server)); - Ok(JsValue::UNDEFINED) + SERVER.with(|slot| *slot.borrow_mut() = Some(server.server.clone())); + ready.call0(&JsValue::UNDEFINED)?; + server.start().await; + SERVER.with(|slot| slot.borrow_mut().take()); + Ok(()) }) } +/// Requests shutdown; `pumpkin_run` completes the save and returns. #[wasm_bindgen] -pub fn pumpkin_connect(raw: JsValue) -> js_sys::Promise { - future_to_promise(async move { - let server = server()?; - let socket = socket_from_value(raw)?; - let id = NEXT_CLIENT.with(|slot| { - let mut id = slot.borrow_mut(); - *id += 1; - *id - }); - // The SDK's socket is the injected Tokio byte stream. Its adapter handles - // backpressure, EOF and JS stream errors; no emulated TCP listener is used. - server - .serve_connection(socket, ([127, 0, 0, 1], 0).into(), id) - .await; - Ok(JsValue::UNDEFINED) - }) +pub fn pumpkin_stop() { + pumpkin::stop_server(); } #[wasm_bindgen] @@ -75,12 +74,12 @@ pub fn pumpkin_status() -> Result { js_sys::Reflect::set( &result, &"players".into(), - &(server.server.get_all_players().len() as f64).into(), + &(server.get_all_players().len() as f64).into(), )?; js_sys::Reflect::set( &result, &"ticks".into(), - &(server.server.tick_count.load(Ordering::Relaxed) as f64).into(), + &(server.tick_count.load(Ordering::Relaxed) as f64).into(), )?; js_sys::Reflect::set( &result, @@ -92,20 +91,3 @@ pub fn pumpkin_status() -> Result { } Ok(result.into()) } - -#[wasm_bindgen] -pub fn pumpkin_shutdown() -> js_sys::Promise { - future_to_promise(async { - let server = server()?; - if !server.server.get_all_players().is_empty() { - return Err(js_error( - "Disconnect clients before shutting down the world", - )); - } - pumpkin::stop_server(); - server.server.save_all().await.map_err(js_error)?; - server.server.shutdown().await; - SERVER.with(|slot| slot.borrow_mut().take()); - Ok(JsValue::UNDEFINED) - }) -} diff --git a/tests/fixtures/filesystem.rs b/tests/fixtures/filesystem.rs index cf6c210..37a044e 100644 --- a/tests/fixtures/filesystem.rs +++ b/tests/fixtures/filesystem.rs @@ -1,31 +1,42 @@ +#![allow(deprecated)] + use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::TcpListener; use wasm_bindgen::prelude::*; -use worker::emscripten::{future_to_promise, js_error, socket_from_value}; fn main() {} -#[wasm_bindgen] -pub fn echo(socket: JsValue) -> js_sys::Promise { - future_to_promise(async move { - let mut socket = socket_from_value(socket)?; - let mut bytes = [0u8; 16 * 1024]; - loop { - let count = socket.read(&mut bytes).await.map_err(js_error)?; - if count == 0 { - break; - } - socket.write_all(&bytes[..count]).await.map_err(js_error)?; - } - socket.shutdown().await.map_err(js_error)?; - Ok(JsValue::UNDEFINED) - }) +fn js_error(error: impl std::fmt::Display) -> JsValue { + js_sys::Error::new(&error.to_string()).into() } -#[wasm_bindgen] -pub fn tick_after(milliseconds: u32) -> js_sys::Promise { - future_to_promise(async move { - tokio::time::sleep(std::time::Duration::from_millis(milliseconds.into())).await; - Ok(JsValue::TRUE) +/// Echo every accepted connection for the lifetime of the instance. +#[wasm_bindgen(jspi)] +pub fn echo_server(ready: js_sys::Function) -> Result<(), JsValue> { + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build() + .map_err(js_error)?; + runtime.block_on(async { + let listener = TcpListener::bind("0.0.0.0:25565").await.map_err(js_error)?; + ready.call0(&JsValue::UNDEFINED)?; + loop { + let (mut socket, _) = listener.accept().await.map_err(js_error)?; + tokio::spawn(async move { + let mut bytes = [0u8; 16 * 1024]; + loop { + match socket.read(&mut bytes).await { + Ok(0) | Err(_) => break, + Ok(count) => { + if socket.write_all(&bytes[..count]).await.is_err() { + break; + } + } + } + } + let _ = socket.shutdown().await; + }); + } }) } diff --git a/tests/fixtures/worker.mjs b/tests/fixtures/worker.mjs index f8300ac..dbde6d3 100644 --- a/tests/fixtures/worker.mjs +++ b/tests/fixtures/worker.mjs @@ -1,10 +1,13 @@ import { DurableObject, exports } from 'cloudflare:workers'; +import { handleAsNodeConnection } from 'cloudflare:node'; export { StartupFailure } from './startup-failure.mjs'; import createModule from '../../target/workers/wasm32-unknown-emscripten/release/filesystem-probe.js'; import wasm from '../../target/workers/wasm32-unknown-emscripten/release/filesystem_probe.wasm'; import { createWorldRuntime } from '../../worker/filesystem'; import { forwardSocket } from '../../worker/socket'; +const PORT = 25565; + export class FilesystemTest extends DurableObject { runtime; @@ -16,18 +19,23 @@ export class FilesystemTest extends DurableObject { receive(instance); return instance.exports; }, - })); + })).then(async runtime => { + await new Promise((ready, reject) => { + runtime.run(() => runtime.instance.echo_server(ready)).catch(reject); + }); + return runtime; + }); } async connect(socket) { void socket.closed.catch(() => undefined); - const runtime = await this.getRuntime(); - await runtime.run(() => runtime.instance.echo(socket)); + await this.getRuntime(); + await handleAsNodeConnection(socket); } async status() { - const runtime = await this.getRuntime(); - return { timer: await runtime.run(() => runtime.instance.tick_after(20)) }; + await this.getRuntime(); + return { listening: true }; } async filesystemProbe(value) { @@ -38,7 +46,7 @@ export class FilesystemTest extends DurableObject { export default { async connect(socket, env) { - const target = exports.FilesystemTest.getByName(env.WORLD_NAME).connect('world:25565', { allowHalfOpen: true }); + const target = exports.FilesystemTest.getByName(env.WORLD_NAME).connect(`world:${PORT}`, { allowHalfOpen: true }); await forwardSocket(socket, target); }, async fetch(request, env) { diff --git a/tests/integration.mjs b/tests/integration.mjs index ddeceb7..9c9a9eb 100644 --- a/tests/integration.mjs +++ b/tests/integration.mjs @@ -58,7 +58,7 @@ async function marker(world, value) { let worker = await startWorker(true); try { await Promise.all([echo(), echo()]); - assert.equal((await request('/')).timer, true); + assert.equal((await request('/')).listening, true); const failure = await request('/startup-failure'); assert.equal(failure.starting.phase, 'starting'); assert.equal(failure.failed.phase, 'failed'); diff --git a/toolchain/wasm-bindgen.Cargo.lock b/toolchain/wasm-bindgen.Cargo.lock deleted file mode 100644 index e772975..0000000 --- a/toolchain/wasm-bindgen.Cargo.lock +++ /dev/null @@ -1,4510 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "add" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "adler2" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "aho-corasick" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" -dependencies = [ - "memchr", -] - -[[package]] -name = "alloc-no-stdlib" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" - -[[package]] -name = "alloc-stdlib" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" -dependencies = [ - "alloc-no-stdlib", -] - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] -name = "anstream" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" -dependencies = [ - "anstyle", - "anstyle-parse", - "anstyle-query", - "anstyle-wincon", - "colorchoice", - "is_terminal_polyfill", - "utf8parse", -] - -[[package]] -name = "anstyle" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" - -[[package]] -name = "anstyle-parse" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" -dependencies = [ - "utf8parse", -] - -[[package]] -name = "anstyle-query" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "anstyle-wincon" -version = "3.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" -dependencies = [ - "anstyle", - "once_cell_polyfill", - "windows-sys 0.61.2", -] - -[[package]] -name = "anyhow" -version = "1.0.104" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" - -[[package]] -name = "approx" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" -dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "ascii" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" - -[[package]] -name = "askama" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b8246bcbf8eb97abef10c2d92166449680d41d55c0fc6978a91dec2e3619608" -dependencies = [ - "askama_macros", - "itoa", - "percent-encoding", - "serde", - "serde_json", -] - -[[package]] -name = "askama_derive" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f9670bc84a28bb3da91821ef74226949ab63f1265aff7c751634f1dd0e6f97c" -dependencies = [ - "askama_parser", - "basic-toml", - "memchr", - "proc-macro2", - "quote", - "rustc-hash", - "serde", - "serde_derive", - "syn 2.0.119", -] - -[[package]] -name = "askama_macros" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0756b45480437dded0565dfc568af62ccce146fb6cfe902e808ba86e445f44f" -dependencies = [ - "askama_derive", -] - -[[package]] -name = "askama_parser" -version = "0.15.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0af3691ba3af77949c0b5a3925444b85cb58a0184cc7fec16c68ba2e7be868" -dependencies = [ - "rustc-hash", - "serde", - "serde_derive", - "unicode-ident", - "winnow 1.0.4", -] - -[[package]] -name = "assert_cmd" -version = "2.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aa3a22042e45de04255c7bf3626e239f450200fd0493c1e382263544b20aea6" -dependencies = [ - "anstyle", - "bstr", - "libc", - "predicates", - "predicates-core", - "predicates-tree", - "wait-timeout", -] - -[[package]] -name = "async-trait" -version = "0.1.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae36dc4177970ef04fde5178d3e2429882def40e57a451f919c098f72baa6cec" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.2", -] - -[[package]] -name = "atomic-waker" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" - -[[package]] -name = "autocfg" -version = "1.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" - -[[package]] -name = "aws-lc-rs" -version = "1.17.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00bdb5da18dac48ca2cc7cd4a98e533e8635a58e2361d13a1a4ee3888e0d72f1" -dependencies = [ - "aws-lc-sys", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43103168cc76fe62678a375e722fc9cb3a0146159ac5828bc4f0dfd755c2224c" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", - "pkg-config", -] - -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - -[[package]] -name = "base64ct" -version = "1.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" - -[[package]] -name = "basic-toml" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" -dependencies = [ - "serde", -] - -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad18937a628ec6abcd26d1489012cc0e18c21798210f491af69ded9b881106d" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" - -[[package]] -name = "brotli-decompressor" -version = "5.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" -dependencies = [ - "alloc-no-stdlib", - "alloc-stdlib", -] - -[[package]] -name = "bstr" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530" -dependencies = [ - "memchr", - "regex-automata", - "serde_core", -] - -[[package]] -name = "buf_redux" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" -dependencies = [ - "memchr", - "safemem", -] - -[[package]] -name = "bumpalo" -version = "3.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" - -[[package]] -name = "bytemuck" -version = "1.25.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" - -[[package]] -name = "canvas" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - -[[package]] -name = "cc" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89588d05638b5b4594a3348a2d6c20277e43a7f5c5202b05cc56888475a47b8" -dependencies = [ - "find-msvc-tools", - "jobserver", - "libc", - "shlex 2.0.1", -] - -[[package]] -name = "cfg-if" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" - -[[package]] -name = "cfg_aliases" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" - -[[package]] -name = "chacha20" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" -dependencies = [ - "cfg-if", - "cpufeatures", - "rand_core 0.10.1", -] - -[[package]] -name = "char" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "chrono" -version = "0.4.45" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits 0.2.19", - "wasm-bindgen", - "windows-link", -] - -[[package]] -name = "chunked_transfer" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" - -[[package]] -name = "clap" -version = "4.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fb99565819980999fb7b4a1796046a5c949e6d4ff132cf5fadf5a641e20d776" -dependencies = [ - "clap_builder", - "clap_derive", -] - -[[package]] -name = "clap_builder" -version = "4.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" -dependencies = [ - "anstream", - "anstyle", - "clap_lex", - "strsim", -] - -[[package]] -name = "clap_derive" -version = "4.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32f2392eae7f16557a3d727ef3a12e57b2b2ca6f98566a5f4fb41ffe305df077" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "clap_lex" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" - -[[package]] -name = "closures" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "colorchoice" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - -[[package]] -name = "console_log" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "console_log" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86919cef3e37b9356ccf54d4421208c17ecfda01beae61393e7ffd72916c0ef1" -dependencies = [ - "log", - "web-sys", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "cpufeatures" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" -dependencies = [ - "libc", -] - -[[package]] -name = "crc32fast" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5181e0de7b61eb03a81e347d6dd8797bae9da5146707b51077e2d71a54ec0ceb" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" - -[[package]] -name = "crunchy" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" - -[[package]] -name = "deflate" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "707b6a7b384888a70c8d2e8650b3e60170dfc6a67bb4aa67b6dfca57af4bedb4" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "defmt" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" -dependencies = [ - "bitflags 1.3.2", - "defmt-macros", -] - -[[package]] -name = "defmt-macros" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" -dependencies = [ - "defmt-parser", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "defmt-parser" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" -dependencies = [ - "thiserror 2.0.19", -] - -[[package]] -name = "der" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69dedd701da44b0536442edf09c81a64b0ab97a7a4a5e3d1971f00027cbc63d" -dependencies = [ - "pem-rfc7468", - "zeroize", -] - -[[package]] -name = "deranged" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" - -[[package]] -name = "diff" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" - -[[package]] -name = "difflib" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" - -[[package]] -name = "displaydoc" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "dom" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - -[[package]] -name = "either" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" - -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "enum_primitive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4551092f4d519593039259a9ed8daedf0da12e5109c5280338073eaeb81180" -dependencies = [ - "num-traits 0.1.43", -] - -[[package]] -name = "env_filter" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217" -dependencies = [ - "log", - "regex", -] - -[[package]] -name = "env_logger" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6" -dependencies = [ - "anstream", - "anstyle", - "env_filter", - "jiff", - "log", -] - -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - -[[package]] -name = "errno" -version = "0.3.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "explicit-resource-management" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "fallible-iterator" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" - -[[package]] -name = "fastrand" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" - -[[package]] -name = "fetch" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "filetime" -version = "0.2.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" -dependencies = [ - "cfg-if", - "libc", -] - -[[package]] -name = "find-msvc-tools" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" - -[[package]] -name = "flate2" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" -dependencies = [ - "crc32fast", - "miniz_oxide", -] - -[[package]] -name = "float-cmp" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" -dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foldhash" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "form_urlencoded" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-channel-preview" -version = "0.3.0-alpha.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e5f4df964fa9c1c2f8bddeb5c3611631cacd93baf810fc8bb2fb4b495c263a" -dependencies = [ - "futures-core-preview", -] - -[[package]] -name = "futures-core" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" - -[[package]] -name = "futures-core-preview" -version = "0.3.0-alpha.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35b6263fb1ef523c3056565fa67b1d16f0a8604ff12b11b08c25f28a734c60a" - -[[package]] -name = "futures-executor" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" - -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "futures-macro" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "futures-sink" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" - -[[package]] -name = "futures-task" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" - -[[package]] -name = "futures-timer" -version = "3.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" - -[[package]] -name = "futures-util" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "slab", -] - -[[package]] -name = "getrandom" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", - "wasm-bindgen", -] - -[[package]] -name = "getrandom" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -dependencies = [ - "cfg-if", - "js-sys", - "libc", - "r-efi", - "rand_core 0.10.1", - "wasm-bindgen", -] - -[[package]] -name = "gg-alloc" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee5134f1abeb59e9ba515150ddc26bf2b2207f8bce38e1f5cf8d1596efa22ba" - -[[package]] -name = "gif" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e41945ba23db3bf51b24756d73d81acb4f28d85c3dccc32c6fae904438c25f" -dependencies = [ - "color_quant", - "lzw", -] - -[[package]] -name = "gimli" -version = "0.32.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" -dependencies = [ - "fallible-iterator", - "indexmap", - "stable_deref_trait", -] - -[[package]] -name = "glob" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" - -[[package]] -name = "gloo" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" -dependencies = [ - "gloo-console", - "gloo-dialogs", - "gloo-events", - "gloo-file", - "gloo-history", - "gloo-net", - "gloo-render", - "gloo-storage", - "gloo-timers", - "gloo-utils", - "gloo-worker", -] - -[[package]] -name = "gloo-console" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" -dependencies = [ - "gloo-utils", - "js-sys", - "serde", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-dialogs" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-events" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-file" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" -dependencies = [ - "gloo-events", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-history" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" -dependencies = [ - "getrandom 0.2.17", - "gloo-events", - "gloo-utils", - "serde", - "serde-wasm-bindgen", - "serde_urlencoded", - "thiserror 1.0.69", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-net" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" -dependencies = [ - "futures-channel", - "futures-core", - "futures-sink", - "gloo-utils", - "http 0.2.12", - "js-sys", - "pin-project", - "serde", - "serde_json", - "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-render" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-storage" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" -dependencies = [ - "gloo-utils", - "js-sys", - "serde", - "serde_json", - "thiserror 1.0.69", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "gloo-utils" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" -dependencies = [ - "js-sys", - "serde", - "serde_json", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "gloo-worker" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" -dependencies = [ - "bincode", - "futures", - "gloo-utils", - "gloo-worker-macros", - "js-sys", - "pinned", - "serde", - "thiserror 1.0.69", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "gloo-worker-macros" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" -dependencies = [ - "proc-macro-crate 1.3.1", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "guide-supported-types-examples" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "h2" -version = "0.4.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.4.2", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[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 = "hashbrown" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -dependencies = [ - "foldhash", - "serde", - "serde_core", -] - -[[package]] -name = "hashbrown" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hello_world" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" -dependencies = [ - "bytes", - "itoa", -] - -[[package]] -name = "http-body" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" -dependencies = [ - "bytes", - "http 1.4.2", -] - -[[package]] -name = "http-body-util" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" -dependencies = [ - "bytes", - "futures-core", - "http 1.4.2", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "httparse" -version = "1.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humantime" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" - -[[package]] -name = "hyper" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" -dependencies = [ - "atomic-waker", - "bytes", - "futures-channel", - "futures-core", - "h2", - "http 1.4.2", - "http-body", - "httparse", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" -dependencies = [ - "http 1.4.2", - "hyper", - "hyper-util", - "rustls", - "tokio", - "tokio-rustls", - "tower-service", -] - -[[package]] -name = "hyper-util" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" -dependencies = [ - "base64 0.22.1", - "bytes", - "futures-channel", - "futures-util", - "http 1.4.2", - "http-body", - "hyper", - "ipnet", - "libc", - "percent-encoding", - "pin-project-lite", - "socket2", - "system-configuration", - "tokio", - "tower-service", - "tracing", - "windows-registry", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "icu_collections" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" -dependencies = [ - "displaydoc", - "potential_utf", - "utf8_iter", - "yoke", - "zerofrom", - "zerovec", -] - -[[package]] -name = "icu_locale_core" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" -dependencies = [ - "displaydoc", - "litemap", - "tinystr", - "writeable", - "zerovec", -] - -[[package]] -name = "icu_normalizer" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" -dependencies = [ - "icu_collections", - "icu_normalizer_data", - "icu_properties", - "icu_provider", - "smallvec", - "zerovec", -] - -[[package]] -name = "icu_normalizer_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" - -[[package]] -name = "icu_properties" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" -dependencies = [ - "icu_collections", - "icu_locale_core", - "icu_properties_data", - "icu_provider", - "zerotrie", - "zerovec", -] - -[[package]] -name = "icu_properties_data" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" - -[[package]] -name = "icu_provider" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" -dependencies = [ - "displaydoc", - "icu_locale_core", - "writeable", - "yoke", - "zerofrom", - "zerotrie", - "zerovec", -] - -[[package]] -name = "id-arena" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" -dependencies = [ - "rayon", -] - -[[package]] -name = "idna" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" -dependencies = [ - "idna_adapter", - "smallvec", - "utf8_iter", -] - -[[package]] -name = "idna_adapter" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" -dependencies = [ - "icu_normalizer", - "icu_properties", -] - -[[package]] -name = "image" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d95816db758249fe16f23a4e23f1a3a817fe11892dbfd1c5836f625324702158" -dependencies = [ - "byteorder", - "enum_primitive", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational 0.1.43", - "num-traits 0.1.43", - "png", - "scoped_threadpool", -] - -[[package]] -name = "import_js" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "indexmap" -version = "2.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" -dependencies = [ - "equivalent", - "hashbrown 0.17.1", - "serde", - "serde_core", -] - -[[package]] -name = "inflate" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7e0062d2dc2f17d2f13750d95316ae8a2ff909af0fda957084f5defd87c43bb" - -[[package]] -name = "ipnet" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" - -[[package]] -name = "is_terminal_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" - -[[package]] -name = "itoa" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" - -[[package]] -name = "jiff" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e184d09547b80eb7e20d141ba2fb1fbac843ca53f4cf1b31210adc4c1adc6e16" -dependencies = [ - "defmt", - "jiff-core", - "jiff-static", - "log", - "portable-atomic", - "portable-atomic-util", - "serde_core", -] - -[[package]] -name = "jiff-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" -dependencies = [ - "defmt", -] - -[[package]] -name = "jiff-static" -version = "0.2.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "323da076b7a6faf914dc677cb05a4b907742ff7375c8322c9e7f5061e5e0e9de" -dependencies = [ - "jiff-core", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jni" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" -dependencies = [ - "cfg-if", - "combine", - "jni-macros", - "jni-sys", - "log", - "simd_cesu8", - "thiserror 2.0.19", - "walkdir", - "windows-link", -] - -[[package]] -name = "jni-macros" -version = "0.22.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" -dependencies = [ - "proc-macro2", - "quote", - "rustc_version", - "simd_cesu8", - "syn 2.0.119", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" -dependencies = [ - "quote", - "syn 2.0.119", -] - -[[package]] -name = "jobserver" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" -dependencies = [ - "getrandom 0.4.3", - "libc", -] - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "js-sys" -version = "0.3.103" -dependencies = [ - "cfg-if", - "futures-channel", - "futures-core", - "futures-lite", - "futures-util", - "half", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test", - "web-sys", -] - -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - -[[package]] -name = "julia_set" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "leb128" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c83bff1d572d6b9aeef67ddfc8448e4a3737909cb28e81f97c791b9018703e52" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "libc" -version = "0.2.186" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" - -[[package]] -name = "libm" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" - -[[package]] -name = "linux-raw-sys" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" - -[[package]] -name = "litemap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" - -[[package]] -name = "log" -version = "0.4.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" - -[[package]] -name = "lru-slab" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" - -[[package]] -name = "lzw" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d947cbb889ed21c2a84be6ffbaebf5b4e0f4340638cba0444907e38b56be084" - -[[package]] -name = "matrixmultiply" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f607c237553f086e7043417a51df26b2eb899d3caff94e6a67592ff992fedc7" -dependencies = [ - "autocfg", - "rawpointer", -] - -[[package]] -name = "memchr" -version = "2.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" - -[[package]] -name = "mime" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" - -[[package]] -name = "mime_guess" -version = "2.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minicov" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4869b6a491569605d66d3952bcdf03df789e5b536e5f0cf7758a7f08a55ae24d" -dependencies = [ - "cc", - "walkdir", -] - -[[package]] -name = "miniz_oxide" -version = "0.8.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" -dependencies = [ - "adler2", - "simd-adler32", -] - -[[package]] -name = "mio" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.61.2", -] - -[[package]] -name = "multipart" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00dec633863867f29cb39df64a397cdf4a6354708ddd7759f70c7fb51c5f9182" -dependencies = [ - "buf_redux", - "httparse", - "log", - "mime", - "mime_guess", - "quick-error", - "rand 0.8.7", - "safemem", - "tempfile", - "twoway", -] - -[[package]] -name = "nalgebra" -version = "0.33.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d43ddcacf343185dfd6de2ee786d9e8b1c2301622afab66b6c73baf9882abfd" -dependencies = [ - "approx", - "matrixmultiply", - "num-complex", - "num-rational 0.4.2", - "num-traits 0.2.19", - "rand 0.8.7", - "rand_distr", - "simba", - "typenum", -] - -[[package]] -name = "native-tls" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" -dependencies = [ - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - -[[package]] -name = "no-std" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "node_and_deno" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "nodejs-threads" -version = "0.1.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "nom" -version = "5.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" -dependencies = [ - "memchr", - "version_check", -] - -[[package]] -name = "normalize-line-endings" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" - -[[package]] -name = "nu-ansi-term" -version = "0.50.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "num-bigint" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" -dependencies = [ - "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "num-conv" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "num-iter" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" -dependencies = [ - "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-rational" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbfff0773e8a07fb033d726b9ff1327466709820788e5298afce4d752965ff1e" -dependencies = [ - "autocfg", - "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits 0.2.19", -] - -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" -dependencies = [ - "num-traits 0.2.19", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "num_threads" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" -dependencies = [ - "libc", -] - -[[package]] -name = "once_cell" -version = "1.21.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" - -[[package]] -name = "once_cell_polyfill" -version = "1.70.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" - -[[package]] -name = "oorandom" -version = "11.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" - -[[package]] -name = "openssl" -version = "0.10.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77823a27f0babb03091cb9ed9ef80af3b39dbc82f97e8fa530374b7dafd87a45" -dependencies = [ - "bitflags 2.13.1", - "cfg-if", - "foreign-types", - "libc", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - -[[package]] -name = "openssl-src" -version = "300.6.1+3.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46eb8fb9fb3b61ce1c0f8a026c4c1a0714d3a9e138e7fbde78753ce2babc3846" -dependencies = [ - "cc", -] - -[[package]] -name = "openssl-sys" -version = "0.9.117" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b47e7e6bb2c38cd930d25a23b40fa52e068c10e85f3e03a7f5ba5aaca5713695" -dependencies = [ - "cc", - "libc", - "openssl-src", - "pkg-config", - "vcpkg", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "pem-rfc7468" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6305423e0e7738146434843d1694d621cce767262b2a86910beab705e4493d9" -dependencies = [ - "base64ct", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - -[[package]] -name = "performance" -version = "0.0.0" -dependencies = [ - "humantime", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "pin-project" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" - -[[package]] -name = "pinned" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" -dependencies = [ - "futures", - "rustversion", - "thiserror 1.0.69", -] - -[[package]] -name = "pkg-config" -version = "0.3.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" - -[[package]] -name = "png" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb773e9a557edb568ce9935cf783e3cdcabe06a9449d41b3e5506d88e582c82" -dependencies = [ - "bitflags 0.7.0", - "deflate", - "inflate", - "num-iter", -] - -[[package]] -name = "portable-atomic" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3" - -[[package]] -name = "portable-atomic-util" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" -dependencies = [ - "portable-atomic", -] - -[[package]] -name = "potential_utf" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" -dependencies = [ - "zerovec", -] - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "predicates" -version = "3.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" -dependencies = [ - "anstyle", - "difflib", - "float-cmp", - "normalize-line-endings", - "predicates-core", - "regex", -] - -[[package]] -name = "predicates-core" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" - -[[package]] -name = "predicates-tree" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" -dependencies = [ - "predicates-core", - "termtree", -] - -[[package]] -name = "pretty_assertions" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" -dependencies = [ - "diff", - "yansi", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit 0.19.15", -] - -[[package]] -name = "proc-macro-crate" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" -dependencies = [ - "toml_edit 0.25.13+spec-1.1.0", -] - -[[package]] -name = "proc-macro2" -version = "1.0.107" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - -[[package]] -name = "quinn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" -dependencies = [ - "bytes", - "cfg_aliases", - "pin-project-lite", - "quinn-proto", - "quinn-udp", - "rustc-hash", - "rustls", - "socket2", - "thiserror 2.0.19", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-proto" -version = "0.11.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" -dependencies = [ - "aws-lc-rs", - "bytes", - "getrandom 0.4.3", - "lru-slab", - "rand 0.10.2", - "rand_pcg", - "ring", - "rustc-hash", - "rustls", - "rustls-pki-types", - "slab", - "thiserror 2.0.19", - "tinyvec", - "tracing", - "web-time", -] - -[[package]] -name = "quinn-udp" -version = "0.5.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" -dependencies = [ - "cfg_aliases", - "libc", - "once_cell", - "socket2", - "tracing", - "windows-sys 0.61.2", -] - -[[package]] -name = "quote" -version = "1.0.47" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" - -[[package]] -name = "rand" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" -dependencies = [ - "chacha20", - "getrandom 0.4.3", - "rand_core 0.10.1", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - -[[package]] -name = "rand_distr" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" -dependencies = [ - "num-traits 0.2.19", - "rand 0.8.7", -] - -[[package]] -name = "rand_pcg" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" -dependencies = [ - "rand_core 0.10.1", -] - -[[package]] -name = "rawpointer" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" - -[[package]] -name = "rayon" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "raytrace-parallel" -version = "0.0.0" -dependencies = [ - "console_error_panic_hook", - "futures-channel-preview", - "js-sys", - "rayon", - "rayon-core", - "raytracer", - "serde-wasm-bindgen", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "raytracer" -version = "0.1.0" -source = "git+https://github.com/alexcrichton/raytracer?branch=update-deps#42faa13859f7d8d47fd18be785c108003a207786" -dependencies = [ - "image", - "serde", - "serde_derive", -] - -[[package]] -name = "regex" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" - -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - -[[package]] -name = "request-animation-frame" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "reqwest" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" -dependencies = [ - "base64 0.22.1", - "bytes", - "encoding_rs", - "futures-core", - "h2", - "http 1.4.2", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "mime", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls", - "rustls-pki-types", - "rustls-platform-verifier", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "ring" -version = "0.17.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" -dependencies = [ - "cc", - "cfg-if", - "getrandom 0.2.17", - "libc", - "untrusted", - "windows-sys 0.52.0", -] - -[[package]] -name = "rouille" -version = "3.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3716fbf57fc1084d7a706adf4e445298d123e4a44294c4e8213caf1b85fcc921" -dependencies = [ - "base64 0.13.1", - "chrono", - "filetime", - "multipart", - "percent-encoding", - "rand 0.8.7", - "serde", - "serde_derive", - "serde_json", - "sha1_smol", - "threadpool", - "time", - "tiny_http", - "url", -] - -[[package]] -name = "rstest" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5a3193c063baaa2a95a33f03035c8a72b83d97a54916055ba22d35ed3839d49" -dependencies = [ - "futures-timer", - "futures-util", - "rstest_macros", -] - -[[package]] -name = "rstest_macros" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c845311f0ff7951c5506121a9ad75aec44d083c31583b2ea5a30bcb0b0abba0" -dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate 3.5.0", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.119", - "unicode-ident", -] - -[[package]] -name = "rust-duck-typed-interfaces" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "rust-webassembly-weather-reports" -version = "0.0.0" -dependencies = [ - "chrono", - "gloo", - "json", - "reqwest", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "rustc-demangle" -version = "0.1.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" - -[[package]] -name = "rustc-hash" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[package]] -name = "rustix" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" -dependencies = [ - "bitflags 2.13.1", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls" -version = "0.23.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "ring", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pki-types" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" -dependencies = [ - "web-time", - "zeroize", -] - -[[package]] -name = "rustls-platform-verifier" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" -dependencies = [ - "core-foundation 0.10.1", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - -[[package]] -name = "rustls-webpki" -version = "0.103.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" -dependencies = [ - "aws-lc-rs", - "ring", - "rustls-pki-types", - "untrusted", -] - -[[package]] -name = "rustversion" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" - -[[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] -name = "safe_arch" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" -dependencies = [ - "bytemuck", -] - -[[package]] -name = "safemem" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "sample" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.10.1", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "semver" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" - -[[package]] -name = "serde" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde-wasm-bindgen" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" -dependencies = [ - "js-sys", - "serde", - "wasm-bindgen", -] - -[[package]] -name = "serde_core" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.229" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.2", -] - -[[package]] -name = "serde_json" -version = "1.0.151" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" -dependencies = [ - "itoa", - "memchr", - "serde", - "serde_core", - "zmij", -] - -[[package]] -name = "serde_spanned" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" -dependencies = [ - "serde_core", -] - -[[package]] -name = "serde_urlencoded" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" -dependencies = [ - "form_urlencoded", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "sha1_smol" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbfa15b3dddfee50a0fff136974b3e1bde555604ba463834a7eb7deb6417705d" - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "shlex" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" - -[[package]] -name = "simba" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99284beb21666094ba2b75bbceda012e610f5479dfcc2d6e2426f53197ffd95" -dependencies = [ - "approx", - "num-complex", - "num-traits 0.2.19", - "paste", - "wide", -] - -[[package]] -name = "simd-adler32" -version = "0.3.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" - -[[package]] -name = "simd_cesu8" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" -dependencies = [ - "rustc_version", - "simdutf8", -] - -[[package]] -name = "simdutf8" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" - -[[package]] -name = "slab" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" - -[[package]] -name = "smallvec" -version = "1.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" - -[[package]] -name = "socket2" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "sourcefile" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654ee0ca20343b1b2d6cb222c3c136b6209e20324ef53f7bde19cdd1e766e986" - -[[package]] -name = "stable_deref_trait" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" - -[[package]] -name = "statrs" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a3fe7c28c6512e766b0874335db33c94ad7b8f9054228ae1c2abd47ce7d335e" -dependencies = [ - "approx", - "nalgebra", - "num-traits 0.2.19", - "rand 0.8.7", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.119" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "sync_wrapper" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" -dependencies = [ - "futures-core", -] - -[[package]] -name = "synchronous-instantiation" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "synstructure" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "system-configuration" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" -dependencies = [ - "bitflags 2.13.1", - "core-foundation 0.9.4", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "target-triple" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3a6bfce3d99adfa72d24750a61f782f3036a81e7f86d8841ee1326deaebd171" - -[[package]] -name = "tempfile" -version = "3.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" -dependencies = [ - "fastrand", - "getrandom 0.4.3", - "once_cell", - "rustix", - "windows-sys 0.61.2", -] - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "termtree" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" -dependencies = [ - "thiserror-impl 2.0.19", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" -dependencies = [ - "proc-macro2", - "quote", - "syn 3.0.2", -] - -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - -[[package]] -name = "time" -version = "0.3.54" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" -dependencies = [ - "deranged", - "libc", - "num-conv", - "num_threads", - "powerfmt", - "serde_core", - "time-core", -] - -[[package]] -name = "time-core" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" - -[[package]] -name = "tiny_http" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" -dependencies = [ - "ascii", - "chunked_transfer", - "httpdate", - "log", -] - -[[package]] -name = "tinystr" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" -dependencies = [ - "displaydoc", - "zerovec", -] - -[[package]] -name = "tinyvec" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "todomvc" -version = "0.0.0" -dependencies = [ - "askama", - "console_error_panic_hook", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "tokio" -version = "1.53.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" -dependencies = [ - "bytes", - "libc", - "mio", - "pin-project-lite", - "socket2", - "tokio-macros", - "windows-sys 0.61.2", -] - -[[package]] -name = "tokio-macros" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "toml" -version = "1.1.3+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" -dependencies = [ - "indexmap", - "serde_core", - "serde_spanned", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "toml_writer", - "winnow 1.0.4", -] - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_datetime" -version = "1.1.1+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" -dependencies = [ - "serde_core", -] - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap", - "toml_datetime 0.6.11", - "winnow 0.5.40", -] - -[[package]] -name = "toml_edit" -version = "0.25.13+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" -dependencies = [ - "indexmap", - "toml_datetime 1.1.1+spec-1.1.0", - "toml_parser", - "winnow 1.0.4", -] - -[[package]] -name = "toml_parser" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" -dependencies = [ - "winnow 1.0.4", -] - -[[package]] -name = "toml_writer" -version = "1.1.2+spec-1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" - -[[package]] -name = "tower" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" -dependencies = [ - "futures-core", - "futures-util", - "pin-project-lite", - "sync_wrapper", - "tokio", - "tower-layer", - "tower-service", -] - -[[package]] -name = "tower-http" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" -dependencies = [ - "bitflags 2.13.1", - "bytes", - "futures-util", - "http 1.4.2", - "http-body", - "pin-project-lite", - "tower", - "tower-layer", - "tower-service", - "url", -] - -[[package]] -name = "tower-layer" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" - -[[package]] -name = "tower-service" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" - -[[package]] -name = "tracing" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" -dependencies = [ - "pin-project-lite", - "tracing-core", -] - -[[package]] -name = "tracing-core" -version = "0.1.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" -dependencies = [ - "once_cell", -] - -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - -[[package]] -name = "trybuild" -version = "1.0.118" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06649c6f63d86604ba0c8950d5a1829fc9a17afd70fc6629f481d75b6a624c78" -dependencies = [ - "glob", - "serde", - "serde_derive", - "serde_json", - "target-triple", - "termcolor", - "toml", -] - -[[package]] -name = "twoway" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" -dependencies = [ - "memchr", -] - -[[package]] -name = "typenum" -version = "1.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" - -[[package]] -name = "typescript-tests" -version = "0.0.0" -dependencies = [ - "js-sys", - "serde", - "serde-wasm-bindgen", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "unicase" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" - -[[package]] -name = "unicode-ident" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" - -[[package]] -name = "unicode-width" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64 0.22.1", - "brotli-decompressor", - "der", - "flate2", - "log", - "native-tls", - "percent-encoding", - "rustls", - "rustls-pki-types", - "ureq-proto", - "utf8-zero", - "webpki-root-certs", - "webpki-roots", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64 0.22.1", - "http 1.4.2", - "httparse", - "log", -] - -[[package]] -name = "url" -version = "2.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", - "serde", -] - -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - -[[package]] -name = "utf8_iter" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" - -[[package]] -name = "utf8parse" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" - -[[package]] -name = "vcpkg" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wait-timeout" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" -dependencies = [ - "libc", -] - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "walrus" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bfa49767bb3a9e1afb02aa95bbcbde8d82f2db4ca377afae94d688f14f62378" -dependencies = [ - "anyhow", - "gimli", - "id-arena", - "leb128", - "log", - "rayon", - "walrus-macro", - "wasm-encoder 0.245.1", - "wasmparser 0.245.1", -] - -[[package]] -name = "walrus-macro" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a9b0525d7ea6e5f906aca581a172e5c91b4c595290dfa8ad4a2bc9ffef33b44" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - -[[package]] -name = "wasm-audio-worklet" -version = "0.0.0" -dependencies = [ - "console_error_panic_hook", - "console_log 1.1.0", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "wasm-bindgen" -version = "0.2.126" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "paste", - "rustversion", - "serde", - "serde_derive", - "serde_json", - "wasm-bindgen-futures", - "wasm-bindgen-macro", - "wasm-bindgen-shared", - "wasm-bindgen-test", - "wasm-bindgen-test-crate-a", - "wasm-bindgen-test-crate-b", -] - -[[package]] -name = "wasm-bindgen-benchmark" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-cli" -version = "0.2.126" -dependencies = [ - "anyhow", - "assert_cmd", - "clap", - "env_logger", - "log", - "native-tls", - "predicates", - "pretty_assertions", - "regex", - "rouille", - "rstest", - "serde", - "serde_derive", - "serde_json", - "shlex 1.3.0", - "tempfile", - "ureq", - "walrus", - "wasm-bindgen-cli-support", - "wasm-bindgen-test-shared", - "wasmparser 0.245.1", - "wasmprinter 0.245.1", -] - -[[package]] -name = "wasm-bindgen-cli-support" -version = "0.2.126" -dependencies = [ - "anyhow", - "base64 0.22.1", - "leb128", - "log", - "rstest", - "rustc-demangle", - "serde", - "serde_json", - "walrus", - "wasm-bindgen-shared", - "wasmparser 0.245.1", - "wasmprinter 0.243.0", - "wast 243.0.0", - "wat", -] - -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.76" -dependencies = [ - "futures-channel", - "futures-lite", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-test", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.126" -dependencies = [ - "js-sys", - "quote", - "trybuild", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-macro-support", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.126" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn 2.0.119", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-paint" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.126" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "wasm-bindgen-test" -version = "0.3.76" -dependencies = [ - "async-trait", - "cast", - "gg-alloc", - "js-sys", - "libm", - "minicov", - "nu-ansi-term", - "num-traits 0.2.19", - "oorandom", - "serde", - "serde_json", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", - "wasm-bindgen-test-shared", - "web-sys", -] - -[[package]] -name = "wasm-bindgen-test-crate-a" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-test-crate-b" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.76" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "tokio", - "trybuild", - "wasm-bindgen-test", -] - -[[package]] -name = "wasm-bindgen-test-shared" -version = "0.2.126" - -[[package]] -name = "wasm-bindgen-webidl" -version = "0.0.0" -dependencies = [ - "anyhow", - "clap", - "env_logger", - "heck", - "lazy_static", - "log", - "once_cell", - "proc-macro2", - "quote", - "sourcefile", - "syn 2.0.119", - "weedle", -] - -[[package]] -name = "wasm-encoder" -version = "0.243.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55db9c896d70bd9fa535ce83cd4e1f2ec3726b0edd2142079f594fc3be1cb35" -dependencies = [ - "leb128fmt", - "wasmparser 0.243.0", -] - -[[package]] -name = "wasm-encoder" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9dca005e69bf015e45577e415b9af8c67e8ee3c0e38b5b0add5aa92581ed5c" -dependencies = [ - "leb128fmt", - "wasmparser 0.245.1", -] - -[[package]] -name = "wasm-encoder" -version = "0.254.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09480d646178e5fdd12bb06e812d0af9a3a191dbc9cd697fdc86687beade7393" -dependencies = [ - "leb128fmt", - "wasmparser 0.254.0", -] - -[[package]] -name = "wasm-in-wasm" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "wasm-in-wasm-imports" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", -] - -[[package]] -name = "wasm-in-web-worker" -version = "0.0.0" -dependencies = [ - "console_error_panic_hook", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "wasmparser" -version = "0.243.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d8db401b0528ec316dfbe579e6ab4152d61739cfe076706d2009127970159d" -dependencies = [ - "bitflags 2.13.1", - "indexmap", - "semver", -] - -[[package]] -name = "wasmparser" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" -dependencies = [ - "bitflags 2.13.1", - "hashbrown 0.16.1", - "indexmap", - "semver", - "serde", -] - -[[package]] -name = "wasmparser" -version = "0.254.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27" -dependencies = [ - "bitflags 2.13.1", - "indexmap", - "semver", -] - -[[package]] -name = "wasmprinter" -version = "0.243.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2b6035559e146114c29a909a3232928ee488d6507a1504d8934e8607b36d7b" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.243.0", -] - -[[package]] -name = "wasmprinter" -version = "0.245.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f41517a3716fbb8ccf46daa9c1325f760fcbff5168e75c7392288e410b91ac8" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser 0.245.1", -] - -[[package]] -name = "wast" -version = "243.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df21d01c2d91e46cb7a221d79e58a2d210ea02020d57c092e79255cc2999ca7f" -dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width", - "wasm-encoder 0.243.0", -] - -[[package]] -name = "wast" -version = "254.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ed4dfc8f6b9fc38b231065e2cdfbf7359af5ab945990abf09658dcc63c3e32" -dependencies = [ - "bumpalo", - "leb128fmt", - "memchr", - "unicode-width", - "wasm-encoder 0.254.0", -] - -[[package]] -name = "wat" -version = "1.254.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7127f7f9b8f127c879991cecd35f494e4628bae1b0874c681414d8d8831e952c" -dependencies = [ - "wast 254.0.0", -] - -[[package]] -name = "wcodspeed" -version = "0.1.0" -dependencies = [ - "anyhow", - "serde", - "serde_json", - "statrs", -] - -[[package]] -name = "web-sys" -version = "0.3.103" -dependencies = [ - "futures", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test", -] - -[[package]] -name = "web-time" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webaudio" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "webgl" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "webidl-tests" -version = "0.0.0" -dependencies = [ - "env_logger", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test", - "wasm-bindgen-webidl", -] - -[[package]] -name = "webpki-root-certs" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b96554aa2acc8ccdb7e1c9a58a7a68dd5d13bccc69cd124cb09406db612a1c9b" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webpki-roots" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "webrtc_datachannel" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "websockets" -version = "0.0.0" -dependencies = [ - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "webxr" -version = "0.0.0" -dependencies = [ - "console_error_panic_hook", - "futures", - "js-sys", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", -] - -[[package]] -name = "weedle" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5352ce9558ad6aa2c4eca1b18329177f5f77ba71f54eac09709ef47701137eb" -dependencies = [ - "nom", -] - -[[package]] -name = "wide" -version = "0.7.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" -dependencies = [ - "bytemuck", - "safe_arch", -] - -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - -[[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "windows-link" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" - -[[package]] -name = "windows-registry" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" -dependencies = [ - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-sys" -version = "0.61.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "winnow" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" -dependencies = [ - "memchr", -] - -[[package]] -name = "without-a-bundler" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "without-a-bundler-no-modules" -version = "0.0.0" -dependencies = [ - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "writeable" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" - -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" - -[[package]] -name = "yoke" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" -dependencies = [ - "stable_deref_trait", - "yoke-derive", - "zerofrom", -] - -[[package]] -name = "yoke-derive" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zerocopy" -version = "0.8.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.8.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zerofrom" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" -dependencies = [ - "zerofrom-derive", -] - -[[package]] -name = "zerofrom-derive" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", - "synstructure", -] - -[[package]] -name = "zeroize" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" - -[[package]] -name = "zerotrie" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" -dependencies = [ - "displaydoc", - "yoke", - "zerofrom", -] - -[[package]] -name = "zerovec" -version = "0.11.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" -dependencies = [ - "yoke", - "zerofrom", - "zerovec-derive", -] - -[[package]] -name = "zerovec-derive" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.119", -] - -[[package]] -name = "zmij" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/worker/index.ts b/worker/index.ts index 142919f..e98fe11 100644 --- a/worker/index.ts +++ b/worker/index.ts @@ -1,10 +1,11 @@ import { exports } from 'cloudflare:workers'; +import { PORT } from './pumpkin'; import { forwardSocket } from './socket'; export { MinecraftWorld } from './world'; export default { async connect(socket: Socket, env: Env): Promise { - const target = exports.MinecraftWorld.getByName(env.WORLD_NAME).connect('world:25565', { allowHalfOpen: true }); + const target = exports.MinecraftWorld.getByName(env.WORLD_NAME).connect(`world:${PORT}`, { allowHalfOpen: true }); await forwardSocket(socket, target); }, async fetch(request: Request, env: Env): Promise { diff --git a/worker/modules.d.ts b/worker/modules.d.ts index 552bea7..3238267 100644 --- a/worker/modules.d.ts +++ b/worker/modules.d.ts @@ -11,10 +11,16 @@ declare module '*pumpkin-do.js' { printErr(message: string): void; } interface Pumpkin { - pumpkin_start(): Promise; - pumpkin_connect(socket: Socket): Promise; + pumpkin_run(ready: () => void): Promise; + pumpkin_stop(): void; pumpkin_status(): { players: number; ticks: number; wasm_memory_bytes: number }; - pumpkin_shutdown(): Promise; } export default function create(options: Options): Promise; } + +// Routes an inbound socket to the net.Server listening on its local address's +// port within the current Durable Object's port table; resolves when the +// connection closes. +declare module 'cloudflare:node' { + export function handleAsNodeConnection(socket: Socket): Promise; +} diff --git a/worker/pumpkin.ts b/worker/pumpkin.ts index c8b86fd..9928a22 100644 --- a/worker/pumpkin.ts +++ b/worker/pumpkin.ts @@ -1,8 +1,11 @@ +import { handleAsNodeConnection } from 'cloudflare:node'; import createPumpkin from '../target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js'; import pumpkinWasm from '../target/workers/wasm32-unknown-emscripten/release/pumpkin_do.wasm'; import { createWorldRuntime } from './filesystem'; -/** Bind the server API to its instance's filesystem and async context once. */ +export const PORT = 25565; + +/** Start the server in its instance's filesystem context and resolve once it is listening. */ export async function startPumpkin(storage: DurableObjectStorage) { const { instance, run } = await createWorldRuntime(storage, options => createPumpkin({ ...options, @@ -13,10 +16,18 @@ export async function startPumpkin(storage: DurableObjectStorage) { }, printErr: console.error, })); - await run(() => instance.pumpkin_start()); + // The single JSPI activation runs the whole server lifetime, suspending + // whenever Tokio parks. It settles after `stop` completes the final save, or + // rejects if the server fails. + let finished!: Promise; + await new Promise((ready, reject) => { + finished = run(() => instance.pumpkin_run(ready)); + finished.catch(reject); + }); return { - connect: (socket: Socket) => run(() => instance.pumpkin_connect(socket)), - stop: () => run(() => instance.pumpkin_shutdown()), - status: () => run(() => instance.pumpkin_status()), + finished, + connect: (socket: Socket) => handleAsNodeConnection(socket), + stop: () => { instance.pumpkin_stop(); return finished; }, + status: () => instance.pumpkin_status(), }; } diff --git a/worker/world.ts b/worker/world.ts index f457cfa..75d7bca 100644 --- a/worker/world.ts +++ b/worker/world.ts @@ -39,6 +39,7 @@ export class MinecraftWorld extends DurableObject { const pending = Promise.resolve().then(() => this.loadRuntime()).then(runtime => { this.startupMilliseconds = Date.now() - started; this.state = { phase: 'running', runtime }; + runtime.finished.catch(error => this.fail(error)); return runtime; }).catch(error => { this.fail(error); throw error; }); this.state = { phase: 'starting', pending }; From caaa63b41bf54607e1983fe748d13123eebff1f0 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 16 Sep 2026 02:37:14 -0700 Subject: [PATCH 3/6] Run the Durable Object on Tokio's EventLoopRuntime The whole Worker is now Rust linked directly by cargo and emcc, with no JSPI and no JS-side driver. The Durable Object owns an EventLoopRuntime (tokio-rs/tokio#8479): Tokio's current-thread scheduler and drivers wait on the host event loop through Emscripten's epoll listeners (emscripten-core/emscripten#27547). Exports return promises; the server lifetime is a scheduled root that completes by callback after the final save and checkpoint. Connections arriving while the server starts or stops wait for the next phase change and retry. worker/index.mjs re-exports the generated module and derives the object class from DurableObject for RPC. The worker and worker-build dependencies, the TypeScript worker, and the filesystem fixtures are removed. The Pumpkin patch makes the stop token replaceable behind a StopSignal and adds reset_stop so a server can start again in the same instance. Under NODERAWFS the host enforces permissions; src/workerd.js forces FS.ignorePermissions since workerd reports directories without execute bits, and keeps stdio on the console callbacks. Documentation describes the event-loop model and the workerd node:fs requirements (positional buffer I/O, O_TRUNC, O_CREAT, rename over an existing path). --- .cargo/config.toml | 17 +- AGENTS.md | 14 +- Cargo.lock | 25 +-- Cargo.toml | 10 +- README.md | 18 +- build.rs | 19 +- docs/architecture.md | 119 +++++------ docs/dependencies.md | 39 ++-- docs/development.md | 15 +- package-lock.json | 66 ------- package.json | 15 +- patches/README.md | 2 +- patches/pumpkin-emscripten.patch | 83 +++++++- scripts/setup.sh | 4 +- scripts/test.sh | 3 - src/host.rs | 67 +++++++ src/main.rs | 149 +++++++------- src/persist.rs | 93 +++++++++ src/workerd.js | 54 +++++ src/world.rs | 303 +++++++++++++++++++++++++++++ tests/fixtures/filesystem.rs | 87 --------- tests/fixtures/startup-failure.mjs | 14 -- tests/fixtures/worker.mjs | 64 ------ tests/fixtures/wrangler.jsonc | 14 -- tests/integration.mjs | 50 +---- tests/server.mjs | 6 +- tsconfig.json | 12 -- worker/filesystem.ts | 16 -- worker/fs-library.js | 114 ----------- worker/index.mjs | 16 ++ worker/index.ts | 18 -- worker/modules.d.ts | 26 --- worker/pumpkin.ts | 33 ---- worker/socket.ts | 25 --- worker/world.ts | 93 --------- wrangler.jsonc | 5 +- 36 files changed, 817 insertions(+), 891 deletions(-) create mode 100644 src/host.rs create mode 100644 src/persist.rs create mode 100644 src/workerd.js create mode 100644 src/world.rs delete mode 100644 tests/fixtures/filesystem.rs delete mode 100644 tests/fixtures/startup-failure.mjs delete mode 100644 tests/fixtures/worker.mjs delete mode 100644 tests/fixtures/wrangler.jsonc delete mode 100644 tsconfig.json delete mode 100644 worker/filesystem.ts delete mode 100644 worker/fs-library.js create mode 100644 worker/index.mjs delete mode 100644 worker/index.ts delete mode 100644 worker/modules.d.ts delete mode 100644 worker/pumpkin.ts delete mode 100644 worker/socket.ts delete mode 100644 worker/world.ts diff --git a/.cargo/config.toml b/.cargo/config.toml index ddb88b3..8afc927 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -5,18 +5,18 @@ target-dir = "target/workers" [target.wasm32-unknown-emscripten] rustflags = [ "-Crelocation-model=static", + "--cfg=tokio_unstable", # exnref exception handling throughout: the C side gets `-fwasm-exceptions - # -sWASM_LEGACY_EXCEPTIONS=0` from EMCC_CFLAGS (scripts/common.sh), emcc - # translates the prebuilt std at link, and wasm-bindgen's JSPI wrappers use - # try_table. V8 rejects a module mixing the two. + # -sWASM_LEGACY_EXCEPTIONS=0` from EMCC_CFLAGS (scripts/common.sh) and the + # prebuilt std is translated at link. V8 rejects a module mixing the two. "-Cllvm-args=-wasm-use-legacy-eh=false", + "-Clink-arg=-sBINARYEN_EXTRA_PASSES=--translate-to-exnref", # Link-only settings stay out of EMCC_CFLAGS so they do not reach C compiles. "-Clink-arg=-sWASM_BINDGEN", - "-Clink-arg=-sJSPI", - # Each promising activation runs on its own shadow stack, so a request or - # timer entering the module while the server is suspended cannot clobber it. - "-Clink-arg=-sREENTRANT_JSPI", + "-Clink-arg=-sMODULARIZE=instance", "-Clink-arg=-sEXPORT_ES6", + "-Clink-arg=-sAUTO_INIT", + "-Clink-arg=-sSOURCE_PHASE_IMPORTS", # Generated host glue uses Workers' Node compatibility APIs. "-Clink-arg=-sENVIRONMENT=node", "-Clink-arg=-sNODERAWSOCKETS", @@ -24,7 +24,4 @@ rustflags = [ "-Clink-arg=-sASSERTIONS=0", "-Clink-arg=-sSTACK_SIZE=8MB", "-Clink-arg=-sALLOW_MEMORY_GROWTH=1", - # Each Durable Object creates its own wasm instance and mounted filesystem. - "-Clink-arg=-sMODULARIZE=1", - "-Clink-arg=-sEXPORT_NAME=createPumpkinModule", ] diff --git a/AGENTS.md b/AGENTS.md index 1096947..5cbf3f8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,15 +5,16 @@ Read [README.md](README.md), [architecture](docs/architecture.md), and ## Project layout -- `src/`: Pumpkin entry points and configuration. -- `worker/`: TCP ingress, Durable Object lifecycle, and SQLite filesystem. -- `tests/`: protocol clients, integration tests, and isolated filesystem fixtures. +- `src/`: the Worker: TCP ingress, the Durable Object, world persistence, + Pumpkin configuration, and the Emscripten JS library (`workerd.js`). +- `worker/index.mjs`: the module Wrangler loads; re-exports the generated build. +- `tests/`: protocol clients and the integration test. - `scripts/setup.sh`: provision pinned sources and build the toolchain. - `scripts/{build,serve,test}.sh`: build, run, and validate the Workers server. Use Node 24+ (26 recommended) and the pinned Rust toolchain. Run `npm test` after build/runtime changes; it must print `PUMPKIN-DO-SQLITE-RESTART-OK` after verifying -player and chunk restoration. Keep test fixtures out of the production bundle. +player and chunk restoration. Scripts bind to loopback and fail if their ports are occupied. Do not kill another process to free a port. @@ -21,8 +22,9 @@ process to free a port. Changes to the patched Pumpkin checkout must be reflected in `patches/`. Keep unpatched checkouts unmodified; update pins for upstream changes. Preserve the unified ticker and cooperative scheduler unless -the task requires a runtime change. Do not reintroduce old Tokio/libc networking -patches. +the task requires a runtime change. Keep the `EventLoopRuntime` model: exports +return promises and nothing blocks or suspends; do not reintroduce JSPI, old +Tokio/libc networking patches, or a JS-side driver. Worlds under `.data/` are user data; do not remove or copy them into commits. Playable databases live in `.data/workers/server/`; tests use `.data/probes/`. diff --git a/Cargo.lock b/Cargo.lock index da0cad1..7cc9763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "async-compression" -version = "0.4.46" +version = "0.4.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f10dafd0c8d2e51ae9a748805777613ed0bbe17bf586b76c8311f45c020a32f" +checksum = "ef217a77a86a6e3dab9a5b3c81dc445b603fe743a90c1cb10a2f2144628d8cfa" dependencies = [ "compression-codecs", "compression-core", @@ -164,9 +164,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.5" +version = "1.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" dependencies = [ "find-msvc-tools", "shlex", @@ -250,9 +250,9 @@ dependencies = [ [[package]] name = "compression-codecs" -version = "0.4.41" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a6d0db8759036a783bc7c3f7a07f8cef3bf9470eb1db3bc86e8bcd1c5d0fe8" +checksum = "257c7085cbb71be72d8fb97edff08b03d86d5d9f2222b9cc34a6bec87093bc10" dependencies = [ "compression-core", "flate2", @@ -313,9 +313,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.5.1" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8498c871161e1742aaa9d52551b2d6ebdd4c3d45a3be423e3728f33b955be550" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" dependencies = [ "cfg-if", ] @@ -1812,6 +1812,7 @@ dependencies = [ "rayon", "tokio", "wasm-bindgen", + "web-sys", ] [[package]] @@ -2036,9 +2037,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.44" +version = "0.23.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6725596c3f2c3a0aef021139e145d4eafe314a6623e4680ca83852b2c67ab2ba" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" dependencies = [ "log", "once_cell", @@ -2352,9 +2353,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.16.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" [[package]] name = "socket2" diff --git a/Cargo.toml b/Cargo.toml index 1e7f0ee..3c53cd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,14 +12,6 @@ autobins = false exclude = [".work"] resolver = "2" -[features] -test-fixtures = [] - -[[bin]] -name = "filesystem-probe" -path = "tests/fixtures/filesystem.rs" -required-features = ["test-fixtures"] - [[bin]] name = "pumpkin-do" path = "src/main.rs" @@ -27,6 +19,7 @@ path = "src/main.rs" [dependencies] wasm-bindgen = "0.2.128" js-sys = "0.3.105" +web-sys = { version = "0.3.105", features = ["AbortController", "AbortSignal", "Headers", "ReadableStream", "Request", "Response", "ResponseInit", "StreamPipeOptions", "Url", "WritableStream", "console"] } tokio = { version = "1", default-features = false, features = ["rt", "macros", "sync", "time", "net", "io-util"] } pumpkin = { path = ".work/pumpkin/crates/pumpkin", default-features = false, features = ["single-threaded"] } pumpkin-config = { path = ".work/pumpkin/crates/pumpkin-config", default-features = false } @@ -34,6 +27,7 @@ rayon = "1.12" # wasm32-unknown-emscripten support pending upstream releases. [patch.crates-io] +# EventLoopRuntime: the host event loop drives Tokio (tokio-rs/tokio#8479). tokio = { path = ".work/tokio/tokio" } tokio-macros = { path = ".work/tokio/tokio-macros" } # tokio-rs/mio#1969; its emscripten epoll bindings come from libc's unreleased libc-0.2 branch. diff --git a/README.md b/README.md index 3a03ec5..f64bae2 100644 --- a/README.md +++ b/README.md @@ -25,13 +25,13 @@ npm run dev Setup installs the pinned Rust toolchain, fetches dependency sources and the Emscripten toolchain under `.work/` (building Binaryen), and installs the -wasm-bindgen CLI. `npm run -dev` compiles Pumpkin and starts Wrangler. The first build takes several -minutes; later builds use Cargo's cache. +wasm-bindgen CLI. `npm run dev` compiles Pumpkin and starts Wrangler. The first +build takes several minutes; later builds use Cargo's cache. Until Wrangler's bundled workerd routes inbound sockets to `net.Server` listeners -inside Durable Objects, point Miniflare at a workerd build that does: -`MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. +inside Durable Objects and carries the `node:fs` fixes listed in +[dependency pins](docs/dependencies.md#host-tools), point Miniflare at a workerd +build that does: `MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. Connect **Minecraft Java 26.2** to **`localhost:25565`**. Status is available at **http://localhost:8787/**. @@ -48,10 +48,9 @@ Wrangler; changes held in memory can be lost if the process is terminated early. ## Build and contribute ```sh -npm run build # Compile the server without starting Wrangler +npm run build # Compile the Worker without starting Wrangler npm test # Two-player gameplay, a persistent block edit, and restart npm run test:scheduler -npm run typecheck ``` The tests use separate data under `.data/probes/`. They verify that two clients @@ -77,8 +76,9 @@ Provision the public TCP endpoint separately and route it to this Worker's ## Credits and license Built on [Pumpkin](https://github.com/Pumpkin-MC/Pumpkin), -[Guy Bedford's Rust/Emscripten work](https://github.com/guybedford), and -[worker-fs-mount](https://github.com/danlapid/worker-fs-mount). +[Tokio](https://github.com/tokio-rs/tokio), [Emscripten](https://emscripten.org/), +[wasm-bindgen](https://github.com/wasm-bindgen/wasm-bindgen), and +[Guy Bedford's Rust/Emscripten work](https://github.com/guybedford). Licensed under [GPL-3.0-only](LICENSE), consistent with Pumpkin. The [original MIT notice](LICENSES/rust-workers-minecraft-MIT.txt) is retained for diff --git a/build.rs b/build.rs index 413100c..10f8c3d 100644 --- a/build.rs +++ b/build.rs @@ -1,17 +1,18 @@ use std::{env, path::PathBuf}; fn main() { - println!("cargo::rerun-if-changed=worker/fs-library.js"); + println!("cargo::rerun-if-changed=src/workerd.js"); if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("emscripten") { return; } - // Keep growth headroom bounded after generation's temporary allocations. - println!("cargo::rustc-link-arg-bins=-sMEMORY_GROWTH_LINEAR_STEP=2097152"); - let library = - PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("worker/fs-library.js"); - println!( - "cargo::rustc-link-arg-bins=-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\"$workerFilesystem\"]" - ); - println!("cargo::rustc-link-arg-bins=--js-library"); + for arg in [ + // Keep growth headroom bounded after generation's temporary allocations. + "-sMEMORY_GROWTH_LINEAR_STEP=2097152", + "-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\"$workerdStdio\"]", + "--js-library", + ] { + println!("cargo::rustc-link-arg-bins={arg}"); + } + let library = PathBuf::from(env::var_os("CARGO_MANIFEST_DIR").unwrap()).join("src/workerd.js"); println!("cargo::rustc-link-arg-bins={}", library.display()); } diff --git a/docs/architecture.md b/docs/architecture.md index 1427a9b..d43e7d6 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,32 +2,44 @@ ## Runtime and connections -The Worker forwards TCP streams to a named Durable Object using the platform's -stream-piping API. The object lazily creates an Emscripten module and calls its -single `pumpkin_run` export. That export is a `#[wasm_bindgen(jspi)]` function: it -builds a current-thread Tokio runtime and `block_on`s the whole server lifetime, -and every park (`epoll_wait`, timers) suspends the Wasm stack on the host event -loop. There is no host-driven scheduling and no Rust promise adapter; the run -promise settles only when the server has stopped and saved. `-sREENTRANT_JSPI` -gives each promising activation its own shadow stack, so status calls and -connection routing enter the module while the server is suspended without -touching its frames. +The whole Worker is Rust. Cargo links the `pumpkin-do` bin for +`wasm32-unknown-emscripten` with emcc, which emits an ES module instance +(`-sMODULARIZE=instance`, `-sWASM_BINDGEN`) that `worker/index.mjs` re-exports +for Wrangler. The entrypoint's `connect` handler forwards each TCP connection to +the named `MinecraftWorld` object with `stub.connect` and pipes both directions; +`fetch` serves status. Both are plain exports composing the platform's promises. + +`MinecraftWorld` is a `#[wasm_bindgen]` class constructed with the object's state +and owning Tokio's `EventLoopRuntime` (tokio-rs/tokio#8479): the current-thread +scheduler and drivers with the host event loop as their wait. Nothing blocks or +suspends. Work is scheduled as roots that complete by callback, and each export +wraps its root in a Promise. The first `connect` after idle schedules the server +lifetime as a root: it restores the world, starts Pumpkin, awaits the server's +return after its final save, checkpoints, and settles the connection's promise. +Later connections and `status` calls enter the same instance as ordinary calls +while that root is parked. A connection arriving while the server is starting +or stopping waits for the next phase change and then retries, so a client that +connects as the previous server checkpoints starts the next one. The runtime's epoll and timer waits register with the +host through Emscripten's Node backend (emscripten-core/emscripten#27547), so a +readiness or timer callback resumes the scheduler on the host loop. Pumpkin binds its stock `TcpListener` on port 25565. Emscripten's Node socket backend implements that with `net.BoundSocket`/`net.Server`, which workerd scopes -to the Durable Object's own port table, so every object binds the same port. The -object's `connect` handler routes each inbound socket to that listener with -`handleAsNodeConnection`; the accepted connection then surfaces through epoll -readiness and Pumpkin's normal accept loop. Accepted sockets report the bound +to the Durable Object's own port table. `connect` routes each inbound socket to +that listener with `handleAsNodeConnection`; the accepted connection surfaces +through epoll readiness and Pumpkin's normal accept loop, reporting the bound address and an unspecified peer. -Each object owns separate wasm memory, Rust statics, filesystem descriptors, and -its Tokio runtime. Pumpkin's `single-threaded` feature selects cooperative inline -chunk generation and the async ticker. The same scheduler loop serves native -builds, which dispatch generation onto Rayon. Save batches await queue capacity; -shutdown drains results with an elapsed-time timeout before the final flush. -Bounded CPU tasks use regular Tokio tasks because `spawn_blocking` requires OS -threads. JSPI supplies stack suspension, not threads. +One wasm instance serves the isolate, so one object hosts one server at a time; +`WORLD_NAME` selects it. When the last connection closes, the server root calls +`stop_server`, Pumpkin saves and returns, and `reset_stop` clears the process-wide +stop state so the next connection can start a fresh server in the same instance. + +Pumpkin's `single-threaded` feature selects cooperative inline chunk generation +and the async ticker. The same scheduler loop serves native builds, which +dispatch generation onto Rayon. Save batches await queue capacity; shutdown +drains results with an elapsed-time timeout before the final flush. Bounded CPU +tasks use regular Tokio tasks because `spawn_blocking` requires OS threads. Structure templates store palette indices and share their block arrays between the placement and query APIs. Block-entity NBT is shared until a placement needs @@ -37,41 +49,19 @@ neighborhood compact without changing its dependency rules. Emscripten grows memory in 2 MiB increments, configured by `build.rs`, while retaining the 8 MiB stack. See [memory measurements](memory-reduction.md). -Emscripten factory mode (`MODULARIZE=1`) isolates module instances. Wrangler -provides the compiled wasm through `instantiateWasm`; the generated host glue uses -Workers' Node compatibility APIs. The SDK's standalone-loader initialization and -recovery hook is disabled for this embedding. - ## Filesystem and checkpoints -The host mounts `LocalDOFilesystem(ctx.storage)` from durable-object-fs at `/data`. -Emscripten's `NODERAWFS` routes Rust filesystem calls through worker-fs-mount's -synchronous Node API, including descriptors, positional I/O, rename, and sync. -World metadata, chunks, entities, players, and configuration use this mount. -Logging goes to the host console. - -Each wasm instance retains its own mount context across requests and Tokio -callbacks using `AsyncLocalStorage.snapshot()`. Descriptors opened at startup -therefore remain valid until that runtime shuts down, without exposing one world's -mount to another object. - -The Emscripten compatibility library in `worker/fs-library.js` supplies the mounted Node -filesystem to the factory: the generated loader's runtime `createRequire` calls -cannot use Wrangler's build-time aliases. It also initializes file flags from -public `fs.constants` and implements synchronous `fd_sync`. Paths resolve against -the wasm instance's working directory (initially `/data`), without changing the -isolate-wide Node process directory. - -The upstream `entries` table stores metadata, and `file_pages` stores contents in -64 KiB rows. Descriptor writes update only affected pages. Truncation supports -sparse files, and file writes and native rename use SQLite transactions. Whole-file -reads still require a buffer large enough for the result. - -After the final connection leaves, the object requests a stop; Pumpkin saves, -`pumpkin_run` resolves, and the object awaits `storage.sync()`. New connections wait for this checkpoint, then create a -fresh runtime from the stored files. Checkpoint failures remain visible in status. -This preserves issued writes; terminating a server with active clients can still -lose changes in Pumpkin's in-memory caches. +Pumpkin runs against workerd's own Node filesystem through Emscripten's +`NODERAWFS`, with the world under `/tmp/world`. That tree lives for the Durable +Object's lifetime. Between runs it lives in the object's SQLite storage, one row +per file: the server root restores the tree before starting Pumpkin and, after +the server's final save, writes every changed file back, deletes rows for removed +files, and awaits `storage.sync()` before the last connection is reported closed. +Status shows the checkpoint time. Terminating a server with active clients can +lose changes since the previous checkpoint. + +Logging goes to the host console: `src/workerd.js` keeps Emscripten's stdio +descriptors on its console callbacks, since workerd exposes no process stdio. ## Development interfaces @@ -85,28 +75,17 @@ Runtime statistics include Wasm capacity and allocator in-use/free/arena bytes. Allocator counters include allocation metadata and unused container capacity; Wasm capacity additionally includes static data, stack, and growth headroom. -The public status path reads no filesystem internals and remains available when -startup fails. Checkpointing is internal to the connection lifecycle. The -`worker/pumpkin.ts` facade binds exports to the module context once, so the -Durable Object deals with `connect`, `stop`, and `status` methods. - Local databases live in `.data/workers/server/`. Integration tests use separate -storage under `.data/probes/` and restart Wrangler to verify restoration. The echo -and large-file test fixture has its own Worker configuration and wasm binary; it -is excluded from the application bundle. - -Rust panics, Rust error logs, and runtime crashes fail the integration test. +storage under `.data/probes/` and restart Wrangler to verify restoration. Rust +panics, Rust error logs, and runtime crashes fail the integration test. ## Build constraints - Use the pinned Rust toolchain, Emscripten, and wasm-bindgen CLI from setup. - Keep static relocation, exnref exception handling on both the C and Rust - sides, the 8 MiB stack, and memory growth. -- Pass Emscripten link settings through rustc so they do not affect C compilation. -- Keep the compatibility initializer linked even when Rust does not call `fd_sync`; - `build.rs` configures this and tracks the library as a build input. + sides, the 8 MiB stack, and memory growth. `.cargo/config.toml` holds the link + settings; `build.rs` adds the JS library and growth step. +- Build with `--cfg tokio_unstable`; `EventLoopRuntime` is unstable API. - Prefix Rust exports to avoid collisions with libc and Emscripten symbols. -- The synchronous SQLite mount supplies `fd_sync`; checkpoint completion separately - awaits Durable Object storage synchronization. [Dependency pins and patch maintenance](dependencies.md) diff --git a/docs/dependencies.md b/docs/dependencies.md index a2d9ed5..95fe69f 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -8,10 +8,10 @@ locked. | Component | Source / branch | Commit | Local changes or purpose | | --- | --- | --- | --- | -| pumpkin | [master](https://github.com/Pumpkin-MC/Pumpkin) | `b5b9b9d7010e793806a83c495af223c67e1d35ee` | Headless embedding, shared async scheduler, compact templates/generation chunks, and build-script fixes | -| tokio | [emscripten-epoll](https://github.com/guybedford/tokio) | `8d0a2a845c546e93a4cf0e8ff2c21ac50a3d1931` | JSPI parking and `net` over epoll on Emscripten (tokio-rs/tokio#8281 follow-ons); unmodified | -| emscripten | [cf-final](https://github.com/guybedford/emscripten) | `a5013dd597ec9d48856370f5707d438937a7b4e8` | Upstream main plus emscripten-core/emscripten#27698, #27699 (`REENTRANT_JSPI`), #27547, and two fixes pending for #27699; unmodified | -| binaryen | [jspi-hooks](https://github.com/guybedford/binaryen) | `d6483a04d7dab0ef83e5c43f87343061d97a3b8d` | WebAssembly/binaryen#9102, the `--jspi-hooks` pass; built by setup | +| pumpkin | [master](https://github.com/Pumpkin-MC/Pumpkin) | `b5b9b9d7010e793806a83c495af223c67e1d35ee` | Headless embedding, shared async scheduler, restartable stop signal, compact templates/generation chunks, and build-script fixes | +| tokio | [emscripten-event-loop](https://github.com/guybedford/tokio) | `5315798b6a62a47d03dc40e7d04fbf83c80f3246` | `EventLoopRuntime` (tokio-rs/tokio#8479) and `net` over epoll on Emscripten; unmodified | +| emscripten | [cf-final](https://github.com/guybedford/emscripten) | `4e034c65a18c0034c68e7eb628477fe4428fa1f9` | Upstream main plus emscripten-core/emscripten#27547 (epoll listeners on the host loop), #27698, #27699, and pending fixes: side-module accept, exnref ordering, `fs.constants` in NODEFS; unmodified | +| binaryen | [jspi-hooks](https://github.com/guybedford/binaryen) | `d6483a04d7dab0ef83e5c43f87343061d97a3b8d` | The frontend pin expects this Binaryen; built by setup | | emsdk | [main](https://github.com/emscripten-core/emsdk) | `5eb0bde7585670252e8ba05e9d361627bffd08b5` | LLVM from emscripten-releases build `e8579ea489b44a6792f5abf95377a6ee38a16cce`, paired with the frontend's main | Patches are relative to the pinned commits above. Setup checks reverse application @@ -31,7 +31,9 @@ commit. | ring | https://github.com/guybedford/ring branch `emscripten` | getrandom-backed `SystemRandom` on Emscripten | The root Cargo.toml applies these overrides and the local checkouts. Cargo.lock -pins the full application graph, including the branch commits. +pins the full application graph, including the branch commits. The Worker uses +`wasm-bindgen`, `js-sys`, and `web-sys` from crates.io directly; there is no +`worker` crate dependency. ## Host tools @@ -40,18 +42,19 @@ pins the full application graph, including the branch commits. larger compile-thread stack for pumpkin-data's generated tables; the scripts set `RUST_MIN_STACK`. - wasm-bindgen CLI: the release matching the `wasm-bindgen` crate version in - Cargo.toml, installed by setup into `.work/bin/` (or `WASM_BINDGEN_BIN`). emcc - runs it as a post-link step under `-sWASM_BINDGEN`. + Cargo.toml, installed by setup into `.work/bin/`. emcc runs it as a post-link + step under `-sWASM_BINDGEN`. - Node: 24+; 26 recommended and selected in CI. Used for build tools and tests. - Python: 3.11+ (Emscripten scripts and TOML parsing). - Emscripten backend: LLVM through emsdk under `.work/emsdk` (or an activated - emsdk selected with `EMSDK`), and Binaryen built from the checkout above with - CMake and Ninja. Setup writes the frontend's machine-local `.emscripten_cf` - pointing at both. Homebrew's release cannot be used while the frontend is - ahead of the tagged releases. -- workerd: Wrangler must run a workerd with per-Durable-Object port tables and - `net.Server` inbound routing (`handleAsNodeConnection`). Until that ships in - Wrangler's bundled version, set `MINIFLARE_WORKERD_PATH`. + emsdk selected with `EMSDK` during setup), and Binaryen built from the checkout + above with CMake and Ninja. Setup writes the frontend's `.emscripten_cf` config + pointing at both, and `scripts/common.sh` selects it with `EM_CONFIG`. +- workerd: Wrangler must run a workerd with per-Durable-Object port tables, + `net.Server` inbound routing (`handleAsNodeConnection`), and the `node:fs` + fixes for positional buffer I/O, `O_TRUNC`, `O_CREAT`, and rename over an + existing path. Until those ship in Wrangler's bundled version, set + `MINIFLARE_WORKERD_PATH`. ## Updating a patch @@ -91,11 +94,9 @@ bash scripts/setup.sh --sources-only ## JavaScript dependencies -`package-lock.json` pins Wrangler 4.129.0, worker-fs-mount 0.2.0, -durable-object-fs 1.0.0, TypeScript, and their dependencies. The npm override for -durable-object-fs replaces its published `workspace:*` peer dependency with the -selected worker-fs-mount version. Generated runtime/binding types are recreated by -`npm run types` and are not committed. +`package-lock.json` pins Wrangler 4.129.0. The only application JavaScript is +`worker/index.mjs`, which re-exports the generated module and derives the +Durable Object class from `DurableObject` for RPC. After moving a checkout with cached build output, run `cargo clean` before rebuilding. Generated data can contain absolute paths. This leaves databases under `.data/` intact. diff --git a/docs/development.md b/docs/development.md index f05481d..0bcd4c8 100644 --- a/docs/development.md +++ b/docs/development.md @@ -4,12 +4,13 @@ Follow the [README setup instructions](../README.md#try-it) to install the pinne Rust toolchain, provision dependency sources and Emscripten, and install the wasm-bindgen CLI. The initial Pumpkin compilation takes several minutes. Later builds use Cargo's -cache. `build.rs` owns the Emscripten library arguments and tracks changes to the -compatibility library. +cache. `.cargo/config.toml` owns the link settings; `build.rs` adds the JS library +(`src/workerd.js`) and tracks changes to it. The build output Wrangler serves is +`target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js`, re-exported by +`worker/index.mjs`. ```sh -npm run build # Compile the production server -npm run typecheck # Regenerate Worker types and check TypeScript +npm run build # Compile the production Worker npm test # Full Wrangler integration suite npm run test:scheduler # Native queue/backpressure and shutdown regressions npm run test:memory # Two-player memory probe on a fresh, fixed-seed world @@ -19,9 +20,9 @@ Tests fail if their loopback ports (25565 and 8787) are occupied. They start and stop only their own Wrangler process groups. Their databases and logs live in `.data/probes/`; playable worlds are separate under `.data/workers/server/`. -`npm test` must finish with `PUMPKIN-DO-SQLITE-RESTART-OK`. It checks filesystem -isolation, synchronous descriptors, large files, failed-startup status, two-player -gameplay, a real block edit, and player/block restoration after a restart. +`npm test` must finish with `PUMPKIN-DO-SQLITE-RESTART-OK`. It checks two-player +gameplay, a real block edit, and player/block restoration after a restart, which +exercises the tree-to-SQLite checkpoint and restore. The memory probe waits for both clients to receive 81 chunks, observes 30 seconds of stationary play, then checkpoints. Each run creates a fresh isolated world diff --git a/package-lock.json b/package-lock.json index 5c4e381..18ad428 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,14 +5,7 @@ "packages": { "": { "name": "rust-workers-minecraft", - "dependencies": { - "durable-object-fs": "1.0.0", - "worker-fs-mount": "0.2.0" - }, "devDependencies": { - "@cloudflare/workers-types": "5.20260905.1", - "@types/node": "26.4.1", - "typescript": "~5.9.3", "wrangler": "4.129.0" }, "engines": { @@ -130,13 +123,6 @@ "node": ">=16" } }, - "node_modules/@cloudflare/workers-types": { - "version": "5.20260905.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260905.1.tgz", - "integrity": "sha512-ebDpVTgrnee245IRBTazyfjfmZas+HJehZyVQkKmUozsBN8RzvXXMI0XjjcT2rQjw+0USewfEGGLYiHO+TL0Sg==", - "dev": true, - "license": "MIT OR Apache-2.0" - }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -1255,16 +1241,6 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/@types/node": { - "version": "26.4.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-26.4.1.tgz", - "integrity": "sha512-k97ENvZWtvA6yqz5/FS6a7duDgOPEeOQOc2iKS/nY6mX6qJUKtLnWzQS+Xj6tXweyj6ZcTAK2Qecetnvi9nCLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~8.3.0" - } - }, "node_modules/blake3-wasm": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", @@ -1296,18 +1272,6 @@ "node": ">=8" } }, - "node_modules/durable-object-fs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/durable-object-fs/-/durable-object-fs-1.0.0.tgz", - "integrity": "sha512-r4XOu7vlnC3kg8HiFYO3sQweO3eUJLa7q1tZHUy6Oj1ifHR8So4W5oljz4YUD2sIzWuWgEt+ljirqPVj/WhbwA==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "worker-fs-mount": "workspace:*" - } - }, "node_modules/error-stack-parser-es": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", @@ -1496,20 +1460,6 @@ "license": "0BSD", "optional": true }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/undici": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", @@ -1520,13 +1470,6 @@ "node": ">=20.18.1" } }, - "node_modules/undici-types": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", - "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", - "dev": true, - "license": "MIT" - }, "node_modules/unenv": { "version": "2.0.0-rc.24", "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", @@ -1537,15 +1480,6 @@ "pathe": "^2.0.3" } }, - "node_modules/worker-fs-mount": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/worker-fs-mount/-/worker-fs-mount-0.2.0.tgz", - "integrity": "sha512-SHnIcUkBIyojQNtMdpIBDM0/JDD58xgTMcyJMTamU2z9WbG8cuoAWs5SBzyBfCcp+xPMXu8h+Ou3yqC9kUxFdw==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/workerd": { "version": "1.20260903.1", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260903.1.tgz", diff --git a/package.json b/package.json index eac72cc..c6e507e 100644 --- a/package.json +++ b/package.json @@ -5,28 +5,15 @@ "engines": { "node": ">=24" }, - "dependencies": { - "durable-object-fs": "1.0.0", - "worker-fs-mount": "0.2.0" - }, + "dependencies": {}, "devDependencies": { - "@cloudflare/workers-types": "5.20260905.1", - "@types/node": "26.4.1", - "typescript": "~5.9.3", "wrangler": "4.129.0" }, - "overrides": { - "durable-object-fs": { - "worker-fs-mount": "$worker-fs-mount" - } - }, "scripts": { "build": "bash scripts/build.sh", "dev": "bash scripts/serve.sh", "test": "bash scripts/test.sh", "test:memory": "npm run build && node tests/memory.mjs", - "types": "wrangler types worker/env.d.ts --strict-vars=false", - "typecheck": "npm run types && tsc --noEmit", "test:scheduler": "bash scripts/test-scheduler.sh" } } diff --git a/patches/README.md b/patches/README.md index 882110c..5d6f0da 100644 --- a/patches/README.md +++ b/patches/README.md @@ -9,7 +9,7 @@ diff below it. Preserve the description when regenerating the diff. | Patch | Checkout | Purpose | | --- | --- | --- | -| `pumpkin-emscripten.patch` | `.work/pumpkin` | Headless optional subsystems, single-threaded runtime and blocking-task helper, shared async chunk scheduler with backpressure and timed draining, unified ticker, and platform guards | +| `pumpkin-emscripten.patch` | `.work/pumpkin` | Headless optional subsystems, single-threaded runtime and blocking-task helper, shared async chunk scheduler with backpressure and timed draining, unified ticker, restartable stop signal, and platform guards | | `pumpkin-memory.patch` | `.work/pumpkin` | Shared indexed structure templates, immutable block-entity NBT, paletted generation chunks, and a temporary dense noise buffer | Setup applies `pumpkin-emscripten.patch` before `pumpkin-memory.patch`. The two patches use the diff --git a/patches/pumpkin-emscripten.patch b/patches/pumpkin-emscripten.patch index fc4bfd6..b516429 100644 --- a/patches/pumpkin-emscripten.patch +++ b/patches/pumpkin-emscripten.patch @@ -12,6 +12,10 @@ and track cooperative tasks through shutdown. Drop the Tokio features that need OS threads and signals from single-threaded builds and guard unsupported Emscripten system calls and synchronous HTTP lookups. +Make the process-wide stop token replaceable behind a StopSignal wrapper +and add reset_stop, so an embedding can start another server in the same +process after one has stopped. + Reserve save-queue capacity before transferring chunks or marking writes pending, and widen pending-write counters to usize. Drain late generation results with an elapsed-time timeout before the final save. Regression @@ -1276,7 +1280,7 @@ index a19825bc..45d6783e 100644 || { self.chunk_sender diff --git a/crates/pumpkin/src/lib.rs b/crates/pumpkin/src/lib.rs -index 7110bc9e..060225ea 100644 +index 7110bc9e..f45cadbe 100644 --- a/crates/pumpkin/src/lib.rs +++ b/crates/pumpkin/src/lib.rs @@ -8,12 +8,14 @@ extern crate pumpkin_macros; @@ -1340,7 +1344,66 @@ index 7110bc9e..060225ea 100644 }); assert!( -@@ -251,7 +263,7 @@ impl PumpkinServer { +@@ -204,16 +216,57 @@ pub fn init_logger(advanced_config: &AdvancedConfiguration) { + } + + pub static SHOULD_STOP: AtomicBool = AtomicBool::new(false); +-pub static STOP_INTERRUPT: LazyLock = LazyLock::new(CancellationToken::new); ++pub static STOP_INTERRUPT: StopSignal = StopSignal(std::sync::RwLock::new(None)); + pub static SERVER_IS_STOPPING: AtomicBool = AtomicBool::new(false); + pub static CRASH_REPORT: OnceLock = OnceLock::new(); + pub static SERVER_EXIT_CODE: AtomicI32 = AtomicI32::new(0); + ++/// The process-wide stop token, replaceable so an embedding can start another ++/// server in the same process after one has stopped. ++pub struct StopSignal(std::sync::RwLock>); ++ ++impl StopSignal { ++ fn token(&self) -> CancellationToken { ++ if let Some(token) = self.0.read().unwrap_or_else(|e| e.into_inner()).as_ref() { ++ return token.clone(); ++ } ++ self.0 ++ .write() ++ .unwrap_or_else(|e| e.into_inner()) ++ .get_or_insert_with(CancellationToken::new) ++ .clone() ++ } ++ ++ pub fn cancel(&self) { ++ self.token().cancel(); ++ } ++ ++ pub fn is_cancelled(&self) -> bool { ++ self.token().is_cancelled() ++ } ++ ++ pub fn cancelled(&self) -> tokio_util::sync::WaitForCancellationFutureOwned { ++ self.token().cancelled_owned() ++ } ++ ++ #[allow(clippy::should_implement_trait)] ++ pub fn clone(&self) -> CancellationToken { ++ self.token() ++ } ++} ++ + pub fn stop_server() { + SHOULD_STOP.store(true, Ordering::Relaxed); + STOP_INTERRUPT.cancel(); + } + ++/// Clears the stop state so `PumpkinServer::start` can run again in this process. ++pub fn reset_stop() { ++ SHOULD_STOP.store(false, Ordering::Relaxed); ++ SERVER_IS_STOPPING.store(false, Ordering::Release); ++ *STOP_INTERRUPT.0.write().unwrap_or_else(|e| e.into_inner()) = Some(CancellationToken::new()); ++} ++ + pub fn stop_or_exit_server() { + if SERVER_IS_STOPPING.load(Ordering::Acquire) { + // Server is already stopping, so we forcefully exit. +@@ -251,7 +304,7 @@ impl PumpkinServer { ) -> Self { let server = Server::new(basic_config, advanced_config, vanilla_data).await; @@ -1349,7 +1412,7 @@ index 7110bc9e..060225ea 100644 adjust_file_descriptor_limit(); let rcon = server.advanced_config.networking.rcon.clone(); -@@ -320,19 +332,30 @@ impl PumpkinServer { +@@ -320,19 +373,30 @@ impl PumpkinServer { None }; @@ -1382,7 +1445,7 @@ index 7110bc9e..060225ea 100644 let (bedrock_status, ice_socket) = Self::bind_bedrock_status(&server).await; let nethernet_listener = Self::bind_nethernet(&server, ice_socket).await; -@@ -345,6 +368,15 @@ impl PumpkinServer { +@@ -345,6 +409,15 @@ impl PumpkinServer { } } @@ -1398,7 +1461,7 @@ index 7110bc9e..060225ea 100644 async fn bind_nethernet( server: &Arc, ice_socket: Option, -@@ -441,6 +473,7 @@ impl PumpkinServer { +@@ -441,6 +514,7 @@ impl PumpkinServer { } pub async fn start(&self) { @@ -1406,7 +1469,7 @@ index 7110bc9e..060225ea 100644 if self.server.advanced_config.commands.use_console && let Some((wrapper, _, _)) = LOGGER_IMPL.wait() { -@@ -527,6 +560,7 @@ impl PumpkinServer { +@@ -527,6 +601,7 @@ impl PumpkinServer { info!("Completed save!"); @@ -1414,7 +1477,7 @@ index 7110bc9e..060225ea 100644 if let Some((wrapper, _, _)) = LOGGER_IMPL.wait() && let Some(rl) = wrapper.take_readline() { -@@ -618,7 +652,7 @@ impl PumpkinServer { +@@ -618,7 +693,7 @@ impl PumpkinServer { }); } Err(e) => { @@ -1423,7 +1486,7 @@ index 7110bc9e..060225ea 100644 if e.raw_os_error() == Some(libc::EMFILE) { error!( "Too many open files! Server reached file descriptor limit. \ -@@ -719,6 +753,7 @@ impl PumpkinServer { +@@ -719,6 +794,7 @@ impl PumpkinServer { } } @@ -1431,7 +1494,7 @@ index 7110bc9e..060225ea 100644 fn setup_stdin_console(server: &Arc) { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let rt = tokio::runtime::Handle::current(); -@@ -759,6 +794,7 @@ fn setup_stdin_console(server: &Arc) { +@@ -759,6 +835,7 @@ fn setup_stdin_console(server: &Arc) { }); } @@ -1439,7 +1502,7 @@ index 7110bc9e..060225ea 100644 fn setup_console(mut rl: Editor, server: Arc) { let (tx, mut rx) = tokio::sync::mpsc::channel(1); let (tx_reply, mut rx_reply) = tokio::sync::mpsc::channel(1); -@@ -839,7 +875,7 @@ fn scrub_address(ip: &str) -> String { +@@ -839,7 +916,7 @@ fn scrub_address(ip: &str) -> String { .collect() } diff --git a/scripts/setup.sh b/scripts/setup.sh index 6faeb84..42a158a 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -44,7 +44,7 @@ apply_patch() { echo "==> Pinned dependencies" checkout pumpkin https://github.com/Pumpkin-MC/Pumpkin master b5b9b9d7010e793806a83c495af223c67e1d35ee -checkout tokio https://github.com/guybedford/tokio emscripten-epoll 8d0a2a845c546e93a4cf0e8ff2c21ac50a3d1931 +checkout tokio https://github.com/guybedford/tokio emscripten-event-loop 5315798b6a62a47d03dc40e7d04fbf83c80f3246 apply_patch pumpkin pumpkin-emscripten.patch apply_patch pumpkin pumpkin-memory.patch @@ -66,7 +66,7 @@ echo "==> Emscripten" # main, and Binaryen from the branch carrying the jspi-hooks pass the frontend # needs; setup builds it. EMSDK selects an activated emsdk for LLVM instead. EMSDK_RELEASE=e8579ea489b44a6792f5abf95377a6ee38a16cce -checkout emscripten https://github.com/guybedford/emscripten cf-final a5013dd597ec9d48856370f5707d438937a7b4e8 +checkout emscripten https://github.com/guybedford/emscripten cf-final 4e034c65a18c0034c68e7eb628477fe4428fa1f9 NODE_PATH="$("$NODE" -p 'process.execPath')" (cd "$EMSCRIPTEN" && PATH="$(dirname "$NODE_PATH"):$PATH" npm ci --no-audit --no-fund && python3 bootstrap.py) if [ -n "${EMSDK:-}" ]; then diff --git a/scripts/test.sh b/scripts/test.sh index 6d67bd4..5ef4a42 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -7,7 +7,4 @@ require_toolchain "$NODE" "$REPO/scripts/check-ports.mjs" 25565 8787 bash "$REPO/scripts/build.sh" cd "$REPO" -cargo build --locked --release --features test-fixtures --bin filesystem-probe -"$NODE" node_modules/wrangler/bin/wrangler.js types worker/env.d.ts --strict-vars=false -"$NODE" node_modules/typescript/bin/tsc --noEmit exec "$NODE" tests/integration.mjs diff --git a/src/host.rs b/src/host.rs new file mode 100644 index 0000000..b674fb3 --- /dev/null +++ b/src/host.rs @@ -0,0 +1,67 @@ +//! Platform calls, each returning the platform's own Promise or value. + +use wasm_bindgen::prelude::*; + +#[wasm_bindgen(module = "cloudflare:node")] +extern "C" { + /// Routes an inbound socket to the `net.Server` listening on its local + /// port within the current Durable Object's port table; resolves when the + /// connection closes. + #[wasm_bindgen(js_name = handleAsNodeConnection, catch)] + pub fn handle_as_node_connection(socket: &JsValue) -> Result; +} + +pub fn property(target: &JsValue, name: &str) -> Result { + js_sys::Reflect::get(target, &name.into()) +} + +pub fn method(target: &JsValue, name: &str, args: &[&JsValue]) -> Result { + let function: js_sys::Function = property(target, name)?.unchecked_into(); + let array = js_sys::Array::new(); + for arg in args { + array.push(arg); + } + js_sys::Reflect::apply(&function, target, &array) +} + +/// `promise.then(on_fulfilled, on_rejected)` on any thenable; the callbacks +/// are one-shot closures already converted with `Closure::once_into_js`. +pub fn then( + promise: &JsValue, + on_fulfilled: Option<&JsValue>, + on_rejected: Option<&JsValue>, +) -> Result { + let then: js_sys::Function = property(promise, "then")?.unchecked_into(); + then.call2( + promise, + on_fulfilled.unwrap_or(&JsValue::UNDEFINED), + on_rejected.unwrap_or(&JsValue::UNDEFINED), + ) +} + +/// `stub.connect(authority, { allowHalfOpen: true })`. +pub fn stub_connect(stub: &JsValue, authority: &str) -> Result { + let options = js_sys::Object::new(); + js_sys::Reflect::set(&options, &"allowHalfOpen".into(), &JsValue::TRUE)?; + method(stub, "connect", &[&authority.into(), &options]) +} + +pub fn js_error(error: impl std::fmt::Display) -> JsValue { + js_sys::Error::new(&error.to_string()).into() +} + +/// `storage.sql.exec(query, ...bindings)` with the rows read out eagerly as +/// arrays of column values. +pub fn sql(storage: &JsValue, query: &str, bindings: &[JsValue]) -> Result>, JsValue> { + let sql = property(storage, "sql")?; + let exec: js_sys::Function = property(&sql, "exec")?.unchecked_into(); + let args = js_sys::Array::new(); + args.push(&query.into()); + for binding in bindings { + args.push(binding); + } + let cursor = js_sys::Reflect::apply(&exec, &sql, &args)?; + let raw = method(&cursor, "raw", &[])?; + let rows = js_sys::Array::from(&method(&raw, "toArray", &[])?); + Ok(rows.iter().map(|row| js_sys::Array::from(&row).to_vec()).collect()) +} diff --git a/src/main.rs b/src/main.rs index 93bf34e..a4114cd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,93 +1,92 @@ -// `#[wasm_bindgen(jspi)]` is experimental and warns as deprecated. -#![allow(deprecated)] - mod config; +mod host; mod memory; +mod persist; +mod world; -use pumpkin::{data::VanillaData, server::Server, PumpkinServer}; -use std::{cell::RefCell, sync::atomic::Ordering, sync::Arc}; +use host::{method, property, then}; use wasm_bindgen::prelude::*; fn main() {} -thread_local! { - static FAILURE: RefCell> = const { RefCell::new(None) }; - static SERVER: RefCell>> = const { RefCell::new(None) }; -} - -fn js_error(error: impl std::fmt::Display) -> JsValue { - js_sys::Error::new(&error.to_string()).into() +fn stub(env: &JsValue) -> Result { + let name = property(env, "WORLD_NAME")?; + let namespace = property(env, "WORLD")?; + method(&namespace, "getByName", &[&name]) } -fn server() -> Result, JsValue> { - if let Some(error) = FAILURE.with(|failure| failure.borrow().clone()) { - return Err(js_error(error)); +/// Pipes an inbound TCP connection to the world's object until either side closes. +#[wasm_bindgen] +pub fn connect(socket: JsValue, env: JsValue, _ctx: JsValue) -> Result { + let target = host::stub_connect(&stub(&env)?, &world::authority())?; + // Read/write failures are observed by the pumps; a normal peer disconnect + // must not surface as an unhandled rejection of the lifetime promise. + let swallow = Closure::::new(|_| ()).into_js_value(); + for side in [&socket, &target] { + then(&property(side, "closed")?, None, Some(&swallow))?; } - SERVER - .with(|server| server.borrow().clone()) - .ok_or_else(|| js_error("Server is not running")) -} - -/// Runs the server on a Tokio runtime that parks by suspending this activation. -/// `ready` is called once the listener is bound. Resolves after `pumpkin_stop` -/// once the final save completes. -#[wasm_bindgen(jspi)] -pub fn pumpkin_run(ready: js_sys::Function) -> Result<(), JsValue> { - std::panic::set_hook(Box::new(|info| { - FAILURE.with(|failure| { - failure.borrow_mut().get_or_insert_with(|| info.to_string()); + let abort = web_sys::AbortController::new()?; + let pipe = |from: &JsValue, to: &JsValue| -> Result { + let readable: web_sys::ReadableStream = property(from, "readable")?.unchecked_into(); + let writable: web_sys::WritableStream = property(to, "writable")?.unchecked_into(); + let options = web_sys::StreamPipeOptions::new(); + options.set_signal(&abort.signal()); + let abort = abort.clone(); + let on_error = Closure::once_into_js(move |error: JsValue| -> Result { + abort.abort(); + Err(error) }); - eprintln!("RUST PANIC: {info}"); - })); - rayon::ThreadPoolBuilder::new() - .num_threads(1) - .use_current_thread() - .build_global() - .map_err(js_error)?; - let (basic, advanced) = config::configuration(); - pumpkin::init_logger(&advanced); - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(js_error)?; - runtime.block_on(async { - let server = PumpkinServer::new(basic, advanced, VanillaData::load()).await; - server.init_plugins().await; - SERVER.with(|slot| *slot.borrow_mut() = Some(server.server.clone())); - ready.call0(&JsValue::UNDEFINED)?; - server.start().await; - SERVER.with(|slot| slot.borrow_mut().take()); - Ok(()) - }) + then(&readable.pipe_to_with_options(&writable, &options), None, Some(&on_error)) + }; + let pumps = js_sys::Array::of2(&pipe(&socket, &target)?, &pipe(&target, &socket)?); + let close_both = Closure::once_into_js(move |results: JsValue| -> Result { + for result in js_sys::Array::from(&results).iter() { + if property(&result, "status")? != "rejected" { + continue; + } + let reason = property(&result, "reason")?; + let text = reason + .as_string() + .unwrap_or_else(|| js_sys::Error::from(reason.clone()).message().into()) + .to_lowercase(); + let expected = ["closed", "closing", "abort", "cancel", "reset", "network connection lost"]; + if !expected.iter().any(|e| text.contains(e)) { + web_sys::console::error_2(&"TCP forwarding failed".into(), &reason); + } + } + let closes = js_sys::Array::new(); + for side in [&socket, &target] { + closes.push(&method(side, "close", &[])?); + } + Ok(js_sys::Promise::all_settled(&closes).into()) + }); + then(&js_sys::Promise::all_settled(&pumps).into(), Some(&close_both), None) } -/// Requests shutdown; `pumpkin_run` completes the save and returns. -#[wasm_bindgen] -pub fn pumpkin_stop() { - pumpkin::stop_server(); +fn json_response(value: &JsValue, status: u16) -> Result { + let init = web_sys::ResponseInit::new(); + init.set_status(status); + let headers = web_sys::Headers::new()?; + headers.set("content-type", "application/json")?; + init.set_headers(&headers); + let body = js_sys::JSON::stringify(value)?; + Ok(web_sys::Response::new_with_opt_str_and_init(body.as_string().as_deref(), &init)?.into()) } #[wasm_bindgen] -pub fn pumpkin_status() -> Result { - let server = server()?; - let result = js_sys::Object::new(); - js_sys::Reflect::set( - &result, - &"players".into(), - &(server.get_all_players().len() as f64).into(), - )?; - js_sys::Reflect::set( - &result, - &"ticks".into(), - &(server.tick_count.load(Ordering::Relaxed) as f64).into(), - )?; - js_sys::Reflect::set( - &result, - &"wasm_memory_bytes".into(), - &((core::arch::wasm32::memory_size::<0>() * 65536) as f64).into(), - )?; - for (name, bytes) in memory::heap_usage() { - js_sys::Reflect::set(&result, &name.into(), &(bytes as f64).into())?; +pub fn fetch(request: web_sys::Request, env: JsValue, _ctx: JsValue) -> Result { + let url = web_sys::Url::new(&request.url())?; + match url.pathname().as_str() { + "/health" => { + let body = js_sys::Object::new(); + js_sys::Reflect::set(&body, &"ready".into(), &JsValue::TRUE)?; + json_response(&body, 200) + } + "/" if request.method() == "GET" => { + let status = method(&stub(&env)?, "status", &[])?; + let respond = Closure::once_into_js(|value: JsValue| json_response(&value, 200)); + then(&status, Some(&respond), None) + } + _ => json_response(&"Not found".into(), 404), } - Ok(result.into()) } diff --git a/src/persist.rs b/src/persist.rs new file mode 100644 index 0000000..ef67f08 --- /dev/null +++ b/src/persist.rs @@ -0,0 +1,93 @@ +//! The world lives in the object's `/tmp` tree while it runs and in its SQLite +//! storage between runs: one row per file, written at checkpoint, read at start. + +use crate::host::{js_error, sql}; +use std::{ + collections::HashMap, + fs, + path::{Path, PathBuf}, +}; +use wasm_bindgen::JsValue; + +pub const ROOT: &str = "/tmp/world"; + +pub fn prepare(storage: &JsValue) -> Result<(), JsValue> { + sql( + storage, + "CREATE TABLE IF NOT EXISTS files (path TEXT PRIMARY KEY, data BLOB NOT NULL)", + &[], + )?; + Ok(()) +} + +fn stored(storage: &JsValue) -> Result>, JsValue> { + let mut files = HashMap::new(); + for row in sql(storage, "SELECT path, data FROM files", &[])? { + let [path, data] = row.as_slice() else { + return Err(js_error("unexpected files row")); + }; + let path = path.as_string().ok_or_else(|| js_error("path is not text"))?; + let data = js_sys::Uint8Array::new(&data).to_vec(); + files.insert(path, data); + } + Ok(files) +} + +/// Recreates the stored tree under `ROOT`. +pub fn restore(storage: &JsValue) -> Result { + let _ = fs::remove_dir_all(ROOT); + fs::create_dir_all(ROOT).map_err(js_error)?; + let files = stored(storage)?; + for (path, data) in &files { + let target = Path::new(ROOT).join(path); + if let Some(parent) = target.parent() { + fs::create_dir_all(parent).map_err(js_error)?; + } + fs::write(target, data).map_err(js_error)?; + } + Ok(files.len()) +} + +/// Writes every file under `ROOT` that differs from storage and removes rows for +/// files that no longer exist. Returns (written, removed). +pub fn save(storage: &JsValue) -> Result<(usize, usize), JsValue> { + let mut previous = stored(storage)?; + let mut files = Vec::new(); + walk(Path::new(ROOT), &mut files).map_err(js_error)?; + let mut written = 0; + for file in files { + let key = file + .strip_prefix(ROOT) + .map_err(js_error)? + .to_string_lossy() + .into_owned(); + let data = fs::read(&file).map_err(js_error)?; + if previous.remove(&key).is_some_and(|old| old == data) { + continue; + } + sql( + storage, + "INSERT OR REPLACE INTO files (path, data) VALUES (?, ?)", + &[key.into(), js_sys::Uint8Array::from(data.as_slice()).into()], + )?; + written += 1; + } + let removed = previous.len(); + for key in previous.into_keys() { + sql(storage, "DELETE FROM files WHERE path = ?", &[key.into()])?; + } + Ok((written, removed)) +} + +fn walk(dir: &Path, out: &mut Vec) -> std::io::Result<()> { + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + walk(&path, out)?; + } else { + out.push(path); + } + } + Ok(()) +} diff --git a/src/workerd.js b/src/workerd.js new file mode 100644 index 0000000..fd37705 --- /dev/null +++ b/src/workerd.js @@ -0,0 +1,54 @@ +// Emscripten library adjustments for NODERAWFS on workerd's Node filesystem. +addToLibrary({ + // Node's deprecated process.binding is absent; fs.constants is the public + // form of the same table (emscripten-core/emscripten cf-final 4e034c65a). + $NODEFS__postset: ` + NODEFS.isWindows = false; + NODEFS.flagsForNodeMap = Object.fromEntries([ + [{{{ cDefs.O_APPEND }}}, 'O_APPEND'], + [{{{ cDefs.O_CREAT }}}, 'O_CREAT'], + [{{{ cDefs.O_EXCL }}}, 'O_EXCL'], + [{{{ cDefs.O_NOCTTY }}}, 'O_NOCTTY'], + [{{{ cDefs.O_RDONLY }}}, 'O_RDONLY'], + [{{{ cDefs.O_RDWR }}}, 'O_RDWR'], + [{{{ cDefs.O_DSYNC }}}, 'O_SYNC'], + [{{{ cDefs.O_TRUNC }}}, 'O_TRUNC'], + [{{{ cDefs.O_WRONLY }}}, 'O_WRONLY'], + [{{{ cDefs.O_NOFOLLOW }}}, 'O_NOFOLLOW'], + ].map(([flag, name]) => [flag, fs.constants[name]])); + `, + + // workerd has no process stdio descriptors: keep fds 0-2 on Emscripten's + // console callbacks so Pumpkin's log and Rust panics reach the host console. + // Under NODERAWFS the host enforces permissions; workerd reports directories + // as 0666, which would otherwise fail the VFS's execute check in getdents. + $workerdStdio__deps: ['$FS', '$TTY'], + $workerdStdio__postset: () => addAtPreRun('workerdStdio()'), + $workerdStdio: () => { + Object.defineProperty(FS, 'ignorePermissions', { get: () => true, set() {} }); + const consoles = [ + null, + { output: [], ops: TTY.default_tty_ops }, + { output: [], ops: TTY.default_tty1_ops }, + ]; + const fstat = FS.fstat; + FS.fstat = (fd) => { + const stream = FS.getStreamChecked(fd); + if (stream.nfd === 0 || stream.nfd === 1 || stream.nfd === 2) { + return { + dev: 0, ino: stream.nfd, mode: 0o020666, nlink: 1, uid: 0, gid: 0, rdev: 0, + size: 0, blksize: 4096, blocks: 0, + atime: new Date(0), mtime: new Date(0), ctime: new Date(0), + }; + } + return fstat(fd); + }; + const write = FS.write; + FS.write = (stream, buffer, offset, length, position) => { + const tty = consoles[stream.nfd]; + if (!tty) return write(stream, buffer, offset, length, position); + for (let i = offset; i < offset + length; i++) tty.ops.put_char(tty, buffer[i]); + return length; + }; + }, +}); diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..6215c00 --- /dev/null +++ b/src/world.rs @@ -0,0 +1,303 @@ +//! The Durable Object hosting one Pumpkin server. +//! +//! The object owns an `EventLoopRuntime`: Tokio's current-thread scheduler and +//! drivers with the host event loop as its wait. Work is scheduled as roots +//! that complete by callback, so every export returns a Promise and nothing +//! blocks or suspends. The server lifetime is one such root: the first +//! `connect` after idle schedules it, and its completion (after the final save) +//! is the checkpoint. Later connections are routed to the running listener. + +use crate::{config, host, memory, persist}; +use host::{js_error, method, property, then}; +use pumpkin::{data::VanillaData, server::Server, PumpkinServer}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, + sync::{atomic::Ordering, Arc}, +}; +use tokio::runtime::EventLoopRuntime; +use wasm_bindgen::prelude::*; + +pub const PORT: u16 = 25565; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum Phase { + Idle, + Starting, + Running, + Stopping, + Failed, +} + +impl Phase { + fn name(self) -> &'static str { + match self { + Phase::Idle => "idle", + Phase::Starting => "starting", + Phase::Running => "running", + Phase::Stopping => "stopping", + Phase::Failed => "failed", + } + } +} + +thread_local! { + static PANIC: RefCell> = const { RefCell::new(None) }; + static SERVER: RefCell>> = const { RefCell::new(None) }; + /// Pumpkin's logger is process-wide and installed once. + static LOGGER: Cell = const { Cell::new(false) }; +} + +/// State shared between the exports and the scheduled roots. +struct Shared { + /// `ctx.storage` + storage: JsValue, + runtime: EventLoopRuntime, + phase: Cell, + failure: RefCell>, + connections: Cell, + startup_ms: Cell>, + checkpointed_at: RefCell>, + /// Signals the server root that the last connection has closed. + idle: Arc, + /// Resolves at the next phase change; connections arriving mid-transition + /// wait on it and retry. + transition: RefCell>, +} + +// Closures run under catch_unwind; the object is only ever touched from this +// thread through the runtime's calls, so a poisoned borrow cannot be observed. +impl std::panic::RefUnwindSafe for Shared {} + +#[wasm_bindgen] +pub struct MinecraftWorld { + shared: Rc, +} + +#[wasm_bindgen] +impl MinecraftWorld { + #[wasm_bindgen(constructor)] + pub fn new(state: JsValue, _env: JsValue) -> Result { + std::panic::set_hook(Box::new(|info| { + PANIC.with(|slot| { + slot.borrow_mut().get_or_insert_with(|| info.to_string()); + }); + eprintln!("RUST PANIC: {info}"); + })); + let storage = property(&state, "storage")?; + persist::prepare(&storage)?; + let runtime = tokio::runtime::Builder::new_current_thread() + .enable_all() + .build_event_loop_runtime() + .map_err(js_error)?; + Ok(MinecraftWorld { + shared: Rc::new(Shared { + storage, + runtime, + phase: Cell::new(Phase::Idle), + failure: RefCell::new(None), + connections: Cell::new(0), + startup_ms: Cell::new(None), + checkpointed_at: RefCell::new(None), + idle: Arc::new(tokio::sync::Notify::new()), + transition: RefCell::new(None), + }), + }) + } + + /// Serves one inbound connection; resolves when it closes. The first call + /// while idle also runs the server, resolving once the world is checkpointed. + pub fn connect(&self, socket: JsValue) -> Result { + self.shared.connect(socket) + } + + pub fn status(&self) -> Result { + let shared = &self.shared; + let result = js_sys::Object::new(); + let set = |key: &str, value: JsValue| js_sys::Reflect::set(&result, &key.into(), &value); + set("phase", shared.phase.get().name().into())?; + set("connections", (shared.connections.get() as f64).into())?; + set( + "failure", + shared.failure.borrow().as_deref().map_or(JsValue::NULL, JsValue::from), + )?; + set("startup_ms", shared.startup_ms.get().map_or(JsValue::NULL, JsValue::from))?; + set( + "checkpointed_at", + shared.checkpointed_at.borrow().as_deref().map_or(JsValue::NULL, JsValue::from), + )?; + let server = SERVER.with(|slot| slot.borrow().clone()); + set( + "server", + match (shared.phase.get(), server) { + (Phase::Running, Some(server)) => server_status(&server)?, + _ => JsValue::NULL, + }, + )?; + Ok(result.into()) + } +} + +impl Shared { + fn connect(self: &Rc, socket: JsValue) -> Result { + match self.phase.get() { + Phase::Running => return self.route(socket), + Phase::Failed => { + return Err(js_error(self.failure.borrow().as_deref().unwrap_or("failed"))) + } + Phase::Starting | Phase::Stopping => { + let shared = self.clone(); + let retry = Closure::once_into_js(move |_: JsValue| shared.connect(socket)); + return then(&self.transition()?.into(), Some(&retry), None); + } + Phase::Idle => {} + } + self.set_phase(Phase::Starting); + let started = js_sys::Date::now(); + let root = self.clone(); + self.promise(async move { root.run(socket, started).await }) + } + + /// The promise of the next phase change. + fn transition(&self) -> Result { + let mut slot = self.transition.borrow_mut(); + if let Some((promise, _)) = slot.as_ref() { + return Ok(promise.clone()); + } + let resolvers = with_resolvers()?; + let promise: js_sys::Promise = property(&resolvers, "promise")?.unchecked_into(); + let resolve: js_sys::Function = property(&resolvers, "resolve")?.unchecked_into(); + Ok(slot.insert((promise, resolve)).0.clone()) + } + + fn set_phase(&self, phase: Phase) { + self.phase.set(phase); + if let Some((_, resolve)) = self.transition.borrow_mut().take() { + let _ = resolve.call0(&JsValue::UNDEFINED); + } + } + + /// Schedules `future` as a root and returns the Promise of its outcome. + fn promise( + self: &Rc, + future: impl std::future::Future> + 'static, + ) -> Result { + let resolvers = with_resolvers()?; + let promise = property(&resolvers, "promise")?; + let resolve: js_sys::Function = property(&resolvers, "resolve")?.unchecked_into(); + let reject: js_sys::Function = property(&resolvers, "reject")?.unchecked_into(); + let shared = self.clone(); + self.runtime.schedule(future, move |outcome| { + let outcome = + outcome.unwrap_or_else(|panic| Err(js_error(format!("server task failed: {panic}")))); + match outcome { + Ok(value) => { + let _ = resolve.call1(&JsValue::UNDEFINED, &value); + } + Err(error) => { + shared.fail(&error); + let _ = reject.call1(&JsValue::UNDEFINED, &error); + } + } + }); + Ok(promise) + } + + fn fail(&self, error: &JsValue) { + if self.phase.get() != Phase::Failed { + self.set_phase(Phase::Failed); + let message = js_sys::Error::from(error.clone()).message(); + *self.failure.borrow_mut() = Some(String::from(message)); + } + } + + /// Routes a socket to Pumpkin's listener and tracks it until it closes. + fn route(self: &Rc, socket: JsValue) -> Result { + self.connections.set(self.connections.get() + 1); + let promise = host::handle_as_node_connection(&socket)?; + let shared = self.clone(); + let done = Closure::once_into_js(move |_: JsValue| { + shared.connections.set(shared.connections.get() - 1); + if shared.connections.get() == 0 { + shared.idle.notify_one(); + } + }); + then(&promise, Some(&done), Some(&done)) + } + + async fn run(self: Rc, first: JsValue, started: f64) -> Result { + let restored = persist::restore(&self.storage)?; + std::env::set_current_dir(persist::ROOT).map_err(js_error)?; + eprintln!("Restored {restored} world files"); + pumpkin::reset_stop(); + let (basic, advanced) = config::configuration(); + if !LOGGER.replace(true) { + pumpkin::init_logger(&advanced); + } + let server = PumpkinServer::new(basic, advanced, VanillaData::load()).await; + server.init_plugins().await; + SERVER.with(|slot| *slot.borrow_mut() = Some(server.server.clone())); + self.set_phase(Phase::Running); + self.startup_ms.set(Some(js_sys::Date::now() - started)); + + // The first connection's promise belongs to the platform; the server + // root tracks it only through `connections`. + drop(self.route(first)?); + let idle = self.idle.clone(); + let shared = self.clone(); + tokio::task::spawn_local(async move { + loop { + idle.notified().await; + // A connection may have been routed since the notification. + if shared.connections.get() == 0 { + break; + } + } + // Stopping before the listener closes, so no connection is routed + // to a server that will not accept it. + shared.set_phase(Phase::Stopping); + pumpkin::stop_server(); + }); + // Returns after the final save once stopped. + server.start().await; + SERVER.with(|slot| slot.borrow_mut().take()); + if let Some(panic) = PANIC.with(|slot| slot.borrow_mut().take()) { + return Err(js_error(panic)); + } + let (written, removed) = persist::save(&self.storage)?; + eprintln!("Checkpoint: {written} files written, {removed} removed"); + // Issued writes become durable before the connection is reported closed. + let synced = method(&self.storage, "sync", &[])?; + let shared = self.clone(); + let done = Closure::once_into_js(move |_: JsValue| { + shared + .checkpointed_at + .replace(Some(String::from(js_sys::Date::new_0().to_iso_string()))); + shared.set_phase(Phase::Idle); + }); + then(&synced, Some(&done), None) + } +} + +fn with_resolvers() -> Result { + method(&js_sys::Promise::resolve(&JsValue::UNDEFINED).constructor().into(), "withResolvers", &[]) +} + +fn server_status(server: &Server) -> Result { + let result = js_sys::Object::new(); + let set = |key: &str, value: f64| js_sys::Reflect::set(&result, &key.into(), &value.into()); + set("players", server.get_all_players().len() as f64)?; + set("ticks", server.tick_count.load(Ordering::Relaxed) as f64)?; + set( + "wasm_memory_bytes", + (core::arch::wasm32::memory_size::<0>() * 65536) as f64, + )?; + for (name, bytes) in memory::heap_usage() { + set(name, bytes as f64)?; + } + Ok(result.into()) +} + +pub fn authority() -> String { + format!("world:{PORT}") +} diff --git a/tests/fixtures/filesystem.rs b/tests/fixtures/filesystem.rs deleted file mode 100644 index 37a044e..0000000 --- a/tests/fixtures/filesystem.rs +++ /dev/null @@ -1,87 +0,0 @@ -#![allow(deprecated)] - -use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::net::TcpListener; -use wasm_bindgen::prelude::*; - -fn main() {} - -fn js_error(error: impl std::fmt::Display) -> JsValue { - js_sys::Error::new(&error.to_string()).into() -} - -/// Echo every accepted connection for the lifetime of the instance. -#[wasm_bindgen(jspi)] -pub fn echo_server(ready: js_sys::Function) -> Result<(), JsValue> { - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .map_err(js_error)?; - runtime.block_on(async { - let listener = TcpListener::bind("0.0.0.0:25565").await.map_err(js_error)?; - ready.call0(&JsValue::UNDEFINED)?; - loop { - let (mut socket, _) = listener.accept().await.map_err(js_error)?; - tokio::spawn(async move { - let mut bytes = [0u8; 16 * 1024]; - loop { - match socket.read(&mut bytes).await { - Ok(0) | Err(_) => break, - Ok(count) => { - if socket.write_all(&bytes[..count]).await.is_err() { - break; - } - } - } - } - let _ = socket.shutdown().await; - }); - } - }) -} - -/// Exercise the filesystem syscall path before loading Pumpkin's world data. -#[wasm_bindgen] -pub fn filesystem_probe(value: String) -> Result { - use std::io::{Read, Seek, SeekFrom, Write}; - std::fs::create_dir_all("transport-probe").map_err(js_error)?; - let path = "transport-probe/state.bin"; - let previous = std::fs::read_to_string(path).unwrap_or_default(); - let mut file = std::fs::OpenOptions::new() - .create(true) - .truncate(true) - .read(true) - .write(true) - .open("transport-probe/pending.bin") - .map_err(js_error)?; - file.write_all(b"----").map_err(js_error)?; - file.seek(SeekFrom::Start(0)).map_err(js_error)?; - file.write_all(value.as_bytes()).map_err(js_error)?; - file.set_len(value.len() as u64).map_err(js_error)?; - file.sync_all().map_err(js_error)?; - file.seek(SeekFrom::Start(0)).map_err(js_error)?; - let mut check = String::new(); - file.read_to_string(&mut check).map_err(js_error)?; - if check != value { - return Err(js_error("Filesystem descriptor roundtrip failed")); - } - drop(file); - std::fs::rename("transport-probe/pending.bin", path).map_err(js_error)?; - let mut large = vec![0x5a; 3 * 1024 * 1024]; - std::fs::write("transport-probe/large.bin", &large).map_err(js_error)?; - let mut file = std::fs::OpenOptions::new() - .write(true) - .open("transport-probe/large.bin") - .map_err(js_error)?; - file.seek(SeekFrom::Start(65534)).map_err(js_error)?; - file.write_all(&[1, 2, 3, 4]).map_err(js_error)?; - large[65534..65538].copy_from_slice(&[1, 2, 3, 4]); - file.set_len(large.len() as u64 + 3).map_err(js_error)?; - large.extend_from_slice(&[0, 0, 0]); - file.sync_all().map_err(js_error)?; - drop(file); - if std::fs::read("transport-probe/large.bin").map_err(js_error)? != large { - return Err(js_error("Large file roundtrip failed")); - } - Ok(previous) -} diff --git a/tests/fixtures/startup-failure.mjs b/tests/fixtures/startup-failure.mjs deleted file mode 100644 index 10849da..0000000 --- a/tests/fixtures/startup-failure.mjs +++ /dev/null @@ -1,14 +0,0 @@ -import { MinecraftWorld } from '../../worker/world'; - -export class StartupFailure extends MinecraftWorld { - loadRuntime() { throw new Error('Expected startup failure'); } - - async verify() { - const attempt = this.getRuntime(); - const starting = this.status(); - try { await attempt; } catch {} - const failed = this.status(); - try { await this.getRuntime(); } catch {} - return { starting, failed, repeated: this.status() }; - } -} diff --git a/tests/fixtures/worker.mjs b/tests/fixtures/worker.mjs deleted file mode 100644 index dbde6d3..0000000 --- a/tests/fixtures/worker.mjs +++ /dev/null @@ -1,64 +0,0 @@ -import { DurableObject, exports } from 'cloudflare:workers'; -import { handleAsNodeConnection } from 'cloudflare:node'; -export { StartupFailure } from './startup-failure.mjs'; -import createModule from '../../target/workers/wasm32-unknown-emscripten/release/filesystem-probe.js'; -import wasm from '../../target/workers/wasm32-unknown-emscripten/release/filesystem_probe.wasm'; -import { createWorldRuntime } from '../../worker/filesystem'; -import { forwardSocket } from '../../worker/socket'; - -const PORT = 25565; - -export class FilesystemTest extends DurableObject { - runtime; - - getRuntime() { - return this.runtime ??= createWorldRuntime(this.ctx.storage, options => createModule({ - ...options, - instantiateWasm(imports, receive) { - const instance = new WebAssembly.Instance(wasm, imports); - receive(instance); - return instance.exports; - }, - })).then(async runtime => { - await new Promise((ready, reject) => { - runtime.run(() => runtime.instance.echo_server(ready)).catch(reject); - }); - return runtime; - }); - } - - async connect(socket) { - void socket.closed.catch(() => undefined); - await this.getRuntime(); - await handleAsNodeConnection(socket); - } - - async status() { - await this.getRuntime(); - return { listening: true }; - } - - async filesystemProbe(value) { - const runtime = await this.getRuntime(); - return runtime.run(() => runtime.instance.filesystem_probe(value)); - } -} - -export default { - async connect(socket, env) { - const target = exports.FilesystemTest.getByName(env.WORLD_NAME).connect(`world:${PORT}`, { allowHalfOpen: true }); - await forwardSocket(socket, target); - }, - async fetch(request, env) { - const url = new URL(request.url); - if (url.pathname === '/health') return Response.json({ ready: true }); - if (url.pathname === '/startup-failure') { - return Response.json(await exports.StartupFailure.getByName('failure').verify()); - } - const stub = exports.FilesystemTest.getByName(url.searchParams.get('world') ?? env.WORLD_NAME); - if (request.method === 'POST' && url.pathname === '/filesystem-probe') { - return Response.json({ previous: await stub.filesystemProbe(await request.text()) }); - } - return Response.json(await stub.status()); - }, -}; diff --git a/tests/fixtures/wrangler.jsonc b/tests/fixtures/wrangler.jsonc deleted file mode 100644 index 8ab097a..0000000 --- a/tests/fixtures/wrangler.jsonc +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "../../node_modules/wrangler/config-schema.json", - "name": "minecraft-filesystem-tests", - "main": "worker.mjs", - "compatibility_date": "2026-09-05", - "compatibility_flags": ["nodejs_compat", "new_module_registry", "no_handle_cross_request_promise_resolution"], - "dev": { "ip": "127.0.0.1", "port": 8787 }, - "connect": [{ "protocol": "tcp", "address": "127.0.0.1", "port": 25565 }], - "exports": { - "FilesystemTest": { "type": "durable-object", "storage": "sqlite" }, - "StartupFailure": { "type": "durable-object", "storage": "sqlite" } - }, - "vars": { "WORLD_NAME": "integration" } -} diff --git a/tests/integration.mjs b/tests/integration.mjs index 9c9a9eb..fcc0f51 100644 --- a/tests/integration.mjs +++ b/tests/integration.mjs @@ -1,5 +1,4 @@ import assert from 'node:assert/strict'; -import net from 'node:net'; import { readFile } from 'node:fs/promises'; import { setTimeout as delay } from 'node:timers/promises'; import { status, play, waitFor } from './client.mjs'; @@ -30,55 +29,8 @@ function editableBlock(client) { throw new Error(`No stable block within reach of ${JSON.stringify(client.position)}`); } -async function echo() { - const socket = net.connect({ host: '127.0.0.1', port: 25565 }); - socket.setTimeout(15000, () => socket.destroy(new Error('Echo timed out'))); - const payload = Buffer.from(Array.from({ length: 65536 }, (_, index) => index & 255)); - const chunks = []; - let length = 0; - try { - await new Promise((resolve, reject) => { - socket.once('connect', resolve); - socket.once('error', reject); - }); - for (let offset = 0; offset < payload.length; offset += 997) socket.write(payload.subarray(offset, offset + 997)); - for await (const chunk of socket) { - chunks.push(chunk); - length += chunk.length; - if (length >= payload.length) break; - } - assert.ok(Buffer.concat(chunks).equals(payload), `TCP echo differed: received ${length} of ${payload.length} bytes`); - } finally { socket.destroy(); } -} - -async function marker(world, value) { - return (await request(`/filesystem-probe?world=${world}`, { method: 'POST', body: value })).previous; -} - -let worker = await startWorker(true); -try { - await Promise.all([echo(), echo()]); - assert.equal((await request('/')).listening, true); - const failure = await request('/startup-failure'); - assert.equal(failure.starting.phase, 'starting'); - assert.equal(failure.failed.phase, 'failed'); - assert.equal(failure.failed.failure, 'Expected startup failure'); - assert.deepEqual(failure.repeated, failure.failed); - assert.equal(await marker('isolation-a', 'alpha'), ''); - assert.equal(await marker('isolation-b', 'beta'), ''); - assert.equal(await marker('isolation-a', 'alpha-2'), 'alpha'); - worker.healthy(); -} finally { await worker.stop(); } -worker = await startWorker(true); -try { - assert.equal(await marker('isolation-a', 'alpha-3'), 'alpha-2'); - assert.equal(await marker('isolation-b', 'beta-2'), 'beta'); - worker.healthy(); -} finally { await worker.stop(); } -console.log('Filesystem, socket, and isolation tests passed'); - let savedChunk, savedPosition, changedBlock; -worker = await startWorker(); +let worker = await startWorker(); const clients = []; try { assert.equal((await status()).version.protocol, 776); diff --git a/tests/server.mjs b/tests/server.mjs index 37ac929..b78e406 100644 --- a/tests/server.mjs +++ b/tests/server.mjs @@ -23,11 +23,11 @@ export async function testServer({ seed = '1789195700909824175' } = {}) { } let phase = 0; - async function startWorker(fixture = false) { - const log = path.join(probe, `${++phase}-${fixture ? "filesystem" : "minecraft"}.log`); + async function startWorker() { + const log = path.join(probe, `${++phase}-minecraft.log`); const child = spawn(process.execPath, [ path.join(repo, 'node_modules/wrangler/bin/wrangler.js'), 'dev', '--local', - '--config', path.join(repo, fixture ? 'tests/fixtures/wrangler.jsonc' : 'wrangler.jsonc'), + '--config', path.join(repo, 'wrangler.jsonc'), '--persist-to', persistence, '--var', 'WORLD_NAME:integration', // Known terrain with a stable editable spawn block; random water spawns // cannot exercise the integration test's shared block-edit assertions. diff --git a/tsconfig.json b/tsconfig.json deleted file mode 100644 index afefe46..0000000 --- a/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "Bundler", - "lib": ["ES2022"], - "strict": true, - "noEmit": true, - "skipLibCheck": true - }, - "include": ["worker/**/*.ts"] -} diff --git a/worker/filesystem.ts b/worker/filesystem.ts deleted file mode 100644 index 79dcf6e..0000000 --- a/worker/filesystem.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { AsyncLocalStorage } from 'node:async_hooks'; -import { LocalDOFilesystem } from 'durable-object-fs/local'; -import { mount, withMounts } from 'worker-fs-mount'; -import * as nodeFs from 'worker-fs-mount/fs-sync'; - -/** Each wasm instance keeps one mount context across requests and Tokio callbacks. */ -export function createWorldRuntime( - storage: DurableObjectStorage, - create: (options: { nodeFs: typeof nodeFs; cwd: string }) => Promise, -) { - return withMounts(async () => { - mount('/data', new LocalDOFilesystem(storage)); - const run = AsyncLocalStorage.snapshot(); - return { instance: await create({ nodeFs, cwd: '/data' }), run }; - }); -} diff --git a/worker/fs-library.js b/worker/fs-library.js deleted file mode 100644 index fd7153d..0000000 --- a/worker/fs-library.js +++ /dev/null @@ -1,114 +0,0 @@ -// Emscripten's Node loader uses createRequire at runtime, so Wrangler's build-time -// aliases cannot replace its fs object. Supply the mounted node:fs implementation -// to each factory, with a cwd that does not change the isolate's process.cwd. -addToLibrary({ - // Workers exposes public fs.constants, but not Node's internal process.binding. - $NODEFS__postset: ` - NODEFS.isWindows = false; - NODEFS.flagsForNodeMap = Object.fromEntries([ - [{{{ cDefs.O_APPEND }}}, 'O_APPEND'], - [{{{ cDefs.O_CREAT }}}, 'O_CREAT'], - [{{{ cDefs.O_EXCL }}}, 'O_EXCL'], - [{{{ cDefs.O_NOCTTY }}}, 'O_NOCTTY'], - [{{{ cDefs.O_RDONLY }}}, 'O_RDONLY'], - [{{{ cDefs.O_RDWR }}}, 'O_RDWR'], - [{{{ cDefs.O_DSYNC }}}, 'O_SYNC'], - [{{{ cDefs.O_TRUNC }}}, 'O_TRUNC'], - [{{{ cDefs.O_WRONLY }}}, 'O_WRONLY'], - [{{{ cDefs.O_NOFOLLOW }}}, 'O_NOFOLLOW'], - ].map(([flag, name]) => [flag, fs.constants[name]])); - `, - - $workerFilesystem__deps: ['$FS', '$PATH_FS', '$TTY'], - $workerFilesystem__postset: () => addAtPreRun('workerFilesystem()'), - $workerFilesystem: () => { - // biome-ignore lint/complexity/useLiteralKeys: preserve the external Emscripten property name - fs = Module['nodeFs']; - // biome-ignore lint/complexity/useLiteralKeys: preserve the external Emscripten property name - let workerCwd = Module['cwd']; - FS.cwd = () => workerCwd; - // Keep standard output on Emscripten's console callbacks, including printErr - // so the host can record Rust panics. Native Worker fds are not stdio handles. - const consoles = [ - null, - { output: [], ops: TTY.default_tty_ops }, - { output: [], ops: TTY.default_tty1_ops }, - ]; - // NODERAWFS's stdio streams have no nodes; expose character-device metadata - // without asking the native Workers filesystem about those descriptor numbers. - const fstat = FS.fstat; - FS.fstat = (fd) => { - const stream = FS.getStreamChecked(fd); - if (stream.nfd === 0 || stream.nfd === 1 || stream.nfd === 2) { - return { - dev: 0, - ino: stream.nfd, - mode: 0o020666, - nlink: 1, - uid: 0, - gid: 0, - rdev: 0, - size: 0, - blksize: 4096, - blocks: 0, - atime: new Date(0), - mtime: new Date(0), - ctime: new Date(0), - }; - } - return fstat(fd); - }; - const write = FS.write; - FS.write = (stream, buffer, offset, length, position) => { - const tty = consoles[stream.nfd]; - if (!tty) return write(stream, buffer, offset, length, position); - for (let i = offset; i < offset + length; i++) tty.ops.put_char(tty, buffer[i]); - return length; - }; - // Non-*at syscalls can pass relative paths directly to NODERAWFS. Resolve - // those against this wasm instance, not the shared Node process directory. - for (const name of [ - 'lookupPath', - 'stat', - 'chmod', - 'chown', - 'truncate', - 'utime', - 'open', - 'mkdir', - 'rmdir', - 'readdir', - 'unlink', - 'readlink', - ]) { - const operation = FS[name]; - FS[name] = (path, ...args) => operation(PATH_FS.resolve(path), ...args); - } - const rename = FS.rename; - FS.rename = (from, to) => rename(PATH_FS.resolve(from), PATH_FS.resolve(to)); - const symlink = FS.symlink; - FS.symlink = (target, path) => symlink(target, PATH_FS.resolve(path)); - FS.chdir = (path) => { - path = PATH_FS.resolve(path); - if (!FS.isDir(FS.stat(path).mode)) throw new FS.ErrnoError(ERRNO_CODES.ENOTDIR); - workerCwd = path; - }; - }, - - // SQLite operations are synchronous. Checkpoint completion separately awaits - // storage.sync(); ordinary Rust File::sync_all must not suspend a sync export. - fd_sync__deps: ['$FS', '$workerFilesystem'], - fd_sync__async: false, - fd_sync: (fd) => { - try { - const stream = FS.getStreamChecked(fd); - if (typeof stream.nfd === 'number') fs.fsyncSync(stream.nfd); - else stream.stream_ops?.fsync?.(stream); - return 0; - } catch (error) { - if (typeof error.errno === 'number') return error.errno; - if (error.code) return ERRNO_CODES[error.code]; - throw error; - } - }, -}); diff --git a/worker/index.mjs b/worker/index.mjs new file mode 100644 index 0000000..0bd31de --- /dev/null +++ b/worker/index.mjs @@ -0,0 +1,16 @@ +import { DurableObject } from 'cloudflare:workers'; +import { connect, fetch, MinecraftWorld as World } from '../target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js'; + +export default { connect, fetch }; + +// The runtime enables RPC on a Durable Object class only when it derives from +// DurableObject; the wasm-bindgen class is plain, so wrap it. +export class MinecraftWorld extends DurableObject { + #world; + constructor(state, env) { + super(state, env); + this.#world = new World(state, env); + } + connect(socket) { return this.#world.connect(socket); } + status() { return this.#world.status(); } +} diff --git a/worker/index.ts b/worker/index.ts deleted file mode 100644 index e98fe11..0000000 --- a/worker/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { exports } from 'cloudflare:workers'; -import { PORT } from './pumpkin'; -import { forwardSocket } from './socket'; -export { MinecraftWorld } from './world'; - -export default { - async connect(socket: Socket, env: Env): Promise { - const target = exports.MinecraftWorld.getByName(env.WORLD_NAME).connect(`world:${PORT}`, { allowHalfOpen: true }); - await forwardSocket(socket, target); - }, - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); - if (url.pathname === '/health') return Response.json({ ready: true }); - const stub = exports.MinecraftWorld.getByName(env.WORLD_NAME); - if (request.method === 'GET' && url.pathname === '/') return Response.json(await stub.status()); - return new Response('Not found', { status: 404 }); - }, -} satisfies ExportedHandler; diff --git a/worker/modules.d.ts b/worker/modules.d.ts deleted file mode 100644 index 3238267..0000000 --- a/worker/modules.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -declare module '*.wasm' { - const module: WebAssembly.Module; - export default module; -} - -declare module '*pumpkin-do.js' { - interface Options { - cwd: string; - nodeFs: typeof import('worker-fs-mount/fs-sync'); - instantiateWasm(imports: WebAssembly.Imports, receive: (instance: WebAssembly.Instance) => void): WebAssembly.Exports; - printErr(message: string): void; - } - interface Pumpkin { - pumpkin_run(ready: () => void): Promise; - pumpkin_stop(): void; - pumpkin_status(): { players: number; ticks: number; wasm_memory_bytes: number }; - } - export default function create(options: Options): Promise; -} - -// Routes an inbound socket to the net.Server listening on its local address's -// port within the current Durable Object's port table; resolves when the -// connection closes. -declare module 'cloudflare:node' { - export function handleAsNodeConnection(socket: Socket): Promise; -} diff --git a/worker/pumpkin.ts b/worker/pumpkin.ts deleted file mode 100644 index 9928a22..0000000 --- a/worker/pumpkin.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { handleAsNodeConnection } from 'cloudflare:node'; -import createPumpkin from '../target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js'; -import pumpkinWasm from '../target/workers/wasm32-unknown-emscripten/release/pumpkin_do.wasm'; -import { createWorldRuntime } from './filesystem'; - -export const PORT = 25565; - -/** Start the server in its instance's filesystem context and resolve once it is listening. */ -export async function startPumpkin(storage: DurableObjectStorage) { - const { instance, run } = await createWorldRuntime(storage, options => createPumpkin({ - ...options, - instantiateWasm(imports, receive) { - const instance = new WebAssembly.Instance(pumpkinWasm, imports); - receive(instance); - return instance.exports; - }, - printErr: console.error, - })); - // The single JSPI activation runs the whole server lifetime, suspending - // whenever Tokio parks. It settles after `stop` completes the final save, or - // rejects if the server fails. - let finished!: Promise; - await new Promise((ready, reject) => { - finished = run(() => instance.pumpkin_run(ready)); - finished.catch(reject); - }); - return { - finished, - connect: (socket: Socket) => handleAsNodeConnection(socket), - stop: () => { instance.pumpkin_stop(); return finished; }, - status: () => instance.pumpkin_status(), - }; -} diff --git a/worker/socket.ts b/worker/socket.ts deleted file mode 100644 index 47f9d3b..0000000 --- a/worker/socket.ts +++ /dev/null @@ -1,25 +0,0 @@ -export async function forwardSocket(socket: Socket, target: Socket): Promise { - // Read/write failures are observed by the pumps below. Also consume the - // lifetime promises so a normal peer disconnect is not an unhandled rejection. - void socket.closed.catch(() => undefined); - void target.closed.catch(() => undefined); - const abort = new AbortController(); - const pipe = (source: ReadableStream, destination: WritableStream) => - source.pipeTo(destination, { signal: abort.signal }).catch(error => { - abort.abort(); - throw error; - }); - try { - const results = await Promise.allSettled([ - pipe(socket.readable, target.writable), - pipe(target.readable, socket.writable), - ]); - for (const result of results) { - if (result.status === 'rejected' && !/closed|closing|abort|cancel|reset|network connection lost/i.test(String(result.reason))) { - console.error('TCP forwarding failed', result.reason); - } - } - } finally { - await Promise.allSettled([socket.close(), target.close()]); - } -} diff --git a/worker/world.ts b/worker/world.ts deleted file mode 100644 index 75d7bca..0000000 --- a/worker/world.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { DurableObject } from 'cloudflare:workers'; -import { startPumpkin } from './pumpkin'; - -type Runtime = Awaited>; -type State = - | { phase: 'idle' } - | { phase: 'starting'; pending: Promise } - | { phase: 'running'; runtime: Runtime } - | { phase: 'stopping'; pending: Promise } - | { phase: 'failed'; error: string }; - -export class MinecraftWorld extends DurableObject { - private state: State = { phase: 'idle' }; - private connections = 0; - private startupMilliseconds?: number; - private checkpointedAt?: string; - - protected loadRuntime(): Promise { - return startPumpkin(this.ctx.storage); - } - - private fail(error: unknown): void { - if (this.state.phase !== 'failed') { - this.state = { phase: 'failed', error: error instanceof Error ? error.message : String(error) }; - } - } - - protected async getRuntime(): Promise { - const state = this.state; - switch (state.phase) { - case 'running': return state.runtime; - case 'starting': return state.pending; - case 'failed': throw new Error(state.error); - case 'stopping': - await state.pending; - return this.getRuntime(); - case 'idle': { - const started = Date.now(); - const pending = Promise.resolve().then(() => this.loadRuntime()).then(runtime => { - this.startupMilliseconds = Date.now() - started; - this.state = { phase: 'running', runtime }; - runtime.finished.catch(error => this.fail(error)); - return runtime; - }).catch(error => { this.fail(error); throw error; }); - this.state = { phase: 'starting', pending }; - return pending; - } - } - } - - async connect(socket: Socket): Promise { - void socket.closed.catch(() => undefined); - if (this.state.phase === 'stopping') await this.state.pending; - this.connections++; - try { - await (await this.getRuntime()).connect(socket); - } finally { - this.connections--; - await socket.close().catch(() => undefined); - if (this.connections === 0) await this.checkpoint(); - } - } - - private async checkpoint(): Promise { - const state = this.state; - if (state.phase === 'stopping') return state.pending; - if (state.phase !== 'running') return; - const pending = (async () => { - await state.runtime.stop(); - await this.ctx.storage.sync(); - this.checkpointedAt = new Date().toISOString(); - this.state = { phase: 'idle' }; - })().catch(error => { this.fail(error); throw error; }); - this.state = { phase: 'stopping', pending }; - return pending; - } - - status() { - let server: ReturnType | null = null; - if (this.state.phase === 'running') { - try { server = this.state.runtime.status(); } - catch (error) { this.fail(error); } - } - return { - phase: this.state.phase, - connections: this.connections, - failure: this.state.phase === 'failed' ? this.state.error : null, - startup_ms: this.startupMilliseconds ?? null, - checkpointed_at: this.checkpointedAt ?? null, - server, - }; - } -} diff --git a/wrangler.jsonc b/wrangler.jsonc index 7fb22ab..3bf5f27 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,12 +1,13 @@ { "$schema": "node_modules/wrangler/config-schema.json", "name": "rust-workers-minecraft", - "main": "worker/index.ts", + "main": "worker/index.mjs", "compatibility_date": "2026-09-05", "compatibility_flags": ["nodejs_compat", "new_module_registry", "no_handle_cross_request_promise_resolution"], "dev": { "ip": "127.0.0.1", "port": 8787 }, "connect": [{ "protocol": "tcp", "address": "127.0.0.1", "port": 25565 }], - "exports": { "MinecraftWorld": { "type": "durable-object", "storage": "sqlite" } }, + "durable_objects": { "bindings": [{ "name": "WORLD", "class_name": "MinecraftWorld" }] }, + "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MinecraftWorld"] }], "vars": { "WORLD_NAME": "world" }, "observability": { "enabled": true, "head_sampling_rate": 1 } } From 6600809b240e3afe1e775368cf41b8219498d35c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 16 Sep 2026 12:01:41 -0700 Subject: [PATCH 4/6] Build with the worker crate and worker-build --emscripten --tokio The Worker now uses workers-rs (cloudflare/workers-rs#1061): the `worker` crate for the fetch handler, Env and Durable Object plumbing, and `worker-build --emscripten --tokio` to link the bin through emcc, wrap the exports into the entrypoint and DurableObject-derived class, and emit the module Wrangler serves. The JS shim, the factory link arguments in .cargo/config.toml and the wasm-bindgen CLI plumbing in the scripts go away; build.rs keeps only the application's own settings. The Durable Object no longer owns a runtime. `connect` is a `#[wasm_bindgen(tokio)]` export (wasm-bindgen/wasm-bindgen#5334) whose future runs on the thread's Tokio `LocalEventLoop` (tokio-rs/tokio#8484), the host event loop driving the scheduler. The server lifetime is spawned as a task so a panic arrives as a JoinError and fails the object. Pins move to the event-loop stack: tokio `emscripten-event-loop-host`, mio `emscripten`, workers-rs `gbedford/worker-build-emscripten` with its wasm-bindgen submodule supplying every wasm-bindgen crate, and emscripten `cf-final` with the emsdk release its main is paired with. Binaryen comes from that emsdk, so the separate Binaryen checkout and build are dropped. --- .cargo/config.toml | 23 ++---- .gitignore | 1 + AGENTS.md | 3 +- Cargo.lock | 173 +++++++++++++++++++++++++++++++++++++++---- Cargo.toml | 21 +++++- README.md | 19 ++--- build.rs | 4 + docs/architecture.md | 50 +++++++------ docs/dependencies.md | 55 ++++++++------ docs/development.md | 13 ++-- scripts/build.sh | 2 +- scripts/common.sh | 25 ++++--- scripts/setup.sh | 61 ++++++++------- src/main.rs | 52 ++++++------- src/world.rs | 90 +++++++++------------- worker/index.mjs | 16 ---- wrangler.jsonc | 3 +- 17 files changed, 362 insertions(+), 249 deletions(-) delete mode 100644 worker/index.mjs diff --git a/.cargo/config.toml b/.cargo/config.toml index 8afc927..75199fd 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,26 +2,13 @@ target = "wasm32-unknown-emscripten" target-dir = "target/workers" +# worker-build --emscripten --tokio supplies these (and the link settings) +# through RUSTFLAGS when building; they are repeated here so `cargo check` +# sees the same cfgs. [target.wasm32-unknown-emscripten] rustflags = [ "-Crelocation-model=static", - "--cfg=tokio_unstable", - # exnref exception handling throughout: the C side gets `-fwasm-exceptions - # -sWASM_LEGACY_EXCEPTIONS=0` from EMCC_CFLAGS (scripts/common.sh) and the - # prebuilt std is translated at link. V8 rejects a module mixing the two. "-Cllvm-args=-wasm-use-legacy-eh=false", - "-Clink-arg=-sBINARYEN_EXTRA_PASSES=--translate-to-exnref", - # Link-only settings stay out of EMCC_CFLAGS so they do not reach C compiles. - "-Clink-arg=-sWASM_BINDGEN", - "-Clink-arg=-sMODULARIZE=instance", - "-Clink-arg=-sEXPORT_ES6", - "-Clink-arg=-sAUTO_INIT", - "-Clink-arg=-sSOURCE_PHASE_IMPORTS", - # Generated host glue uses Workers' Node compatibility APIs. - "-Clink-arg=-sENVIRONMENT=node", - "-Clink-arg=-sNODERAWSOCKETS", - "-Clink-arg=-sNODERAWFS", - "-Clink-arg=-sASSERTIONS=0", - "-Clink-arg=-sSTACK_SIZE=8MB", - "-Clink-arg=-sALLOW_MEMORY_GROWTH=1", + "--cfg=tokio_unstable", + "--cfg=worker_tokio=\"event_loop\"", ] diff --git a/.gitignore b/.gitignore index 4a53fb4..cca1d50 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /.work/ /target/ **/target/ +/build/ node_modules/ .wrangler/ /worker/env.d.ts diff --git a/AGENTS.md b/AGENTS.md index 5cbf3f8..95d7336 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,7 +7,6 @@ Read [README.md](README.md), [architecture](docs/architecture.md), and - `src/`: the Worker: TCP ingress, the Durable Object, world persistence, Pumpkin configuration, and the Emscripten JS library (`workerd.js`). -- `worker/index.mjs`: the module Wrangler loads; re-exports the generated build. - `tests/`: protocol clients and the integration test. - `scripts/setup.sh`: provision pinned sources and build the toolchain. - `scripts/{build,serve,test}.sh`: build, run, and validate the Workers server. @@ -22,7 +21,7 @@ process to free a port. Changes to the patched Pumpkin checkout must be reflected in `patches/`. Keep unpatched checkouts unmodified; update pins for upstream changes. Preserve the unified ticker and cooperative scheduler unless -the task requires a runtime change. Keep the `EventLoopRuntime` model: exports +the task requires a runtime change. Keep the event-loop model: exports return promises and nothing blocks or suspends; do not reintroduce JSPI, old Tokio/libc networking patches, or a JS-side driver. diff --git a/Cargo.lock b/Cargo.lock index 7cc9763..58057bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -204,6 +204,17 @@ dependencies = [ "rand_core", ] +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "js-sys", + "num-traits", + "wasm-bindgen", +] + [[package]] name = "cipher" version = "0.5.2" @@ -1187,8 +1198,6 @@ dependencies = [ [[package]] name = "js-sys" version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" dependencies = [ "cfg-if", "futures-util", @@ -1252,6 +1261,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "md-5" version = "0.10.6" @@ -1296,8 +1311,8 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.2" -source = "git+https://github.com/guybedford/mio?rev=a62c9e46833fc255c9217ab9aa362c6221ed4401#a62c9e46833fc255c9217ab9aa362c6221ed4401" +version = "1.2.3" +source = "git+https://github.com/guybedford/mio?branch=emscripten#0788dbb67883cb6191503af999471def54e3fb77" dependencies = [ "libc", "wasi", @@ -1483,6 +1498,26 @@ dependencies = [ "siphasher", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1812,7 +1847,9 @@ dependencies = [ "rayon", "tokio", "wasm-bindgen", + "wasm-bindgen-futures", "web-sys", + "worker", ] [[package]] @@ -2121,6 +2158,12 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a252f5e20f038fe7b4ea53e073e65398d652c864cc162fc77c56c2f13717b888" +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + [[package]] name = "same-file" version = "1.0.6" @@ -2198,6 +2241,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -2251,6 +2305,18 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serdect" version = "0.4.3" @@ -2404,6 +2470,27 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strum" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "subtle" version = "2.6.1" @@ -2812,8 +2899,6 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" dependencies = [ "cfg-if", "once_cell", @@ -2825,18 +2910,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" version = "0.4.78" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" dependencies = [ "js-sys", + "tokio", "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2845,8 +2927,6 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" dependencies = [ "bumpalo", "proc-macro2", @@ -2858,17 +2938,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" version = "0.2.128" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-streams" +version = "0.6.0" +source = "git+https://github.com/guybedford/wasm-streams?branch=rlib-only#073b6497be60d7e91fc5b1f23f24c825296b4c88" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "web-sys" version = "0.3.105" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" dependencies = [ "js-sys", "wasm-bindgen", @@ -2925,6 +3013,59 @@ dependencies = [ "primefield", ] +[[package]] +name = "worker" +version = "0.8.6" +dependencies = [ + "async-trait", + "bytes", + "chrono", + "futures-channel", + "futures-util", + "http", + "http-body", + "js-sys", + "matchit", + "pin-project", + "serde", + "serde-wasm-bindgen", + "serde_json", + "serde_urlencoded", + "strum", + "tokio", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "worker-macros", + "worker-sys", +] + +[[package]] +name = "worker-macros" +version = "0.8.6" +dependencies = [ + "async-trait", + "proc-macro2", + "quote", + "strum", + "syn 3.0.5", + "wasm-bindgen", + "wasm-bindgen-macro-support", + "worker-sys", +] + +[[package]] +name = "worker-sys" +version = "0.8.6" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "writeable" version = "0.6.4" diff --git a/Cargo.toml b/Cargo.toml index 3c53cd0..ca12b30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,9 +17,11 @@ name = "pumpkin-do" path = "src/main.rs" [dependencies] +worker = { path = ".work/workers-rs/worker" } wasm-bindgen = "0.2.128" +wasm-bindgen-futures = { version = "0.4.78", features = ["tokio"] } js-sys = "0.3.105" -web-sys = { version = "0.3.105", features = ["AbortController", "AbortSignal", "Headers", "ReadableStream", "Request", "Response", "ResponseInit", "StreamPipeOptions", "Url", "WritableStream", "console"] } +web-sys = { version = "0.3.105", features = ["AbortController", "AbortSignal", "ReadableStream", "StreamPipeOptions", "WritableStream", "console"] } tokio = { version = "1", default-features = false, features = ["rt", "macros", "sync", "time", "net", "io-util"] } pumpkin = { path = ".work/pumpkin/crates/pumpkin", default-features = false, features = ["single-threaded"] } pumpkin-config = { path = ".work/pumpkin/crates/pumpkin-config", default-features = false } @@ -27,18 +29,31 @@ rayon = "1.12" # wasm32-unknown-emscripten support pending upstream releases. [patch.crates-io] -# EventLoopRuntime: the host event loop drives Tokio (tokio-rs/tokio#8479). +# LocalEventLoop: the host event loop drives Tokio (tokio-rs/tokio#8484). tokio = { path = ".work/tokio/tokio" } tokio-macros = { path = ".work/tokio/tokio-macros" } # tokio-rs/mio#1969; its emscripten epoll bindings come from libc's unreleased libc-0.2 branch. -mio = { git = "https://github.com/guybedford/mio", rev = "a62c9e46833fc255c9217ab9aa362c6221ed4401" } +mio = { git = "https://github.com/guybedford/mio", branch = "emscripten" } libc = { git = "https://github.com/rust-lang/libc", branch = "libc-0.2" } # getrandom-backed SystemRandom on emscripten. ring = { git = "https://github.com/guybedford/ring", branch = "emscripten" } +# rlib-only: cargo would otherwise also link its cdylib, which emcc cannot produce. +wasm-streams = { git = "https://github.com/guybedford/wasm-streams", branch = "rlib-only" } +# `#[wasm_bindgen(tokio)]` exports on the event loop (wasm-bindgen/wasm-bindgen#5334), +# from the workers-rs checkout so the worker crate shares one wasm-bindgen. +wasm-bindgen = { path = ".work/workers-rs/wasm-bindgen" } +wasm-bindgen-macro-support = { path = ".work/workers-rs/wasm-bindgen/crates/macro-support" } +wasm-bindgen-shared = { path = ".work/workers-rs/wasm-bindgen/crates/shared" } +wasm-bindgen-futures = { path = ".work/workers-rs/wasm-bindgen/crates/futures" } +js-sys = { path = ".work/workers-rs/wasm-bindgen/crates/js-sys" } +web-sys = { path = ".work/workers-rs/wasm-bindgen/crates/web-sys" } # mio's own libc source, unified with the crates.io users above. [patch."https://github.com/guybedford/libc"] libc = { git = "https://github.com/rust-lang/libc", branch = "libc-0.2" } +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(worker_tokio, values("event_loop", "jspi"))'] } + [profile.release] opt-level = "s" diff --git a/README.md b/README.md index f64bae2..1cb986b 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,7 @@ Minecraft → Workers TCP ingress → MinecraftWorld → Pumpkin + SQLite ## Try it Linux and macOS build hosts are supported. Install Git, -[rustup](https://rustup.rs/), Python 3.11+, CMake and Ninja, and Node 24+ (26 -recommended). From a checkout of this repository: +[rustup](https://rustup.rs/), Python 3.11+, and Node 24+ (26 recommended). From a checkout of this repository: ```sh npm ci @@ -24,14 +23,15 @@ npm run dev ``` Setup installs the pinned Rust toolchain, fetches dependency sources and the -Emscripten toolchain under `.work/` (building Binaryen), and installs the -wasm-bindgen CLI. `npm run dev` compiles Pumpkin and starts Wrangler. The first -build takes several minutes; later builds use Cargo's cache. +Emscripten toolchain under `.work/`, and builds the +worker-build and wasm-bindgen CLIs. `npm run dev` compiles Pumpkin with +`worker-build --emscripten --tokio` and starts Wrangler. The first build takes +several minutes; later builds use Cargo's cache. -Until Wrangler's bundled workerd routes inbound sockets to `net.Server` listeners -inside Durable Objects and carries the `node:fs` fixes listed in -[dependency pins](docs/dependencies.md#host-tools), point Miniflare at a workerd -build that does: `MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. +The runtime support this needs (`net.Server` routing into Durable Objects and +the `node:fs` fixes listed in [dependency pins](docs/dependencies.md#host-tools)) +is in workerd main. Until Wrangler's bundled workerd catches up, point Miniflare +at a build of main: `MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. Connect **Minecraft Java 26.2** to **`localhost:25565`**. Status is available at **http://localhost:8787/**. @@ -76,6 +76,7 @@ Provision the public TCP endpoint separately and route it to this Worker's ## Credits and license Built on [Pumpkin](https://github.com/Pumpkin-MC/Pumpkin), +[workers-rs](https://github.com/cloudflare/workers-rs), [Tokio](https://github.com/tokio-rs/tokio), [Emscripten](https://emscripten.org/), [wasm-bindgen](https://github.com/wasm-bindgen/wasm-bindgen), and [Guy Bedford's Rust/Emscripten work](https://github.com/guybedford). diff --git a/build.rs b/build.rs index 10f8c3d..3239053 100644 --- a/build.rs +++ b/build.rs @@ -1,11 +1,15 @@ use std::{env, path::PathBuf}; +// worker-build supplies the common emscripten link settings; these are the +// application's own. fn main() { println!("cargo::rerun-if-changed=src/workerd.js"); if env::var("CARGO_CFG_TARGET_OS").as_deref() != Ok("emscripten") { return; } for arg in [ + "-sNODERAWFS", + "-sSTACK_SIZE=8MB", // Keep growth headroom bounded after generation's temporary allocations. "-sMEMORY_GROWTH_LINEAR_STEP=2097152", "-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\"$workerdStdio\"]", diff --git a/docs/architecture.md b/docs/architecture.md index d43e7d6..d9087f1 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -2,26 +2,26 @@ ## Runtime and connections -The whole Worker is Rust. Cargo links the `pumpkin-do` bin for -`wasm32-unknown-emscripten` with emcc, which emits an ES module instance -(`-sMODULARIZE=instance`, `-sWASM_BINDGEN`) that `worker/index.mjs` re-exports -for Wrangler. The entrypoint's `connect` handler forwards each TCP connection to -the named `MinecraftWorld` object with `stub.connect` and pipes both directions; -`fetch` serves status. Both are plain exports composing the platform's promises. - -`MinecraftWorld` is a `#[wasm_bindgen]` class constructed with the object's state -and owning Tokio's `EventLoopRuntime` (tokio-rs/tokio#8479): the current-thread -scheduler and drivers with the host event loop as their wait. Nothing blocks or -suspends. Work is scheduled as roots that complete by callback, and each export -wraps its root in a Promise. The first `connect` after idle schedules the server -lifetime as a root: it restores the world, starts Pumpkin, awaits the server's -return after its final save, checkpoints, and settles the connection's promise. -Later connections and `status` calls enter the same instance as ordinary calls -while that root is parked. A connection arriving while the server is starting -or stopping waits for the next phase change and then retries, so a client that -connects as the previous server checkpoints starts the next one. The runtime's epoll and timer waits register with the -host through Emscripten's Node backend (emscripten-core/emscripten#27547), so a -readiness or timer callback resumes the scheduler on the host loop. +The whole Worker is Rust: `worker-build --emscripten --tokio` links the +`pumpkin-do` bin for `wasm32-unknown-emscripten` through emcc and emits the +module Wrangler serves. The entrypoint's `connect` handler forwards each TCP +connection to the named `MinecraftWorld` object with `stub.connect` and pipes +both directions; `fetch` (a `worker` crate `#[event(fetch)]`) serves status. + +`MinecraftWorld` is a `#[wasm_bindgen]` class constructed with the object's +state. Its `connect` is a `#[wasm_bindgen(tokio)]` export: the future runs on +the thread's Tokio `LocalEventLoop` (tokio-rs/tokio#8484), the current-thread +scheduler and drivers with the host event loop as their wait, and the export +returns the Promise of its outcome. Nothing blocks or suspends. The first +`connect` after idle runs the server lifetime: it restores the world, starts +Pumpkin, awaits the server's return after its final save, checkpoints, and +settles the connection's promise. Later connections and `status` calls enter +the same instance as ordinary calls while that future is parked. A connection +arriving while the server is starting or stopping waits for the next phase +change and then retries, so a client that connects as the previous server +checkpoints starts the next one. The runtime's epoll and timer waits register +with the host through Emscripten's Node backend (emscripten-core/emscripten#27547), +so a readiness or timer callback resumes the scheduler on the host loop. Pumpkin binds its stock `TcpListener` on port 25565. Emscripten's Node socket backend implements that with `net.BoundSocket`/`net.Server`, which workerd scopes @@ -81,11 +81,13 @@ panics, Rust error logs, and runtime crashes fail the integration test. ## Build constraints -- Use the pinned Rust toolchain, Emscripten, and wasm-bindgen CLI from setup. +- Use the pinned Rust toolchain, Emscripten, worker-build and wasm-bindgen CLI + from setup. - Keep static relocation, exnref exception handling on both the C and Rust - sides, the 8 MiB stack, and memory growth. `.cargo/config.toml` holds the link - settings; `build.rs` adds the JS library and growth step. -- Build with `--cfg tokio_unstable`; `EventLoopRuntime` is unstable API. + sides, the 8 MiB stack, and memory growth. worker-build supplies the common + codegen and link settings (including `--cfg tokio_unstable`; `LocalEventLoop` + is unstable API); `build.rs` adds the application's own: `NODERAWFS`, the + stack size, the growth step, and the JS library. - Prefix Rust exports to avoid collisions with libc and Emscripten symbols. [Dependency pins and patch maintenance](dependencies.md) diff --git a/docs/dependencies.md b/docs/dependencies.md index 95fe69f..173a00f 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -9,10 +9,10 @@ locked. | Component | Source / branch | Commit | Local changes or purpose | | --- | --- | --- | --- | | pumpkin | [master](https://github.com/Pumpkin-MC/Pumpkin) | `b5b9b9d7010e793806a83c495af223c67e1d35ee` | Headless embedding, shared async scheduler, restartable stop signal, compact templates/generation chunks, and build-script fixes | -| tokio | [emscripten-event-loop](https://github.com/guybedford/tokio) | `5315798b6a62a47d03dc40e7d04fbf83c80f3246` | `EventLoopRuntime` (tokio-rs/tokio#8479) and `net` over epoll on Emscripten; unmodified | -| emscripten | [cf-final](https://github.com/guybedford/emscripten) | `4e034c65a18c0034c68e7eb628477fe4428fa1f9` | Upstream main plus emscripten-core/emscripten#27547 (epoll listeners on the host loop), #27698, #27699, and pending fixes: side-module accept, exnref ordering, `fs.constants` in NODEFS; unmodified | -| binaryen | [jspi-hooks](https://github.com/guybedford/binaryen) | `d6483a04d7dab0ef83e5c43f87343061d97a3b8d` | The frontend pin expects this Binaryen; built by setup | -| emsdk | [main](https://github.com/emscripten-core/emsdk) | `5eb0bde7585670252e8ba05e9d361627bffd08b5` | LLVM from emscripten-releases build `e8579ea489b44a6792f5abf95377a6ee38a16cce`, paired with the frontend's main | +| tokio | [emscripten-event-loop-host](https://github.com/guybedford/tokio) | `067f92b60cbba5c9fea585806af4b07fd838427e` | `LocalEventLoop` (tokio-rs/tokio#8484) and `net` over epoll on Emscripten; unmodified. | +| workers-rs | [gbedford/worker-build-emscripten](https://github.com/cloudflare/workers-rs/pull/1061) | `f4546f5379a933fbf3607bce205a8b372811d465` | cloudflare/workers-rs#1061: the `worker` crate and `worker-build --emscripten --tokio`; its `wasm-bindgen` submodule ([gbedford/emscripten-stack](https://github.com/wasm-bindgen/wasm-bindgen) `d48e82c4b3f7a182223fb1b1c7b99f59cf9b7c17`) carries `#[wasm_bindgen(tokio)]` (wasm-bindgen/wasm-bindgen#5334) and supplies every wasm-bindgen crate; unmodified | +| emscripten | [cf-final](https://github.com/guybedford/emscripten) | `462303990a2f62e2c065dcc3ba0794cb7bb2e6c4` | Upstream main plus emscripten-core/emscripten#27547 (epoll listeners on the host loop), #27742 (async DNS lookup) and #27724 (blocking accept under pthreads); unmodified | +| emsdk | [main](https://github.com/emscripten-core/emsdk) | `5eb0bde7585670252e8ba05e9d361627bffd08b5` | LLVM, Binaryen and Node from emscripten-releases build `8324e94759a0292e342577007021b4b47106333b`, paired with the frontend's main | Patches are relative to the pinned commits above. Setup checks reverse application before applying a patch and refuses to repin a modified checkout. @@ -26,14 +26,15 @@ commit. | Crate | Source | Purpose | | --- | --- | --- | -| mio | https://github.com/guybedford/mio `a62c9e46833fc255c9217ab9aa362c6221ed4401` | Emscripten epoll selector (tokio-rs/mio#1969) | +| mio | https://github.com/guybedford/mio branch `emscripten` | Emscripten epoll selector (tokio-rs/mio#1969) | | libc | https://github.com/rust-lang/libc branch `libc-0.2` | Emscripten epoll bindings, unreleased | | ring | https://github.com/guybedford/ring branch `emscripten` | getrandom-backed `SystemRandom` on Emscripten | +| wasm-streams | https://github.com/guybedford/wasm-streams branch `rlib-only` | MattiasBuelens/wasm-streams#40; a `worker` dependency that must not also link a cdylib | -The root Cargo.toml applies these overrides and the local checkouts. Cargo.lock -pins the full application graph, including the branch commits. The Worker uses -`wasm-bindgen`, `js-sys`, and `web-sys` from crates.io directly; there is no -`worker` crate dependency. +The root Cargo.toml applies these overrides and the local checkouts, and +patches the wasm-bindgen crates to the workers-rs submodule so the `worker` +crate and the application share one wasm-bindgen. Cargo.lock pins the full +application graph, including the branch commits. ## Host tools @@ -41,20 +42,26 @@ pins the full application graph, including the branch commits. The Worker uses `wasm32-unknown-emscripten`; setup installs both through rustup. rustc needs a larger compile-thread stack for pumpkin-data's generated tables; the scripts set `RUST_MIN_STACK`. +- worker-build: built by setup from the workers-rs checkout into `.work/bin/`. + It drives cargo and emcc with the common link settings, wraps the exports + into the entrypoint and Durable Object classes, and emits `build/`. - wasm-bindgen CLI: the release matching the `wasm-bindgen` crate version in - Cargo.toml, installed by setup into `.work/bin/`. emcc runs it as a post-link - step under `-sWASM_BINDGEN`. + Cargo.toml, installed by setup into `.work/bin/`; worker-build is pointed at + it with `WASM_BINDGEN_BIN`. emcc runs it as a post-link step under + `-sWASM_BINDGEN`. - Node: 24+; 26 recommended and selected in CI. Used for build tools and tests. -- Python: 3.11+ (Emscripten scripts and TOML parsing). -- Emscripten backend: LLVM through emsdk under `.work/emsdk` (or an activated - emsdk selected with `EMSDK` during setup), and Binaryen built from the checkout - above with CMake and Ninja. Setup writes the frontend's `.emscripten_cf` config - pointing at both, and `scripts/common.sh` selects it with `EM_CONFIG`. -- workerd: Wrangler must run a workerd with per-Durable-Object port tables, - `net.Server` inbound routing (`handleAsNodeConnection`), and the `node:fs` - fixes for positional buffer I/O, `O_TRUNC`, `O_CREAT`, and rename over an - existing path. Until those ship in Wrangler's bundled version, set - `MINIFLARE_WORKERD_PATH`. +- Python: 3.11+ (Emscripten scripts, emsdk and TOML parsing). +- Emscripten backend: LLVM, Binaryen and Node through emsdk under `.work/emsdk` + (or an activated emsdk selected with `EMSDK`). `scripts/common.sh` hands the + frontend checkout and emsdk to worker-build through `EMSCRIPTEN` and `EMSDK`; + it uses the frontend unpatched. +- workerd: Wrangler must run a workerd with `net.Server` inbound routing into + Durable Objects (`handleAsNodeConnection`, cloudflare/workerd#7306, #7313) and + the `node:fs` fixes for positional buffer I/O (#7368), `O_TRUNC` (#7369), + `O_CREAT` (#7393), and rename over an existing path (#7394). All are in + workerd main; until Wrangler's bundled version catches up, build main + (`bazel build //src/workerd/server:workerd`) and set `MINIFLARE_WORKERD_PATH` + to `bazel-bin/src/workerd/server/workerd`. ## Updating a patch @@ -94,9 +101,9 @@ bash scripts/setup.sh --sources-only ## JavaScript dependencies -`package-lock.json` pins Wrangler 4.129.0. The only application JavaScript is -`worker/index.mjs`, which re-exports the generated module and derives the -Durable Object class from `DurableObject` for RPC. +`package-lock.json` pins Wrangler 4.129.0. There is no application JavaScript; +worker-build generates `build/index.js`, which wraps the exports into the +entrypoint and derives the Durable Object class from `DurableObject` for RPC. After moving a checkout with cached build output, run `cargo clean` before rebuilding. Generated data can contain absolute paths. This leaves databases under `.data/` intact. diff --git a/docs/development.md b/docs/development.md index 0bcd4c8..f972f56 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,13 +1,14 @@ # Development Follow the [README setup instructions](../README.md#try-it) to install the pinned -Rust toolchain, provision dependency sources and Emscripten, and install the -wasm-bindgen CLI. +Rust toolchain, provision dependency sources and Emscripten, and build the +worker-build and wasm-bindgen CLIs. The initial Pumpkin compilation takes several minutes. Later builds use Cargo's -cache. `.cargo/config.toml` owns the link settings; `build.rs` adds the JS library -(`src/workerd.js`) and tracks changes to it. The build output Wrangler serves is -`target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js`, re-exported by -`worker/index.mjs`. +cache. worker-build owns the common codegen and link settings; `build.rs` adds +the application's own and the JS library (`src/workerd.js`), tracking changes to +it. `.cargo/config.toml` repeats the codegen cfgs so `cargo check` sees them. +Wrangler runs `worker-build --emscripten --tokio --release` as its build command +and serves `build/index.js`. ```sh npm run build # Compile the production Worker diff --git a/scripts/build.sh b/scripts/build.sh index 52c7d3f..b55e92d 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -4,4 +4,4 @@ source "$(dirname "${BASH_SOURCE[0]}")/common.sh" require_node require_toolchain cd "$REPO" -cargo build --locked --release --bin pumpkin-do +worker-build --emscripten --tokio --release -- --locked diff --git a/scripts/common.sh b/scripts/common.sh index 8a7f652..5ed03a8 100755 --- a/scripts/common.sh +++ b/scripts/common.sh @@ -4,10 +4,11 @@ set -euo pipefail REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" WORK="$REPO/.work" NODE="${NODE:-node}" -# Setup provisions the frontend checkout, compiler backend config, and the -# wasm-bindgen CLI under .work/; WASM_BINDGEN_BIN may point at another CLI directory. +# Setup provisions the Emscripten frontend and backend and the worker-build and +# wasm-bindgen CLIs under .work/. worker-build reads the toolchain from +# EMSCRIPTEN, EMSDK and WASM_BINDGEN_BIN. EMSCRIPTEN="$WORK/emscripten" -WASM_BINDGEN_BIN="${WASM_BINDGEN_BIN:-$WORK/bin}" +BIN="$WORK/bin" require_node() { local major @@ -19,19 +20,19 @@ require_node() { } require_toolchain() { - if [ ! -f "$EMSCRIPTEN/.emscripten_cf" ] || - [ ! -x "$EMSCRIPTEN/emcc" ] || - [ ! -x "$WASM_BINDGEN_BIN/wasm-bindgen" ] || + if [ ! -x "$EMSCRIPTEN/emcc" ] || + [ ! -x "$BIN/worker-build" ] || + [ ! -x "$BIN/wasm-bindgen" ] || [ ! -f "$WORK/pumpkin/crates/pumpkin/Cargo.toml" ] || - [ ! -f "$WORK/tokio/tokio/Cargo.toml" ]; then + [ ! -f "$WORK/tokio/tokio/Cargo.toml" ] || + [ ! -f "$WORK/workers-rs/worker/Cargo.toml" ]; then echo "error: run bash scripts/setup.sh first." >&2 exit 1 fi - export EM_CONFIG="$EMSCRIPTEN/.emscripten_cf" - export PATH="$EMSCRIPTEN:$WASM_BINDGEN_BIN:$PATH" - export CARGO_TARGET_WASM32_UNKNOWN_EMSCRIPTEN_LINKER="$EMSCRIPTEN/emcc" - # Uniform exnref exception handling for C and Rust objects (see .cargo/config.toml). - export EMCC_CFLAGS="${EMCC_CFLAGS:-} -fwasm-exceptions -sWASM_LEGACY_EXCEPTIONS=0" + export EMSCRIPTEN + export EMSDK="${EMSDK:-$WORK/emsdk}" + export WASM_BINDGEN_BIN="$BIN/wasm-bindgen" + export PATH="$BIN:$PATH" export CARGO_TARGET_DIR="$REPO/target/workers" # rustc's LLVM const emission recurses deeply on pumpkin-data's generated tables # and overflows its default 8 MiB compile-thread stack on current toolchains. diff --git a/scripts/setup.sh b/scripts/setup.sh index 42a158a..18dfbe3 100755 --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -44,7 +44,14 @@ apply_patch() { echo "==> Pinned dependencies" checkout pumpkin https://github.com/Pumpkin-MC/Pumpkin master b5b9b9d7010e793806a83c495af223c67e1d35ee -checkout tokio https://github.com/guybedford/tokio emscripten-event-loop 5315798b6a62a47d03dc40e7d04fbf83c80f3246 +checkout tokio https://github.com/guybedford/tokio emscripten-event-loop-host 067f92b60cbba5c9fea585806af4b07fd838427e +# cloudflare/workers-rs#1061; its wasm-bindgen submodule (wasm-bindgen/wasm-bindgen +# gbedford/emscripten-stack) supplies every wasm-bindgen crate. +checkout workers-rs https://github.com/cloudflare/workers-rs gbedford/worker-build-emscripten f4546f5379a933fbf3607bce205a8b372811d465 +WASM_BINDGEN_SUBMODULE=d48e82c4b3f7a182223fb1b1c7b99f59cf9b7c17 +if [ "$(git -C "$WORK/workers-rs/wasm-bindgen" rev-parse HEAD 2>/dev/null)" != "$WASM_BINDGEN_SUBMODULE" ]; then + git -C "$WORK/workers-rs" submodule update --init wasm-bindgen +fi apply_patch pumpkin pumpkin-emscripten.patch apply_patch pumpkin pumpkin-memory.patch @@ -56,47 +63,37 @@ rustup toolchain install "$RUST_CHANNEL" --profile minimal --target wasm32-unkno echo "==> wasm-bindgen CLI" WASM_BINDGEN_VERSION="$(python3 -c 'import tomllib,sys; print(tomllib.load(open(sys.argv[1],"rb"))["dependencies"]["wasm-bindgen"])' "$REPO/Cargo.toml")" -if [ "$("$WASM_BINDGEN_BIN/wasm-bindgen" --version 2>/dev/null || true)" != "wasm-bindgen $WASM_BINDGEN_VERSION" ]; then - cargo "+$RUST_CHANNEL" install --force --locked wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --root "$(dirname "$WASM_BINDGEN_BIN")" +if [ "$("$BIN/wasm-bindgen" --version 2>/dev/null || true)" != "wasm-bindgen $WASM_BINDGEN_VERSION" ]; then + cargo "+$RUST_CHANNEL" install --force --locked wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --root "$WORK" +fi + +echo "==> worker-build" +# Built from the workers-rs checkout (cloudflare/workers-rs#1061) for the host, +# overriding the wasm target .cargo/config.toml sets for the package path. +if [ "$(cat "$BIN/.worker-build-rev" 2>/dev/null)" != "$(git -C "$WORK/workers-rs" rev-parse HEAD)" ]; then + HOST="$(rustc "+$RUST_CHANNEL" -vV | sed -n 's/^host: //p')" + cargo "+$RUST_CHANNEL" install --force --locked --target "$HOST" --path "$WORK/workers-rs/worker-build" --root "$WORK" + git -C "$WORK/workers-rs" rev-parse HEAD > "$BIN/.worker-build-rev" fi echo "==> Emscripten" -# Frontend: upstream main plus the pending JSPI hooks, reentrant JSPI, and epoll -# listener PRs. LLVM comes from the emscripten-releases build paired with that -# main, and Binaryen from the branch carrying the jspi-hooks pass the frontend -# needs; setup builds it. EMSDK selects an activated emsdk for LLVM instead. -EMSDK_RELEASE=e8579ea489b44a6792f5abf95377a6ee38a16cce -checkout emscripten https://github.com/guybedford/emscripten cf-final 4e034c65a18c0034c68e7eb628477fe4428fa1f9 +# Frontend: upstream main plus the pending epoll listener PR. The backend (LLVM, Binaryen, Node) is the emscripten-releases build +# the frontend's main is paired with, installed through emsdk; EMSDK selects an +# activated emsdk instead. worker-build reads both from EMSCRIPTEN and EMSDK +# (scripts/common.sh) and uses the frontend unpatched. +EMSDK_RELEASE=8324e94759a0292e342577007021b4b47106333b +checkout emscripten https://github.com/guybedford/emscripten cf-final 462303990a2f62e2c065dcc3ba0794cb7bb2e6c4 NODE_PATH="$("$NODE" -p 'process.execPath')" (cd "$EMSCRIPTEN" && PATH="$(dirname "$NODE_PATH"):$PATH" npm ci --no-audit --no-fund && python3 bootstrap.py) -if [ -n "${EMSDK:-}" ]; then - LLVM_ROOT="$EMSDK/upstream/bin" -else +if [ -z "${EMSDK:-}" ]; then checkout emsdk https://github.com/emscripten-core/emsdk main 5eb0bde7585670252e8ba05e9d361627bffd08b5 if [ "$(cat "$WORK/emsdk/upstream/.emsdk_version" 2>/dev/null)" != "releases-$EMSDK_RELEASE-64bit" ]; then (cd "$WORK/emsdk" && ./emsdk install "$EMSDK_RELEASE" && ./emsdk activate "$EMSDK_RELEASE") fi - LLVM_ROOT="$WORK/emsdk/upstream/bin" -fi -checkout binaryen https://github.com/guybedford/binaryen jspi-hooks d6483a04d7dab0ef83e5c43f87343061d97a3b8d -BINARYEN_ROOT="$WORK/binaryen/build" -if [ ! -x "$BINARYEN_ROOT/bin/wasm-opt" ] || [ "$(cat "$BINARYEN_ROOT/.pinned" 2>/dev/null)" != "$(git -C "$WORK/binaryen" rev-parse HEAD)" ]; then - cmake -S "$WORK/binaryen" -B "$BINARYEN_ROOT" -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=OFF >/dev/null - cmake --build "$BINARYEN_ROOT" --target wasm-opt wasm-metadce wasm-emscripten-finalize wasm-split wasm-as wasm-dis wasm2js wasm-merge >/dev/null - git -C "$WORK/binaryen" rev-parse HEAD > "$BINARYEN_ROOT/.pinned" + EMSDK="$WORK/emsdk" fi -if [ ! -x "$LLVM_ROOT/clang" ] || [ ! -x "$BINARYEN_ROOT/bin/wasm-opt" ]; then - echo "error: no Emscripten backend at LLVM_ROOT=$LLVM_ROOT BINARYEN_ROOT=$BINARYEN_ROOT." >&2 +if [ ! -x "$EMSDK/upstream/bin/clang" ] || [ ! -x "$EMSDK/upstream/bin/wasm-opt" ]; then + echo "error: no Emscripten backend at EMSDK=$EMSDK." >&2 exit 1 fi -python3 - "$EMSCRIPTEN/.emscripten_cf" "$LLVM_ROOT" "$BINARYEN_ROOT" "$NODE_PATH" <<'PY' -from pathlib import Path -import sys -config, llvm, binaryen, node = sys.argv[1:] -Path(config).write_text( - f"LLVM_ROOT={llvm!r}\n" - f"BINARYEN_ROOT={binaryen!r}\n" - f"NODE_JS={node!r}\n" -) -PY echo "Setup complete. Run: bash scripts/test.sh or bash scripts/serve.sh" diff --git a/src/main.rs b/src/main.rs index a4114cd..9460aeb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,18 +6,20 @@ mod world; use host::{method, property, then}; use wasm_bindgen::prelude::*; +use worker::{event, Context, Env, Request, Response}; fn main() {} -fn stub(env: &JsValue) -> Result { - let name = property(env, "WORLD_NAME")?; - let namespace = property(env, "WORLD")?; - method(&namespace, "getByName", &[&name]) +/// The world's Durable Object stub. +fn stub(env: &Env) -> worker::Result { + let name = env.var("WORLD_NAME")?.to_string(); + Ok(env.durable_object("WORLD")?.get_by_name(&name)?.into_rpc()) } -/// Pipes an inbound TCP connection to the world's object until either side closes. +/// Pipes an inbound TCP connection to the world's object until either side +/// closes. worker-build exports it as the entrypoint's `connect` handler. #[wasm_bindgen] -pub fn connect(socket: JsValue, env: JsValue, _ctx: JsValue) -> Result { +pub fn connect(socket: JsValue, env: Env, _ctx: JsValue) -> Result { let target = host::stub_connect(&stub(&env)?, &world::authority())?; // Read/write failures are observed by the pumps; a normal peer disconnect // must not surface as an unhandled rejection of the lifetime promise. @@ -63,30 +65,22 @@ pub fn connect(socket: JsValue, env: JsValue, _ctx: JsValue) -> Result Result { - let init = web_sys::ResponseInit::new(); - init.set_status(status); - let headers = web_sys::Headers::new()?; - headers.set("content-type", "application/json")?; - init.set_headers(&headers); - let body = js_sys::JSON::stringify(value)?; - Ok(web_sys::Response::new_with_opt_str_and_init(body.as_string().as_deref(), &init)?.into()) -} - -#[wasm_bindgen] -pub fn fetch(request: web_sys::Request, env: JsValue, _ctx: JsValue) -> Result { - let url = web_sys::Url::new(&request.url())?; - match url.pathname().as_str() { - "/health" => { - let body = js_sys::Object::new(); - js_sys::Reflect::set(&body, &"ready".into(), &JsValue::TRUE)?; - json_response(&body, 200) - } - "/" if request.method() == "GET" => { +#[event(fetch)] +async fn fetch(request: Request, env: Env, _ctx: Context) -> worker::Result { + match request.path().as_str() { + "/health" => Response::ok(r#"{"ready":true}"#).map(json), + "/" if request.method() == worker::Method::Get => { let status = method(&stub(&env)?, "status", &[])?; - let respond = Closure::once_into_js(|value: JsValue| json_response(&value, 200)); - then(&status, Some(&respond), None) + let status = wasm_bindgen_futures::JsFuture::from(js_sys::Promise::from(status)).await?; + let body = js_sys::JSON::stringify(&status)?; + Response::ok(String::from(body)).map(json) } - _ => json_response(&"Not found".into(), 404), + _ => Response::error("Not found", 404), } } + +fn json(response: Response) -> Response { + let headers = worker::Headers::new(); + let _ = headers.set("content-type", "application/json"); + response.with_headers(headers) +} diff --git a/src/world.rs b/src/world.rs index 6215c00..e69efba 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,11 +1,11 @@ //! The Durable Object hosting one Pumpkin server. //! -//! The object owns an `EventLoopRuntime`: Tokio's current-thread scheduler and -//! drivers with the host event loop as its wait. Work is scheduled as roots -//! that complete by callback, so every export returns a Promise and nothing -//! blocks or suspends. The server lifetime is one such root: the first -//! `connect` after idle schedules it, and its completion (after the final save) -//! is the checkpoint. Later connections are routed to the running listener. +//! `connect` is a `#[wasm_bindgen(tokio)]` export: its future runs on the +//! thread's Tokio event loop, whose wait is the host event loop, so nothing +//! blocks or suspends and the export returns a Promise. The server lifetime is +//! one such future: the first `connect` after idle runs it, and its completion +//! (after the final save) is the checkpoint. Later connections are routed to +//! the running listener. use crate::{config, host, memory, persist}; use host::{js_error, method, property, then}; @@ -15,8 +15,8 @@ use std::{ rc::Rc, sync::{atomic::Ordering, Arc}, }; -use tokio::runtime::EventLoopRuntime; use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::JsFuture; pub const PORT: u16 = 25565; @@ -48,11 +48,10 @@ thread_local! { static LOGGER: Cell = const { Cell::new(false) }; } -/// State shared between the exports and the scheduled roots. +/// State shared between the exports and the server future. struct Shared { /// `ctx.storage` storage: JsValue, - runtime: EventLoopRuntime, phase: Cell, failure: RefCell>, connections: Cell, @@ -66,7 +65,7 @@ struct Shared { } // Closures run under catch_unwind; the object is only ever touched from this -// thread through the runtime's calls, so a poisoned borrow cannot be observed. +// thread, so a poisoned borrow cannot be observed. impl std::panic::RefUnwindSafe for Shared {} #[wasm_bindgen] @@ -86,14 +85,9 @@ impl MinecraftWorld { })); let storage = property(&state, "storage")?; persist::prepare(&storage)?; - let runtime = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build_event_loop_runtime() - .map_err(js_error)?; Ok(MinecraftWorld { shared: Rc::new(Shared { storage, - runtime, phase: Cell::new(Phase::Idle), failure: RefCell::new(None), connections: Cell::new(0), @@ -107,8 +101,10 @@ impl MinecraftWorld { /// Serves one inbound connection; resolves when it closes. The first call /// while idle also runs the server, resolving once the world is checkpointed. - pub fn connect(&self, socket: JsValue) -> Result { - self.shared.connect(socket) + #[wasm_bindgen(tokio)] + pub async fn connect(&self, socket: JsValue) -> Result { + let shared = self.shared.clone(); + shared.connect(socket).await } pub fn status(&self) -> Result { @@ -139,23 +135,31 @@ impl MinecraftWorld { } impl Shared { - fn connect(self: &Rc, socket: JsValue) -> Result { - match self.phase.get() { - Phase::Running => return self.route(socket), - Phase::Failed => { - return Err(js_error(self.failure.borrow().as_deref().unwrap_or("failed"))) - } - Phase::Starting | Phase::Stopping => { - let shared = self.clone(); - let retry = Closure::once_into_js(move |_: JsValue| shared.connect(socket)); - return then(&self.transition()?.into(), Some(&retry), None); + async fn connect(self: Rc, socket: JsValue) -> Result { + loop { + match self.phase.get() { + Phase::Running => return self.route(socket), + Phase::Failed => { + return Err(js_error(self.failure.borrow().as_deref().unwrap_or("failed"))) + } + Phase::Starting | Phase::Stopping => { + JsFuture::from(self.transition()?).await?; + } + Phase::Idle => break, } - Phase::Idle => {} } self.set_phase(Phase::Starting); let started = js_sys::Date::now(); - let root = self.clone(); - self.promise(async move { root.run(socket, started).await }) + // A task of its own so a panic arrives as a `JoinError` and fails the + // object rather than escaping the export. + let outcome = match tokio::task::spawn_local(self.clone().run(socket, started)).await { + Ok(outcome) => outcome, + Err(panic) => Err(js_error(format!("server task failed: {panic}"))), + }; + if let Err(error) = &outcome { + self.fail(error); + } + outcome } /// The promise of the next phase change. @@ -177,32 +181,6 @@ impl Shared { } } - /// Schedules `future` as a root and returns the Promise of its outcome. - fn promise( - self: &Rc, - future: impl std::future::Future> + 'static, - ) -> Result { - let resolvers = with_resolvers()?; - let promise = property(&resolvers, "promise")?; - let resolve: js_sys::Function = property(&resolvers, "resolve")?.unchecked_into(); - let reject: js_sys::Function = property(&resolvers, "reject")?.unchecked_into(); - let shared = self.clone(); - self.runtime.schedule(future, move |outcome| { - let outcome = - outcome.unwrap_or_else(|panic| Err(js_error(format!("server task failed: {panic}")))); - match outcome { - Ok(value) => { - let _ = resolve.call1(&JsValue::UNDEFINED, &value); - } - Err(error) => { - shared.fail(&error); - let _ = reject.call1(&JsValue::UNDEFINED, &error); - } - } - }); - Ok(promise) - } - fn fail(&self, error: &JsValue) { if self.phase.get() != Phase::Failed { self.set_phase(Phase::Failed); diff --git a/worker/index.mjs b/worker/index.mjs deleted file mode 100644 index 0bd31de..0000000 --- a/worker/index.mjs +++ /dev/null @@ -1,16 +0,0 @@ -import { DurableObject } from 'cloudflare:workers'; -import { connect, fetch, MinecraftWorld as World } from '../target/workers/wasm32-unknown-emscripten/release/pumpkin-do.js'; - -export default { connect, fetch }; - -// The runtime enables RPC on a Durable Object class only when it derives from -// DurableObject; the wasm-bindgen class is plain, so wrap it. -export class MinecraftWorld extends DurableObject { - #world; - constructor(state, env) { - super(state, env); - this.#world = new World(state, env); - } - connect(socket) { return this.#world.connect(socket); } - status() { return this.#world.status(); } -} diff --git a/wrangler.jsonc b/wrangler.jsonc index 3bf5f27..aef4207 100644 --- a/wrangler.jsonc +++ b/wrangler.jsonc @@ -1,7 +1,8 @@ { "$schema": "node_modules/wrangler/config-schema.json", "name": "rust-workers-minecraft", - "main": "worker/index.mjs", + "main": "build/index.js", + "build": { "command": "worker-build --emscripten --tokio --release -- --locked" }, "compatibility_date": "2026-09-05", "compatibility_flags": ["nodejs_compat", "new_module_registry", "no_handle_cross_request_promise_resolution"], "dev": { "ip": "127.0.0.1", "port": 8787 }, From a4194285df17d898edc4afb2e25a5f4e14771cc3 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 16 Sep 2026 23:17:42 -0700 Subject: [PATCH 5/6] Mount the world's SQLite filesystem and write stdio through NODERAWFS The world is again a live SQLite-backed filesystem instead of a tree copied to and from storage at start and stop: the object constructor mounts durable-object-fs at /data through worker-fs-mount, and src/workerd.js rebinds Emscripten's NODERAWFS to that node:fs implementation, so every Pumpkin write is durable as it happens. Relative paths are resolved against Emscripten's own working directory, which is the object's rather than the isolate's. The restore/checkpoint code and the files table go away; status reports the last save time. The stdio override is removed: workerd's node:fs supports the process's stdout and stderr descriptors, and Wrangler relays them as plain stdout and stderr lines rather than console errors. Wrangler's build command runs scripts/build.sh so it resolves the provisioned worker-build regardless of PATH. --- AGENTS.md | 5 ++- README.md | 9 +++-- build.rs | 2 +- docs/architecture.md | 33 ++++++++------- docs/dependencies.md | 8 ++-- docs/development.md | 2 +- package-lock.json | 73 ++++++++++++--------------------- package.json | 10 ++++- src/host.rs | 25 +++++------- src/js/mount.js | 13 ++++++ src/main.rs | 1 - src/persist.rs | 93 ------------------------------------------- src/workerd.js | 64 ++++++++++++++--------------- src/world.rs | 33 ++++++++------- tests/integration.mjs | 2 +- tests/server.mjs | 2 +- 16 files changed, 140 insertions(+), 235 deletions(-) create mode 100644 src/js/mount.js delete mode 100644 src/persist.rs diff --git a/AGENTS.md b/AGENTS.md index 95d7336..21bbc74 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,8 +5,9 @@ Read [README.md](README.md), [architecture](docs/architecture.md), and ## Project layout -- `src/`: the Worker: TCP ingress, the Durable Object, world persistence, - Pumpkin configuration, and the Emscripten JS library (`workerd.js`). +- `src/`: the Worker: TCP ingress, the Durable Object, the SQLite filesystem + mount (`js/mount.js`), Pumpkin configuration, and the Emscripten JS library + (`workerd.js`). - `tests/`: protocol clients and the integration test. - `scripts/setup.sh`: provision pinned sources and build the toolchain. - `scripts/{build,serve,test}.sh`: build, run, and validate the Workers server. diff --git a/README.md b/README.md index 1cb986b..940014d 100644 --- a/README.md +++ b/README.md @@ -40,10 +40,11 @@ The example uses offline mode, with encryption and compression disabled. Local listeners bind to loopback. `WORLD_NAME` in [wrangler.jsonc](wrangler.jsonc) selects the Durable Object; server settings are in [src/config.rs](src/config.rs). -World data lives under `.data/workers/server/`. After the last connection closes, -the server saves and stops. The next connection restores the same world. Wait for -status to report `phase: "idle"` and a checkpoint timestamp before stopping -Wrangler; changes held in memory can be lost if the process is terminated early. +World data lives under `.data/workers/server/`, written through to the Durable +Object's SQLite storage as Pumpkin saves. After the last connection closes, the +server saves and stops; the next connection starts it on the same world. Wait for +status to report `phase: "idle"` before stopping Wrangler so the final save +completes. ## Build and contribute diff --git a/build.rs b/build.rs index 3239053..a6139d6 100644 --- a/build.rs +++ b/build.rs @@ -12,7 +12,7 @@ fn main() { "-sSTACK_SIZE=8MB", // Keep growth headroom bounded after generation's temporary allocations. "-sMEMORY_GROWTH_LINEAR_STEP=2097152", - "-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\"$workerdStdio\"]", + "-sDEFAULT_LIBRARY_FUNCS_TO_INCLUDE=[\"$workerdFs\"]", "--js-library", ] { println!("cargo::rustc-link-arg-bins={arg}"); diff --git a/docs/architecture.md b/docs/architecture.md index d9087f1..898af32 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -13,9 +13,9 @@ state. Its `connect` is a `#[wasm_bindgen(tokio)]` export: the future runs on the thread's Tokio `LocalEventLoop` (tokio-rs/tokio#8484), the current-thread scheduler and drivers with the host event loop as their wait, and the export returns the Promise of its outcome. Nothing blocks or suspends. The first -`connect` after idle runs the server lifetime: it restores the world, starts -Pumpkin, awaits the server's return after its final save, checkpoints, and -settles the connection's promise. Later connections and `status` calls enter +`connect` after idle runs the server lifetime: it starts Pumpkin on the mounted +world, awaits the server's return after its final save, awaits `storage.sync()`, +and settles the connection's promise. Later connections and `status` calls enter the same instance as ordinary calls while that future is parked. A connection arriving while the server is starting or stopping waits for the next phase change and then retries, so a client that connects as the previous server @@ -49,19 +49,22 @@ neighborhood compact without changing its dependency rules. Emscripten grows memory in 2 MiB increments, configured by `build.rs`, while retaining the 8 MiB stack. See [memory measurements](memory-reduction.md). -## Filesystem and checkpoints +## Filesystem -Pumpkin runs against workerd's own Node filesystem through Emscripten's -`NODERAWFS`, with the world under `/tmp/world`. That tree lives for the Durable -Object's lifetime. Between runs it lives in the object's SQLite storage, one row -per file: the server root restores the tree before starting Pumpkin and, after -the server's final save, writes every changed file back, deletes rows for removed -files, and awaits `storage.sync()` before the last connection is reported closed. -Status shows the checkpoint time. Terminating a server with active clients can -lose changes since the previous checkpoint. +The world is a SQLite-backed filesystem: the object constructor mounts +`durable-object-fs`'s `LocalDOFilesystem(storage)` at `/data` through +`worker-fs-mount`, whose `node:fs` implementation routes that prefix to the +object's storage and every other path (stdio, `/tmp`) to workerd's own +filesystem. `src/workerd.js` rebinds Emscripten's `NODERAWFS` to it, resolves +relative paths against Emscripten's working directory (`/data`, distinct from +the isolate's `process.cwd()`), and disables the VFS's own permission checks +since the host enforces them. Every Pumpkin write is therefore durable as it +happens, under the object's transaction semantics; after the final save the +server awaits `storage.sync()` before the last connection is reported closed, +and status shows that time. -Logging goes to the host console: `src/workerd.js` keeps Emscripten's stdio -descriptors on its console callbacks, since workerd exposes no process stdio. +Logging goes to the process's stdout and stderr through `NODERAWFS`, which +Wrangler relays as `stdout:` and `stderr:` lines. ## Development interfaces @@ -69,7 +72,7 @@ The supplied configuration listens on loopback TCP port 25565 and HTTP port 8787 The world name comes from `WORLD_NAME` in `wrangler.jsonc`. - `GET /health`: Worker readiness. -- `GET /`: phase, connection count, runtime statistics, and last checkpoint time. +- `GET /`: phase, connection count, runtime statistics, and last save time. Runtime statistics include Wasm capacity and allocator in-use/free/arena bytes. Allocator counters include allocation metadata and unused container capacity; diff --git a/docs/dependencies.md b/docs/dependencies.md index 173a00f..d6bd26d 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -101,9 +101,11 @@ bash scripts/setup.sh --sources-only ## JavaScript dependencies -`package-lock.json` pins Wrangler 4.129.0. There is no application JavaScript; -worker-build generates `build/index.js`, which wraps the exports into the -entrypoint and derives the Durable Object class from `DurableObject` for RPC. +`package-lock.json` pins Wrangler 4.129.0, `worker-fs-mount` 0.2.0 and +`durable-object-fs` 1.0.0 (the SQLite filesystem mount, imported by +`src/js/mount.js` and bundled by worker-build). worker-build generates +`build/index.js`, which wraps the exports into the entrypoint and derives the +Durable Object class from `DurableObject` for RPC. After moving a checkout with cached build output, run `cargo clean` before rebuilding. Generated data can contain absolute paths. This leaves databases under `.data/` intact. diff --git a/docs/development.md b/docs/development.md index f972f56..9dd4121 100644 --- a/docs/development.md +++ b/docs/development.md @@ -23,7 +23,7 @@ stop only their own Wrangler process groups. Their databases and logs live in `npm test` must finish with `PUMPKIN-DO-SQLITE-RESTART-OK`. It checks two-player gameplay, a real block edit, and player/block restoration after a restart, which -exercises the tree-to-SQLite checkpoint and restore. +exercises the SQLite-mounted world across a restart. The memory probe waits for both clients to receive 81 chunks, observes 30 seconds of stationary play, then checkpoints. Each run creates a fresh isolated world diff --git a/package-lock.json b/package-lock.json index 18ad428..a1125fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,10 @@ "packages": { "": { "name": "rust-workers-minecraft", + "dependencies": { + "durable-object-fs": "1.0.0", + "worker-fs-mount": "0.2.0" + }, "devDependencies": { "wrangler": "4.129.0" }, @@ -707,9 +711,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -727,9 +728,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -747,9 +745,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -767,9 +762,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -787,9 +779,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -807,9 +796,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -827,9 +813,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -847,9 +830,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "LGPL-3.0-or-later", "optional": true, "os": [ @@ -867,9 +847,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -893,9 +870,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -919,9 +893,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -945,9 +916,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -971,9 +939,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -997,9 +962,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1023,9 +985,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1049,9 +1008,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "Apache-2.0", "optional": true, "os": [ @@ -1272,6 +1228,18 @@ "node": ">=8" } }, + "node_modules/durable-object-fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/durable-object-fs/-/durable-object-fs-1.0.0.tgz", + "integrity": "sha512-r4XOu7vlnC3kg8HiFYO3sQweO3eUJLa7q1tZHUy6Oj1ifHR8So4W5oljz4YUD2sIzWuWgEt+ljirqPVj/WhbwA==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "worker-fs-mount": "workspace:*" + } + }, "node_modules/error-stack-parser-es": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", @@ -1480,6 +1448,15 @@ "pathe": "^2.0.3" } }, + "node_modules/worker-fs-mount": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/worker-fs-mount/-/worker-fs-mount-0.2.0.tgz", + "integrity": "sha512-SHnIcUkBIyojQNtMdpIBDM0/JDD58xgTMcyJMTamU2z9WbG8cuoAWs5SBzyBfCcp+xPMXu8h+Ou3yqC9kUxFdw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/workerd": { "version": "1.20260903.1", "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260903.1.tgz", diff --git a/package.json b/package.json index c6e507e..f3ede69 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,10 @@ "engines": { "node": ">=24" }, - "dependencies": {}, + "dependencies": { + "durable-object-fs": "1.0.0", + "worker-fs-mount": "0.2.0" + }, "devDependencies": { "wrangler": "4.129.0" }, @@ -15,5 +18,10 @@ "test": "bash scripts/test.sh", "test:memory": "npm run build && node tests/memory.mjs", "test:scheduler": "bash scripts/test-scheduler.sh" + }, + "overrides": { + "durable-object-fs": { + "worker-fs-mount": "$worker-fs-mount" + } } } diff --git a/src/host.rs b/src/host.rs index b674fb3..cbb7de3 100644 --- a/src/host.rs +++ b/src/host.rs @@ -2,6 +2,16 @@ use wasm_bindgen::prelude::*; +#[wasm_bindgen(module = "/src/js/mount.js")] +extern "C" { + /// Mounts the object's SQLite storage as the world's filesystem and routes + /// NODERAWFS through it; `ROOT` is the mount path. + #[wasm_bindgen(js_name = mountStorage)] + pub fn mount_storage(storage: &JsValue); + #[wasm_bindgen(js_name = ROOT, thread_local_v2)] + pub static MOUNT_ROOT: JsValue; +} + #[wasm_bindgen(module = "cloudflare:node")] extern "C" { /// Routes an inbound socket to the `net.Server` listening on its local @@ -50,18 +60,3 @@ pub fn js_error(error: impl std::fmt::Display) -> JsValue { js_sys::Error::new(&error.to_string()).into() } -/// `storage.sql.exec(query, ...bindings)` with the rows read out eagerly as -/// arrays of column values. -pub fn sql(storage: &JsValue, query: &str, bindings: &[JsValue]) -> Result>, JsValue> { - let sql = property(storage, "sql")?; - let exec: js_sys::Function = property(&sql, "exec")?.unchecked_into(); - let args = js_sys::Array::new(); - args.push(&query.into()); - for binding in bindings { - args.push(binding); - } - let cursor = js_sys::Reflect::apply(&exec, &sql, &args)?; - let raw = method(&cursor, "raw", &[])?; - let rows = js_sys::Array::from(&method(&raw, "toArray", &[])?); - Ok(rows.iter().map(|row| js_sys::Array::from(&row).to_vec()).collect()) -} diff --git a/src/js/mount.js b/src/js/mount.js new file mode 100644 index 0000000..60f6c21 --- /dev/null +++ b/src/js/mount.js @@ -0,0 +1,13 @@ +// The world's filesystem: worker-fs-mount's node:fs implementation routes paths +// under a mount to the Durable Object's SQLite storage and everything else to +// workerd's node:fs. Emscripten's NODERAWFS is rebound to it (src/workerd.js). +import { LocalDOFilesystem } from 'durable-object-fs/local'; +import { mount } from 'worker-fs-mount'; +import * as fs from 'worker-fs-mount/fs-sync'; + +export const ROOT = '/data'; + +export function mountStorage(storage) { + mount(ROOT, new LocalDOFilesystem(storage)); + globalThis.__pumpkin_fs = fs; +} diff --git a/src/main.rs b/src/main.rs index 9460aeb..9245c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,6 @@ mod config; mod host; mod memory; -mod persist; mod world; use host::{method, property, then}; diff --git a/src/persist.rs b/src/persist.rs deleted file mode 100644 index ef67f08..0000000 --- a/src/persist.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! The world lives in the object's `/tmp` tree while it runs and in its SQLite -//! storage between runs: one row per file, written at checkpoint, read at start. - -use crate::host::{js_error, sql}; -use std::{ - collections::HashMap, - fs, - path::{Path, PathBuf}, -}; -use wasm_bindgen::JsValue; - -pub const ROOT: &str = "/tmp/world"; - -pub fn prepare(storage: &JsValue) -> Result<(), JsValue> { - sql( - storage, - "CREATE TABLE IF NOT EXISTS files (path TEXT PRIMARY KEY, data BLOB NOT NULL)", - &[], - )?; - Ok(()) -} - -fn stored(storage: &JsValue) -> Result>, JsValue> { - let mut files = HashMap::new(); - for row in sql(storage, "SELECT path, data FROM files", &[])? { - let [path, data] = row.as_slice() else { - return Err(js_error("unexpected files row")); - }; - let path = path.as_string().ok_or_else(|| js_error("path is not text"))?; - let data = js_sys::Uint8Array::new(&data).to_vec(); - files.insert(path, data); - } - Ok(files) -} - -/// Recreates the stored tree under `ROOT`. -pub fn restore(storage: &JsValue) -> Result { - let _ = fs::remove_dir_all(ROOT); - fs::create_dir_all(ROOT).map_err(js_error)?; - let files = stored(storage)?; - for (path, data) in &files { - let target = Path::new(ROOT).join(path); - if let Some(parent) = target.parent() { - fs::create_dir_all(parent).map_err(js_error)?; - } - fs::write(target, data).map_err(js_error)?; - } - Ok(files.len()) -} - -/// Writes every file under `ROOT` that differs from storage and removes rows for -/// files that no longer exist. Returns (written, removed). -pub fn save(storage: &JsValue) -> Result<(usize, usize), JsValue> { - let mut previous = stored(storage)?; - let mut files = Vec::new(); - walk(Path::new(ROOT), &mut files).map_err(js_error)?; - let mut written = 0; - for file in files { - let key = file - .strip_prefix(ROOT) - .map_err(js_error)? - .to_string_lossy() - .into_owned(); - let data = fs::read(&file).map_err(js_error)?; - if previous.remove(&key).is_some_and(|old| old == data) { - continue; - } - sql( - storage, - "INSERT OR REPLACE INTO files (path, data) VALUES (?, ?)", - &[key.into(), js_sys::Uint8Array::from(data.as_slice()).into()], - )?; - written += 1; - } - let removed = previous.len(); - for key in previous.into_keys() { - sql(storage, "DELETE FROM files WHERE path = ?", &[key.into()])?; - } - Ok((written, removed)) -} - -fn walk(dir: &Path, out: &mut Vec) -> std::io::Result<()> { - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if entry.file_type()?.is_dir() { - walk(&path, out)?; - } else { - out.push(path); - } - } - Ok(()) -} diff --git a/src/workerd.js b/src/workerd.js index fd37705..dc86445 100644 --- a/src/workerd.js +++ b/src/workerd.js @@ -1,7 +1,7 @@ // Emscripten library adjustments for NODERAWFS on workerd's Node filesystem. addToLibrary({ // Node's deprecated process.binding is absent; fs.constants is the public - // form of the same table (emscripten-core/emscripten cf-final 4e034c65a). + // form of the same table. $NODEFS__postset: ` NODEFS.isWindows = false; NODEFS.flagsForNodeMap = Object.fromEntries([ @@ -18,37 +18,37 @@ addToLibrary({ ].map(([flag, name]) => [flag, fs.constants[name]])); `, - // workerd has no process stdio descriptors: keep fds 0-2 on Emscripten's - // console callbacks so Pumpkin's log and Rust panics reach the host console. - // Under NODERAWFS the host enforces permissions; workerd reports directories - // as 0666, which would otherwise fail the VFS's execute check in getdents. - $workerdStdio__deps: ['$FS', '$TTY'], - $workerdStdio__postset: () => addAtPreRun('workerdStdio()'), - $workerdStdio: () => { + // The host enforces permissions; workerd reports directories as 0666, which + // would otherwise fail the VFS's execute check in getdents. Once the object + // mounts its storage (src/js/mount.js), NODERAWFS reads and writes through + // worker-fs-mount's fs, which routes the mount to SQLite and the rest to + // workerd's own fs. + $workerdFs__deps: ['$FS', '$PATH_FS'], + $workerdFs__postset: () => addAtPreRun('workerdFs()'), + $workerdFs: () => { Object.defineProperty(FS, 'ignorePermissions', { get: () => true, set() {} }); - const consoles = [ - null, - { output: [], ops: TTY.default_tty_ops }, - { output: [], ops: TTY.default_tty1_ops }, - ]; - const fstat = FS.fstat; - FS.fstat = (fd) => { - const stream = FS.getStreamChecked(fd); - if (stream.nfd === 0 || stream.nfd === 1 || stream.nfd === 2) { - return { - dev: 0, ino: stream.nfd, mode: 0o020666, nlink: 1, uid: 0, gid: 0, rdev: 0, - size: 0, blksize: 4096, blocks: 0, - atime: new Date(0), mtime: new Date(0), ctime: new Date(0), - }; - } - return fstat(fd); - }; - const write = FS.write; - FS.write = (stream, buffer, offset, length, position) => { - const tty = consoles[stream.nfd]; - if (!tty) return write(stream, buffer, offset, length, position); - for (let i = offset; i < offset + length; i++) tty.ops.put_char(tty, buffer[i]); - return length; - }; + // The working directory is the object's, not the isolate process's, so + // relative paths (which NODERAWFS otherwise leaves to the host's cwd) are + // resolved here before they reach the fs implementation. + let cwd = '/'; + FS.cwd = () => cwd; + FS.chdir = (path) => { cwd = PATH_FS.resolve(path); }; + const resolve = (path) => (typeof path === 'string' && path[0] !== '/' ? PATH_FS.resolve(path) : path); + const twoPaths = new Set(['renameSync', 'copyFileSync', 'linkSync']); + const resolving = (impl) => new Proxy(impl, { + get(target, name) { + const value = target[name]; + if (typeof value !== 'function' || !String(name).endsWith('Sync')) return value; + return (first, second, ...rest) => + value.call(target, resolve(first), twoPaths.has(name) ? resolve(second) : second, ...rest); + }, + }); + const nodeFs = fs; + fs = resolving(nodeFs); + Object.defineProperty(globalThis, '__pumpkin_fs', { + configurable: true, + get: () => nodeFs, + set: (mounted) => { fs = resolving(mounted); }, + }); }, }); diff --git a/src/world.rs b/src/world.rs index e69efba..fbdaf80 100644 --- a/src/world.rs +++ b/src/world.rs @@ -3,11 +3,11 @@ //! `connect` is a `#[wasm_bindgen(tokio)]` export: its future runs on the //! thread's Tokio event loop, whose wait is the host event loop, so nothing //! blocks or suspends and the export returns a Promise. The server lifetime is -//! one such future: the first `connect` after idle runs it, and its completion -//! (after the final save) is the checkpoint. Later connections are routed to -//! the running listener. +//! one such future: the first `connect` after idle runs it on the world mounted +//! from the object's storage, and it completes after the final save is synced. +//! Later connections are routed to the running listener. -use crate::{config, host, memory, persist}; +use crate::{config, host, memory}; use host::{js_error, method, property, then}; use pumpkin::{data::VanillaData, server::Server, PumpkinServer}; use std::{ @@ -56,7 +56,7 @@ struct Shared { failure: RefCell>, connections: Cell, startup_ms: Cell>, - checkpointed_at: RefCell>, + saved_at: RefCell>, /// Signals the server root that the last connection has closed. idle: Arc, /// Resolves at the next phase change; connections arriving mid-transition @@ -84,7 +84,7 @@ impl MinecraftWorld { eprintln!("RUST PANIC: {info}"); })); let storage = property(&state, "storage")?; - persist::prepare(&storage)?; + host::mount_storage(&storage); Ok(MinecraftWorld { shared: Rc::new(Shared { storage, @@ -92,7 +92,7 @@ impl MinecraftWorld { failure: RefCell::new(None), connections: Cell::new(0), startup_ms: Cell::new(None), - checkpointed_at: RefCell::new(None), + saved_at: RefCell::new(None), idle: Arc::new(tokio::sync::Notify::new()), transition: RefCell::new(None), }), @@ -100,7 +100,7 @@ impl MinecraftWorld { } /// Serves one inbound connection; resolves when it closes. The first call - /// while idle also runs the server, resolving once the world is checkpointed. + /// while idle also runs the server, resolving once the final save is synced. #[wasm_bindgen(tokio)] pub async fn connect(&self, socket: JsValue) -> Result { let shared = self.shared.clone(); @@ -119,8 +119,8 @@ impl MinecraftWorld { )?; set("startup_ms", shared.startup_ms.get().map_or(JsValue::NULL, JsValue::from))?; set( - "checkpointed_at", - shared.checkpointed_at.borrow().as_deref().map_or(JsValue::NULL, JsValue::from), + "saved_at", + shared.saved_at.borrow().as_deref().map_or(JsValue::NULL, JsValue::from), )?; let server = SERVER.with(|slot| slot.borrow().clone()); set( @@ -204,9 +204,9 @@ impl Shared { } async fn run(self: Rc, first: JsValue, started: f64) -> Result { - let restored = persist::restore(&self.storage)?; - std::env::set_current_dir(persist::ROOT).map_err(js_error)?; - eprintln!("Restored {restored} world files"); + let root = host::MOUNT_ROOT.with(|root| root.as_string()).unwrap_or_default(); + std::fs::create_dir_all(&root).map_err(js_error)?; + std::env::set_current_dir(&root).map_err(js_error)?; pumpkin::reset_stop(); let (basic, advanced) = config::configuration(); if !LOGGER.replace(true) { @@ -242,14 +242,13 @@ impl Shared { if let Some(panic) = PANIC.with(|slot| slot.borrow_mut().take()) { return Err(js_error(panic)); } - let (written, removed) = persist::save(&self.storage)?; - eprintln!("Checkpoint: {written} files written, {removed} removed"); - // Issued writes become durable before the connection is reported closed. + // Writes issued through the mount become durable before the connection + // is reported closed. let synced = method(&self.storage, "sync", &[])?; let shared = self.clone(); let done = Closure::once_into_js(move |_: JsValue| { shared - .checkpointed_at + .saved_at .replace(Some(String::from(js_sys::Date::new_0().to_iso_string()))); shared.set_phase(Phase::Idle); }); diff --git a/tests/integration.mjs b/tests/integration.mjs index fcc0f51..0b29590 100644 --- a/tests/integration.mjs +++ b/tests/integration.mjs @@ -59,7 +59,7 @@ try { assert.equal(b.block(changedBlock), 0, 'Second client lost the edit before checkpoint'); await a.close(); await b.close(); clients.length = 0; const saved = await checkpoint(worker); - console.log(`World checkpoint completed at ${saved.checkpointed_at}`); + console.log(`World saved at ${saved.saved_at}`); } finally { await Promise.all(clients.map(client => client.close())); await worker.stop(); diff --git a/tests/server.mjs b/tests/server.mjs index b78e406..7fd5d8b 100644 --- a/tests/server.mjs +++ b/tests/server.mjs @@ -95,7 +95,7 @@ export async function testServer({ seed = '1789195700909824175' } = {}) { throw error; } if (state.failure) throw new Error(state.failure); - return state.phase === 'idle' && state.checkpointed_at && state.connections === 0 && state; + return state.phase === 'idle' && state.saved_at && state.connections === 0 && state; }, 'world checkpoint'); } From 168fae350e0a25b484164998c4b314a49a41bb6c Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Fri, 18 Sep 2026 15:41:59 -0700 Subject: [PATCH 6/6] Run on Wrangler's bundled workerd workerd 1.20260918.1 ships the net.Server Durable Object routing and the node:fs fixes this server needs, so Wrangler 4.135.0's bundled workerd is used as is; the MINIFLARE_WORKERD_PATH instructions for running a workerd main build go away. The version is also pinned directly in package.json. --- .github/workflows/ci.yml | 2 - README.md | 5 - docs/dependencies.md | 10 +- package-lock.json | 316 ++++++++++++++++++++------------------- package.json | 3 +- 5 files changed, 167 insertions(+), 169 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4778200..8640b26 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,6 @@ jobs: run: bash scripts/setup.sh - name: Install Worker dependencies run: npm ci - # Inbound net.Server routing inside Durable Objects is not yet in Wrangler's - # bundled workerd; set MINIFLARE_WORKERD_PATH to a build that has it. - name: Worker TCP, shared world, and SQLite restart run: | set -o pipefail diff --git a/README.md b/README.md index 940014d..5bccc96 100644 --- a/README.md +++ b/README.md @@ -28,11 +28,6 @@ worker-build and wasm-bindgen CLIs. `npm run dev` compiles Pumpkin with `worker-build --emscripten --tokio` and starts Wrangler. The first build takes several minutes; later builds use Cargo's cache. -The runtime support this needs (`net.Server` routing into Durable Objects and -the `node:fs` fixes listed in [dependency pins](docs/dependencies.md#host-tools)) -is in workerd main. Until Wrangler's bundled workerd catches up, point Miniflare -at a build of main: `MINIFLARE_WORKERD_PATH=/path/to/workerd npm run dev`. - Connect **Minecraft Java 26.2** to **`localhost:25565`**. Status is available at **http://localhost:8787/**. diff --git a/docs/dependencies.md b/docs/dependencies.md index d6bd26d..bac9834 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -55,13 +55,11 @@ application graph, including the branch commits. (or an activated emsdk selected with `EMSDK`). `scripts/common.sh` hands the frontend checkout and emsdk to worker-build through `EMSCRIPTEN` and `EMSDK`; it uses the frontend unpatched. -- workerd: Wrangler must run a workerd with `net.Server` inbound routing into +- workerd 1.20260918.1 (Wrangler's bundled version, also pinned directly in + `package.json`): the first release with `net.Server` inbound routing into Durable Objects (`handleAsNodeConnection`, cloudflare/workerd#7306, #7313) and the `node:fs` fixes for positional buffer I/O (#7368), `O_TRUNC` (#7369), - `O_CREAT` (#7393), and rename over an existing path (#7394). All are in - workerd main; until Wrangler's bundled version catches up, build main - (`bazel build //src/workerd/server:workerd`) and set `MINIFLARE_WORKERD_PATH` - to `bazel-bin/src/workerd/server/workerd`. + `O_CREAT` (#7393), and rename over an existing path (#7394). ## Updating a patch @@ -101,7 +99,7 @@ bash scripts/setup.sh --sources-only ## JavaScript dependencies -`package-lock.json` pins Wrangler 4.129.0, `worker-fs-mount` 0.2.0 and +`package-lock.json` pins Wrangler 4.135.0, workerd 1.20260918.1, `worker-fs-mount` 0.2.0 and `durable-object-fs` 1.0.0 (the SQLite filesystem mount, imported by `src/js/mount.js` and bundled by worker-build). worker-build generates `build/index.js`, which wraps the exports into the entrypoint and derives the diff --git a/package-lock.json b/package-lock.json index a1125fb..cb12ab0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "worker-fs-mount": "0.2.0" }, "devDependencies": { - "wrangler": "4.129.0" + "workerd": "1.20260918.1", + "wrangler": "4.135.0" }, "engines": { "node": ">=24" @@ -43,9 +44,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260903.1.tgz", - "integrity": "sha512-FG+4mGxAXhKiL/1temH42alevIkumtYXNidhTa//3yULpzux6APw5UNVocI/vCQ1yG1YOEZRfbVy8lyuipM9MQ==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260918.1.tgz", + "integrity": "sha512-H5Em6Wd0jjxaloYh2rp+WLBl2eWbkk7nSP1svGt6K1RSv/rNVqmKdpjZPJTBSavOmeYGa88qvtOWwS+33OHTqQ==", "cpu": [ "x64" ], @@ -60,9 +61,9 @@ } }, "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260903.1.tgz", - "integrity": "sha512-o241VefnjG8eG+kGap5CjgV4zOT5UaAmS3OR1VZCpNj7vkXGxvp9KftKvtQgcCsIqJKaKx+7Xd7xq0L1DjkfPQ==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260918.1.tgz", + "integrity": "sha512-CR9JRZEQo93fNgBVF4Df2H2/VYO4n7rxSseSwCVv3bJQEb0huADUSCj2FK4ipxar8ToOEB4wawyw0vJo5U/rQQ==", "cpu": [ "arm64" ], @@ -77,9 +78,9 @@ } }, "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260903.1.tgz", - "integrity": "sha512-/VEvvtQ/XKf6HlBbg6FbvpwcpfYUcx6Fv6RkASY8DyEmUyuJ8rc7Qxil83ClRFoBzz/GY0BV94UZ6F+wers/hg==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260918.1.tgz", + "integrity": "sha512-UQ2nnY3qpXLzQ80frmWO+8HvtqyWaQILe8QYZwpemdjT+sqwCz4Dz+0/WVkFco0v/04kIIqikVeLTY/7gEhmkw==", "cpu": [ "x64" ], @@ -94,9 +95,9 @@ } }, "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260903.1.tgz", - "integrity": "sha512-OWhihGC6KoTXF4u2C1AonfpgXeM4/7p/1IXuALqXESmFUpLLP5gZhRzjSk/gWW+mrCZDfSrvnjifl+lRqselbA==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260918.1.tgz", + "integrity": "sha512-4rib51MaLNWweUIUxM/Xj558M5QmyZoBSf0ffv+lYah5VTrvwnez3XGxX72pElnBKHROvAPOi5msQ5Ts8JSI0A==", "cpu": [ "arm64" ], @@ -111,9 +112,9 @@ } }, "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260903.1.tgz", - "integrity": "sha512-soPMF9/aMHlHKK7M0vq5HrRRicPbnZO1F6ZZ7JWN8EltxWJU5L7CEq7CxjO878DzyPx7gYvqBFy3SVgdZKZjMQ==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260918.1.tgz", + "integrity": "sha512-sATrMx5ShYYgmgUGrcTmvsFSJBFuN95NEkX3xwb1qk8w1A6h5N11sea7yN2IeibwPyPmXKjWNjXOno0hZAM71Q==", "cpu": [ "x64" ], @@ -604,9 +605,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", - "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.4.tgz", + "integrity": "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA==", "cpu": [ "arm64" ], @@ -623,13 +624,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.3.1" + "@img/sharp-libvips-darwin-arm64": "1.3.3" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", - "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.4.tgz", + "integrity": "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw==", "cpu": [ "x64" ], @@ -646,13 +647,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.3.1" + "@img/sharp-libvips-darwin-x64": "1.3.3" } }, "node_modules/@img/sharp-freebsd-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", - "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.4.tgz", + "integrity": "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA==", "dev": true, "license": "Apache-2.0", "optional": true, @@ -660,7 +661,7 @@ "freebsd" ], "dependencies": { - "@img/sharp-wasm32": "0.35.2" + "@img/sharp-wasm32": "0.35.4" }, "engines": { "node": ">=20.9.0" @@ -670,9 +671,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", - "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.3.tgz", + "integrity": "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg==", "cpu": [ "arm64" ], @@ -687,9 +688,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", - "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.3.tgz", + "integrity": "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw==", "cpu": [ "x64" ], @@ -704,9 +705,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", - "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.3.tgz", + "integrity": "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA==", "cpu": [ "arm" ], @@ -721,9 +722,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", - "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.3.tgz", + "integrity": "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A==", "cpu": [ "arm64" ], @@ -738,9 +739,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", - "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.3.tgz", + "integrity": "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w==", "cpu": [ "ppc64" ], @@ -755,9 +756,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", - "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.3.tgz", + "integrity": "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ==", "cpu": [ "riscv64" ], @@ -772,9 +773,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", - "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.3.tgz", + "integrity": "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q==", "cpu": [ "s390x" ], @@ -789,9 +790,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", - "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.3.tgz", + "integrity": "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA==", "cpu": [ "x64" ], @@ -806,9 +807,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", - "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.3.tgz", + "integrity": "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw==", "cpu": [ "arm64" ], @@ -823,9 +824,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", - "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.3.tgz", + "integrity": "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw==", "cpu": [ "x64" ], @@ -840,9 +841,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", - "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.4.tgz", + "integrity": "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A==", "cpu": [ "arm" ], @@ -859,13 +860,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.3.1" + "@img/sharp-libvips-linux-arm": "1.3.3" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", - "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.4.tgz", + "integrity": "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ==", "cpu": [ "arm64" ], @@ -882,13 +883,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.3.1" + "@img/sharp-libvips-linux-arm64": "1.3.3" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", - "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.4.tgz", + "integrity": "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ==", "cpu": [ "ppc64" ], @@ -905,13 +906,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.3.1" + "@img/sharp-libvips-linux-ppc64": "1.3.3" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", - "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.4.tgz", + "integrity": "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA==", "cpu": [ "riscv64" ], @@ -928,13 +929,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.3.1" + "@img/sharp-libvips-linux-riscv64": "1.3.3" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", - "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.4.tgz", + "integrity": "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ==", "cpu": [ "s390x" ], @@ -951,13 +952,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.3.1" + "@img/sharp-libvips-linux-s390x": "1.3.3" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", - "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.4.tgz", + "integrity": "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA==", "cpu": [ "x64" ], @@ -974,13 +975,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.3.1" + "@img/sharp-libvips-linux-x64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", - "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.4.tgz", + "integrity": "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA==", "cpu": [ "arm64" ], @@ -997,13 +998,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", - "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.4.tgz", + "integrity": "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg==", "cpu": [ "x64" ], @@ -1020,18 +1021,18 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.3.1" + "@img/sharp-libvips-linuxmusl-x64": "1.3.3" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", - "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.4.tgz", + "integrity": "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA==", "dev": true, "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.11.1" + "@emnapi/runtime": "^1.11.3" }, "engines": { "node": ">=20.9.0" @@ -1041,9 +1042,9 @@ } }, "node_modules/@img/sharp-webcontainers-wasm32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", - "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.4.tgz", + "integrity": "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA==", "cpu": [ "wasm32" ], @@ -1051,7 +1052,7 @@ "license": "Apache-2.0", "optional": true, "dependencies": { - "@img/sharp-wasm32": "0.35.2" + "@img/sharp-wasm32": "0.35.4" }, "engines": { "node": ">=20.9.0" @@ -1061,9 +1062,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", - "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.4.tgz", + "integrity": "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw==", "cpu": [ "arm64" ], @@ -1081,9 +1082,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", - "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.4.tgz", + "integrity": "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw==", "cpu": [ "ia32" ], @@ -1101,9 +1102,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", - "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.4.tgz", + "integrity": "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg==", "cpu": [ "x64" ], @@ -1318,16 +1319,16 @@ } }, "node_modules/miniflare": { - "version": "5.20260903.0-alpha", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260903.0-alpha.tgz", - "integrity": "sha512-VCZIFxOqFXeibRBJWwTlppqbv2lkeO10IX3QBRGK9j1QiMAfH/OSsCvbjp1MslBMhVZmy2VTRlVaVnsKnbDp6A==", + "version": "5.20260918.0-alpha", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260918.0-alpha.tgz", + "integrity": "sha512-vyIes7yW/OTzHtz4GhcBCTohZfqk2eQNiIkngZ07VRA5Qu24aNgW/TrFwlPkMLMjWDQ8R4JJBBHR/KX+liSDtg==", "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "0.8.1", - "sharp": "0.35.2", + "sharp": "0.35.4", "undici": "7.29.0", - "workerd": "1.20260903.1", + "workerd": "1.20260918.1", "ws": "8.21.0", "youch": "4.1.0-beta.10" }, @@ -1363,15 +1364,15 @@ } }, "node_modules/sharp": { - "version": "0.35.2", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", - "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", + "version": "0.35.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.4.tgz", + "integrity": "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.8.4" + "semver": "^7.8.5" }, "engines": { "node": ">=20.9.0" @@ -1380,31 +1381,36 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.2", - "@img/sharp-darwin-x64": "0.35.2", - "@img/sharp-freebsd-wasm32": "0.35.2", - "@img/sharp-libvips-darwin-arm64": "1.3.1", - "@img/sharp-libvips-darwin-x64": "1.3.1", - "@img/sharp-libvips-linux-arm": "1.3.1", - "@img/sharp-libvips-linux-arm64": "1.3.1", - "@img/sharp-libvips-linux-ppc64": "1.3.1", - "@img/sharp-libvips-linux-riscv64": "1.3.1", - "@img/sharp-libvips-linux-s390x": "1.3.1", - "@img/sharp-libvips-linux-x64": "1.3.1", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", - "@img/sharp-libvips-linuxmusl-x64": "1.3.1", - "@img/sharp-linux-arm": "0.35.2", - "@img/sharp-linux-arm64": "0.35.2", - "@img/sharp-linux-ppc64": "0.35.2", - "@img/sharp-linux-riscv64": "0.35.2", - "@img/sharp-linux-s390x": "0.35.2", - "@img/sharp-linux-x64": "0.35.2", - "@img/sharp-linuxmusl-arm64": "0.35.2", - "@img/sharp-linuxmusl-x64": "0.35.2", - "@img/sharp-webcontainers-wasm32": "0.35.2", - "@img/sharp-win32-arm64": "0.35.2", - "@img/sharp-win32-ia32": "0.35.2", - "@img/sharp-win32-x64": "0.35.2" + "@img/sharp-darwin-arm64": "0.35.4", + "@img/sharp-darwin-x64": "0.35.4", + "@img/sharp-freebsd-wasm32": "0.35.4", + "@img/sharp-libvips-darwin-arm64": "1.3.3", + "@img/sharp-libvips-darwin-x64": "1.3.3", + "@img/sharp-libvips-linux-arm": "1.3.3", + "@img/sharp-libvips-linux-arm64": "1.3.3", + "@img/sharp-libvips-linux-ppc64": "1.3.3", + "@img/sharp-libvips-linux-riscv64": "1.3.3", + "@img/sharp-libvips-linux-s390x": "1.3.3", + "@img/sharp-libvips-linux-x64": "1.3.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", + "@img/sharp-libvips-linuxmusl-x64": "1.3.3", + "@img/sharp-linux-arm": "0.35.4", + "@img/sharp-linux-arm64": "0.35.4", + "@img/sharp-linux-ppc64": "0.35.4", + "@img/sharp-linux-riscv64": "0.35.4", + "@img/sharp-linux-s390x": "0.35.4", + "@img/sharp-linux-x64": "0.35.4", + "@img/sharp-linuxmusl-arm64": "0.35.4", + "@img/sharp-linuxmusl-x64": "0.35.4", + "@img/sharp-webcontainers-wasm32": "0.35.4", + "@img/sharp-win32-arm64": "0.35.4", + "@img/sharp-win32-ia32": "0.35.4", + "@img/sharp-win32-x64": "0.35.4" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/supports-color": { @@ -1458,9 +1464,9 @@ } }, "node_modules/workerd": { - "version": "1.20260903.1", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260903.1.tgz", - "integrity": "sha512-xJzt2RnCy7ulOULmZy/4JLbEPg1uisp9lVoOUsEz+UVhDsTmrSQ0rBXZMGcXuhr2HCGKs89bg8nnbbzvBSX1Ig==", + "version": "1.20260918.1", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260918.1.tgz", + "integrity": "sha512-NsjfQlBNQ0iEniv/STOy4zbp8s5k60PzL1Ter02Eg44arbDhHtf6UOs03E31XDRmmBFxSIkAZuCyld3RKH1wqA==", "dev": true, "hasInstallScript": true, "license": "Apache-2.0", @@ -1471,17 +1477,17 @@ "node": ">=16" }, "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20260903.1", - "@cloudflare/workerd-darwin-arm64": "1.20260903.1", - "@cloudflare/workerd-linux-64": "1.20260903.1", - "@cloudflare/workerd-linux-arm64": "1.20260903.1", - "@cloudflare/workerd-windows-64": "1.20260903.1" + "@cloudflare/workerd-darwin-64": "1.20260918.1", + "@cloudflare/workerd-darwin-arm64": "1.20260918.1", + "@cloudflare/workerd-linux-64": "1.20260918.1", + "@cloudflare/workerd-linux-arm64": "1.20260918.1", + "@cloudflare/workerd-windows-64": "1.20260918.1" } }, "node_modules/wrangler": { - "version": "4.129.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.129.0.tgz", - "integrity": "sha512-PGPvs9UPoFrwxT0VogpESSZGvZIctAuTK3wGsLLPHtHsSgS85kNdvtpa2d14UzG8gwLWD64XUGFPGG9tOXG9VQ==", + "version": "4.135.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.135.0.tgz", + "integrity": "sha512-WrNBQSfIG6YcILJcodYr5ty8vgkzGsV8YX+kfd+uZ5/Bd8cUW0GnUIRKAVfn5kYKqh1m/BPcji9h1d7mE8euFw==", "dev": true, "license": "MIT OR Apache-2.0", "dependencies": { @@ -1489,10 +1495,10 @@ "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.28.1", - "miniflare": "5.20260903.0-alpha", + "miniflare": "5.20260918.0-alpha", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", - "workerd": "1.20260903.1" + "workerd": "1.20260918.1" }, "bin": { "cf-wrangler": "bin/cf-wrangler.js", @@ -1506,7 +1512,7 @@ "fsevents": "2.3.3" }, "peerDependencies": { - "@cloudflare/workers-types": "^5.20260903.1" + "@cloudflare/workers-types": "^5.20260918.1" }, "peerDependenciesMeta": { "@cloudflare/workers-types": { diff --git a/package.json b/package.json index f3ede69..68cd6dd 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,8 @@ "worker-fs-mount": "0.2.0" }, "devDependencies": { - "wrangler": "4.129.0" + "workerd": "1.20260918.1", + "wrangler": "4.135.0" }, "scripts": { "build": "bash scripts/build.sh",