From 576d76dfd6796221c8093299c91e7d47f2e8e19d Mon Sep 17 00:00:00 2001 From: Bjorn Beishline <75190918+BjornTheProgrammer@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:12:48 -0700 Subject: [PATCH] feat: add wasm plugin api (#1675) * feat: wasm plugin api base * fix: make metadata macro work * chore: fix clippy lints * Implement guest registering of events * chore: remove hardcoded v0.1.0 dep in wasm_host * feat: add events to wasm * feat: add caching of components * fix: clippy lints * chore: update Cargo.lock * fix: add feature flag for link section * feat: add base for command wit definitions * feat: implement arg variants for command wit interface * feat: add commented out command-tree arg variant * feat: add wit definitions for text-component and command tree Introduces the text interface with a builder-pattern resource, argument-type variant, command-node resource, and register-command on context. Migrates event and command-sender to use the new text-component resource. * feat: add stubs for wasm host * chore: update wasmtime to v42 * feat: implement three methods of wit textcomponent * Add consume function * feat: implement all text-component host methods Replace todo!() stubs with working implementations for all HostTextComponent trait methods: style setters, click events, hover events, add_text, get_text, and encode. * feat: allow events to be modified * feat: add some of base for commands * chore: fix spelling mistake * chore: remove unused dep * feat: implement all argument-type to consumer mappings * Add command exports * log plugin load errors * Some commands work * Some more Commands work * Update how commands are registered * Implement locale * Use handler id with require * chore: fix some warnings * Add PlayerLeaveEvent * chore: fix all clippy warnings * fix: add more descriptive error message --------- Co-authored-by: Purdze Co-authored-by: Alexander Medvedev --- Cargo.lock | 1684 ++++++++++++++++- Cargo.toml | 9 + pumpkin-api-macros/src/lib.rs | 14 +- pumpkin-plugin-api/Cargo.toml | 14 + pumpkin-plugin-api/src/commands.rs | 59 + pumpkin-plugin-api/src/events.rs | 107 ++ pumpkin-plugin-api/src/lib.rs | 141 ++ pumpkin-plugin-api/src/logging.rs | 68 + pumpkin-plugin-wit/v0.1.0/command.wit | 189 ++ pumpkin-plugin-wit/v0.1.0/common.wit | 170 ++ pumpkin-plugin-wit/v0.1.0/context.wit | 11 + pumpkin-plugin-wit/v0.1.0/entity.wit | 36 + pumpkin-plugin-wit/v0.1.0/event.wit | 34 + pumpkin-plugin-wit/v0.1.0/log.wit | 14 + pumpkin-plugin-wit/v0.1.0/metadata.wit | 15 + pumpkin-plugin-wit/v0.1.0/player.wit | 5 + pumpkin-plugin-wit/v0.1.0/plugin.wit | 24 + pumpkin-plugin-wit/v0.1.0/server.wit | 12 + pumpkin-plugin-wit/v0.1.0/text.wit | 41 + pumpkin-plugin-wit/v0.1.0/world.wit | 5 + pumpkin/Cargo.toml | 7 + pumpkin/src/command/commands/plugin.rs | 6 +- pumpkin/src/command/commands/plugins.rs | 6 +- pumpkin/src/net/query.rs | 2 +- pumpkin/src/plugin/api/context.rs | 24 +- pumpkin/src/plugin/api/mod.rs | 10 +- pumpkin/src/plugin/loader/mod.rs | 12 +- pumpkin/src/plugin/loader/wasm/mod.rs | 65 + .../src/plugin/loader/wasm/wasm_host/args.rs | 85 + .../plugin/loader/wasm/wasm_host/logging.rs | 114 ++ .../src/plugin/loader/wasm/wasm_host/mod.rs | 186 ++ .../src/plugin/loader/wasm/wasm_host/state.rs | 146 ++ .../plugin/loader/wasm/wasm_host/wit/mod.rs | 1 + .../wasm_host/wit/v0_1_0/commands/executor.rs | 80 + .../wasm/wasm_host/wit/v0_1_0/commands/mod.rs | 656 +++++++ .../wasm/wasm_host/wit/v0_1_0/common.rs | 3 + .../wasm/wasm_host/wit/v0_1_0/context.rs | 119 ++ .../wasm/wasm_host/wit/v0_1_0/entity.rs | 81 + .../wasm/wasm_host/wit/v0_1_0/events/mod.rs | 74 + .../wasm_host/wit/v0_1_0/events/player.rs | 100 + .../wasm/wasm_host/wit/v0_1_0/logging.rs | 19 + .../loader/wasm/wasm_host/wit/v0_1_0/mod.rs | 71 + .../wasm/wasm_host/wit/v0_1_0/player.rs | 26 + .../wasm/wasm_host/wit/v0_1_0/server.rs | 36 + .../loader/wasm/wasm_host/wit/v0_1_0/text.rs | 292 +++ .../loader/wasm/wasm_host/wit/v0_1_0/world.rs | 18 + pumpkin/src/plugin/mod.rs | 24 +- 47 files changed, 4826 insertions(+), 89 deletions(-) create mode 100644 pumpkin-plugin-api/Cargo.toml create mode 100644 pumpkin-plugin-api/src/commands.rs create mode 100644 pumpkin-plugin-api/src/events.rs create mode 100644 pumpkin-plugin-api/src/lib.rs create mode 100644 pumpkin-plugin-api/src/logging.rs create mode 100644 pumpkin-plugin-wit/v0.1.0/command.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/common.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/context.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/entity.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/event.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/log.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/metadata.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/player.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/plugin.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/server.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/text.wit create mode 100644 pumpkin-plugin-wit/v0.1.0/world.wit create mode 100644 pumpkin/src/plugin/loader/wasm/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/args.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/logging.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/state.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/executor.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/common.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/context.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/player.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/logging.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/text.rs create mode 100644 pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs diff --git a/Cargo.lock b/Cargo.lock index bcdb2a3e4..c36fefa10 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "addr2line" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9698bf0769c641b18618039fe2ebd41eb3541f98433000f64e663fab7cea2c87" +dependencies = [ + "gimli", +] + [[package]] name = "adler2" version = "2.0.1" @@ -37,6 +46,27 @@ dependencies = [ "cc", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "ambient-authority" +version = "0.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d4ee0d472d1cd2e28c97dfa124b3d8d992e10eb0a035f33f5d12e3a177ba3b" + +[[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 = "anes" version = "0.1.6" @@ -55,6 +85,12 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" + [[package]] name = "arc-swap" version = "1.8.2" @@ -87,6 +123,15 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -178,6 +223,15 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -201,6 +255,9 @@ name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +dependencies = [ + "allocator-api2", +] [[package]] name = "byteorder" @@ -214,6 +271,84 @@ version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +[[package]] +name = "cap-fs-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5528f85b1e134ae811704e41ef80930f56e795923f866813255bc342cc20654" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "cap-net-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20a158160765c6a7d0d8c072a53d772e4cb243f38b04bfcf6b4939cfbe7482e7" +dependencies = [ + "cap-primitives", + "cap-std", + "rustix 1.1.4", + "smallvec", +] + +[[package]] +name = "cap-primitives" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cf3aea8a5081171859ef57bc1606b1df6999df4f1110f8eef68b30098d1d3a" +dependencies = [ + "ambient-authority", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix 1.1.4", + "rustix-linux-procfs", + "windows-sys 0.59.0", + "winx", +] + +[[package]] +name = "cap-rand" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8144c22e24bbcf26ade86cb6501a0916c46b7e4787abdb0045a467eb1645a1d" +dependencies = [ + "ambient-authority", + "rand 0.8.5", +] + +[[package]] +name = "cap-std" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6dc3090992a735d23219de5c204927163d922f42f575a0189b005c62d37549a" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", +] + +[[package]] +name = "cap-time-ext" +version = "3.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def102506ce40c11710a9b16e614af0cde8e76ae51b1f48c04b8d79f4b671a80" +dependencies = [ + "ambient-authority", + "cap-primitives", + "iana-time-zone", + "once_cell", + "rustix 1.1.4", + "winx", +] + [[package]] name = "cast" version = "0.3.0" @@ -227,6 +362,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -345,6 +482,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" +[[package]] +name = "cobs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + [[package]] name = "colored" version = "3.1.1" @@ -451,6 +597,21 @@ dependencies = [ "url", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpp_demangle" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bb79cb74d735044c972aae58ed0aaa9a837e85b01106a54c39e42e97f62253" +dependencies = [ + "cfg-if", +] + [[package]] name = "cpubits" version = "0.1.0" @@ -475,6 +636,147 @@ dependencies = [ "libc", ] +[[package]] +name = "cranelift-assembler-x64" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40630d663279bc855bff805d6f5e8a0b6a1867f9df95b010511ac6dc894e9395" +dependencies = [ + "cranelift-assembler-x64-meta", +] + +[[package]] +name = "cranelift-assembler-x64-meta" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ee6aec5ceb55e5fdbcf7ef677d7c7195531360ff181ce39b2b31df11d57305f" +dependencies = [ + "cranelift-srcgen", +] + +[[package]] +name = "cranelift-bforest" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a92d78cc3f087d7e7073828f08d98c7074a3a062b6b29a1b7783ce74305685e" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-bitset" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edcc73d756f2e0d7eda6144fe64a2bc69c624de893cb1be51f1442aed77881d2" +dependencies = [ + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d94c2cd0d73b41369b88da1129589bc3a2d99cf49979af1d14751f35b7a1b" +dependencies = [ + "bumpalo", + "cranelift-assembler-x64", + "cranelift-bforest", + "cranelift-bitset", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-control", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown 0.15.5", + "libm", + "log", + "pulley-interpreter", + "regalloc2", + "rustc-hash", + "serde", + "smallvec", + "target-lexicon", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235da0e52ee3a0052d0e944c3470ff025b1f4234f6ec4089d3109f2d2ffa6cbd" +dependencies = [ + "cranelift-assembler-x64-meta", + "cranelift-codegen-shared", + "cranelift-srcgen", + "heck", + "pulley-interpreter", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c07c6c440bd1bf920ff7597a1e743ede1f68dcd400730bd6d389effa7662af" + +[[package]] +name = "cranelift-control" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8797c022e02521901e1aee483dea3ed3c67f2bf0a26405c9dd48e8ee7a70944b" +dependencies = [ + "arbitrary", +] + +[[package]] +name = "cranelift-entity" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d8e72637246edd2cba337939850caa8b201f6315925ec4c156fdd089999699" +dependencies = [ + "cranelift-bitset", + "serde", + "serde_derive", + "wasmtime-internal-core", +] + +[[package]] +name = "cranelift-frontend" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c31db0085c3dfa131e739c3b26f9f9c84d69a9459627aac1ac4ef8355e3411b" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524d804c1ebd8c542e6f64e71aa36934cec17c5da4a9ae3799796220317f5d23" + +[[package]] +name = "cranelift-native" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc9598f02540e382e1772416eba18e93c5275b746adbbf06ac1f3cf149415270" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-srcgen" +version = "0.129.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d953932541249c91e3fa70a75ff1e52adc62979a2a8132145d4b9b3e6d1a9b6a" + [[package]] name = "crc-fast" version = "1.10.0" @@ -482,7 +784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" dependencies = [ "digest 0.10.7", - "spin", + "spin 0.10.0", ] [[package]] @@ -528,6 +830,12 @@ dependencies = [ "itertools 0.13.0", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crossbeam" version = "0.8.4" @@ -586,9 +894,9 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crossfire" -version = "3.1.4" +version = "3.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb12e9c05ae4854f743f0acec2f817148ba59a902484f6aa298d4fc7df2fac4" +checksum = "cf877d485f079160883c76912869ac04132035f7ea787712264d422793f808df" dependencies = [ "crossbeam-utils", "futures-core", @@ -616,9 +924,9 @@ dependencies = [ [[package]] name = "crypto-bigint" -version = "0.7.0-rc.28" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96dacf199529fb801ae62a9aafdc01b189e9504c0d1ee1512a4c16bcd8666a93" +checksum = "4f438b626cb7c9dd48a613a9826e6bad9db71097f9d628f7237af2f6bc13c0ec" dependencies = [ "cpubits", "ctutils", @@ -653,7 +961,7 @@ version = "0.7.0-pre.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6081ce8b60c0e533e2bba42771b94eb6149052115f4179744d5779883dc98583" dependencies = [ - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.0", "libm", "rand_core 0.10.0", ] @@ -681,6 +989,15 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "debugid" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" +dependencies = [ + "uuid", +] + [[package]] name = "der" version = "0.7.10" @@ -736,6 +1053,27 @@ dependencies = [ "ctutils", ] +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -797,6 +1135,27 @@ dependencies = [ "zeroize", ] +[[package]] +name = "embedded-io" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[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 = "endian-type" version = "0.1.2" @@ -850,7 +1209,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix", + "rustix 1.1.4", "windows-sys 0.59.0", ] @@ -870,6 +1229,12 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "flate2" version = "1.1.9" @@ -892,6 +1257,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "form_urlencoded" version = "1.2.2" @@ -901,6 +1272,17 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs-set-times" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94e7099f6313ecacbe1256e8ff9d617b75d1bcb16a6fddef94866d225a01a14a" +dependencies = [ + "io-lifetimes", + "rustix 1.1.4", + "windows-sys 0.59.0", +] + [[package]] name = "futures" version = "0.3.32" @@ -989,6 +1371,20 @@ dependencies = [ "slab", ] +[[package]] +name = "fxprof-processed-profile" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25234f20a3ec0a962a61770cfe39ecf03cb529a6e474ad8cff025ed497eda557" +dependencies = [ + "bitflags", + "debugid", + "rustc-hash", + "serde", + "serde_derive", + "serde_json", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -1011,6 +1407,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.1" @@ -1025,6 +1433,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gimli" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" +dependencies = [ + "fnv", + "hashbrown 0.16.1", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "group" version = "0.13.0" @@ -1066,6 +1486,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1078,7 +1507,8 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", + "serde", ] [[package]] @@ -1086,6 +1516,11 @@ name = "hashbrown" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] [[package]] name = "hdrhistogram" @@ -1100,6 +1535,20 @@ dependencies = [ "num-traits", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32", + "rustc_version", + "serde", + "spin 0.9.8", + "stable_deref_trait", +] + [[package]] name = "heck" version = "0.5.0" @@ -1264,6 +1713,30 @@ dependencies = [ "tracing", ] +[[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.1.1" @@ -1372,6 +1845,20 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "im-rc" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "sized-chunks", + "typenum", + "version_check", +] + [[package]] name = "indexmap" version = "2.13.0" @@ -1393,6 +1880,28 @@ dependencies = [ "generic-array", ] +[[package]] +name = "io-extras" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2285ddfe3054097ef4b2fe909ef8c3bcd1ea52a8f0d274416caebeef39f04a65" +dependencies = [ + "io-lifetimes", + "windows-sys 0.59.0", +] + +[[package]] +name = "io-lifetimes" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06432fb54d3be7964ecd3649233cddf80db2832f47fec34c01f65b3d9d774983" + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + [[package]] name = "itertools" version = "0.13.0" @@ -1417,6 +1926,36 @@ version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +[[package]] +name = "ittapi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b996fe614c41395cdaedf3cf408a9534851090959d90d54a535f675550b64b1" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52f5385394064fa2c886205dba02598013ce83d3e92d33dbdc0c52fe0e7bf4fc" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + [[package]] name = "js-sys" version = "0.3.87" @@ -1433,6 +1972,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "leb128fmt" version = "0.1.0" @@ -1461,6 +2006,22 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + [[package]] name = "linux-raw-sys" version = "0.12.1" @@ -1510,6 +2071,15 @@ version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08ab2867e3eeeca90e844d1940eab391c9dc5228783db2ed999acbc0a9ed375a" +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1525,6 +2095,12 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + [[package]] name = "md-5" version = "0.10.6" @@ -1547,6 +2123,15 @@ version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memfd" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38eb12aea514a0466ea40a80fd8cc83637065948eb4a426e4aa46261175227" +dependencies = [ + "rustix 1.1.4", +] + [[package]] name = "mime" version = "0.3.17" @@ -1676,6 +2261,18 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "crc32fast", + "hashbrown 0.15.5", + "indexmap", + "memchr", +] + [[package]] name = "once_cell" version = "1.21.3" @@ -1806,6 +2403,16 @@ dependencies = [ "sha2 0.10.9", ] +[[package]] +name = "petgraph" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +dependencies = [ + "fixedbitset", + "indexmap", +] + [[package]] name = "phf" version = "0.13.1" @@ -1871,9 +2478,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.17" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1911,6 +2518,46 @@ dependencies = [ "spki 0.8.0-rc.4", ] +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "postcard" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" +dependencies = [ + "cobs", + "embedded-io 0.4.0", + "embedded-io 0.6.1", + "heapless", + "serde", +] + +[[package]] +name = "postcard-derive" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0232bd009a197ceec9cc881ba46f727fcd8060a2d8d6a9dde7a69030a6fe2bb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "postcard-schema" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475666d89f42231a0a57da32d5f6ca7f9b5cd4c335ea1fe8f3278215b7a21ff" +dependencies = [ + "postcard-derive", + "serde", +] + [[package]] name = "potential_utf" version = "0.1.4" @@ -1926,6 +2573,15 @@ 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 = "prettyplease" version = "0.2.37" @@ -2008,6 +2664,29 @@ dependencies = [ "prost", ] +[[package]] +name = "pulley-interpreter" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc2d61e068654529dc196437f8df0981db93687fdc67dec6a5de92363120b9da" +dependencies = [ + "cranelift-bitset", + "log", + "pulley-macros", + "wasmtime-internal-core", +] + +[[package]] +name = "pulley-macros" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f210c61b6ecfaebbba806b6d9113a222519d4e5cc4ab2d5ecca047bb7927ae" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pumpkin" version = "0.1.0-dev+1.21.11" @@ -2023,6 +2702,7 @@ dependencies = [ "libloading", "num-bigint", "pkcs8 0.11.0-rc.11", + "postcard", "pumpkin-config", "pumpkin-data", "pumpkin-inventory", @@ -2031,7 +2711,7 @@ dependencies = [ "pumpkin-protocol", "pumpkin-util", "pumpkin-world", - "rand", + "rand 0.10.0", "rsa", "rustc-hash", "rustyline", @@ -2040,14 +2720,18 @@ dependencies = [ "sha1", "sha2 0.11.0-rc.5", "tempfile", - "thiserror", + "thiserror 2.0.18", "time", "tokio", "tokio-util", "tracing", + "tracing-serde-structured", "tracing-subscriber", "ureq", "uuid", + "wasmparser 0.245.1", + "wasmtime", + "wasmtime-wasi", ] [[package]] @@ -2066,7 +2750,7 @@ version = "0.1.0-dev+1.21.11" dependencies = [ "pumpkin-util", "serde", - "toml", + "toml 1.0.3+spec-1.1.0", "tracing", "uuid", ] @@ -2092,7 +2776,7 @@ dependencies = [ "pumpkin-protocol", "pumpkin-util", "pumpkin-world", - "thiserror", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -2116,7 +2800,17 @@ dependencies = [ "flate2", "serde", "tempfile", - "thiserror", + "thiserror 2.0.18", +] + +[[package]] +name = "pumpkin-plugin-api" +version = "0.1.0" +dependencies = [ + "postcard", + "tracing", + "tracing-serde-structured", + "wit-bindgen 0.53.1", ] [[package]] @@ -2136,7 +2830,7 @@ dependencies = [ "pumpkin-world", "serde", "take_mut", - "thiserror", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -2160,7 +2854,7 @@ dependencies = [ "serde", "serde_json", "syn", - "thiserror", + "thiserror 2.0.18", "tokio", "uuid", ] @@ -2185,7 +2879,7 @@ dependencies = [ "pumpkin-data", "pumpkin-nbt", "pumpkin-util", - "rand", + "rand 0.10.0", "rustc-hash", "ruzstd", "serde", @@ -2193,7 +2887,7 @@ dependencies = [ "sha2 0.11.0-rc.5", "slotmap", "temp-dir", - "thiserror", + "thiserror 2.0.18", "tokio", "tokio-util", "tracing", @@ -2202,9 +2896,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.45" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -2225,6 +2919,17 @@ dependencies = [ "nibble_vec", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + [[package]] name = "rand" version = "0.10.0" @@ -2236,6 +2941,16 @@ dependencies = [ "rand_core 0.10.0", ] +[[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" @@ -2251,6 +2966,35 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +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 = "redox_syscall" version = "0.5.18" @@ -2260,6 +3004,31 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 1.0.69", +] + +[[package]] +name = "regalloc2" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08effbc1fa53aaebff69521a5c05640523fab037b34a4a2c109506bc938246fa" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown 0.15.5", + "log", + "rustc-hash", + "smallvec", +] + [[package]] name = "regex" version = "1.12.3" @@ -2335,7 +3104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb9fd8c1edd9e6a2693623baf0fe77ff05ce022a5d7746900ffc38a15c233de" dependencies = [ "const-oid 0.10.2", - "crypto-bigint 0.7.0-rc.28", + "crypto-bigint 0.7.0", "crypto-primes", "digest 0.11.0", "pkcs1", @@ -2346,12 +3115,40 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustc-hash" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[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 = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.4" @@ -2361,10 +3158,20 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] +[[package]] +name = "rustix-linux-procfs" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc84bf7e9aa16c4f2c758f27412dc9841341e16aa682d9c7ac308fe3ee12056" +dependencies = [ + "once_cell", + "rustix 1.1.4", +] + [[package]] name = "rustls" version = "0.23.36" @@ -2437,6 +3244,12 @@ dependencies = [ "twox-hash 2.1.2", ] +[[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" @@ -2471,6 +3284,10 @@ name = "semver" version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] [[package]] name = "serde" @@ -2535,6 +3352,19 @@ dependencies = [ "serde_core", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.4.2" @@ -2635,6 +3465,16 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + [[package]] name = "slab" version = "0.4.12" @@ -2655,6 +3495,9 @@ name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2666,6 +3509,15 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spin" version = "0.10.0" @@ -2738,12 +3590,34 @@ dependencies = [ "syn", ] +[[package]] +name = "system-interface" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4592f674ce18521c2a81483873a49596655b179f71c5e05d10c1fe66c78745" +dependencies = [ + "bitflags", + "cap-fs-ext", + "cap-std", + "fd-lock", + "io-lifetimes", + "rustix 0.38.44", + "windows-sys 0.59.0", + "winx", +] + [[package]] name = "take_mut" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "target-lexicon" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca" + [[package]] name = "temp-dir" version = "0.2.0" @@ -2757,18 +3631,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0" dependencies = [ "fastrand", + "getrandom 0.4.1", "once_cell", - "rustix", + "rustix 1.1.4", "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 = "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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl", + "thiserror-impl 2.0.18", +] + +[[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", ] [[package]] @@ -2861,9 +3765,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.6.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2", "quote", @@ -2895,6 +3799,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml" version = "1.0.3+spec-1.1.0" @@ -2904,12 +3823,21 @@ dependencies = [ "indexmap", "serde_core", "serde_spanned", - "toml_datetime", + "toml_datetime 1.0.0+spec-1.1.0", "toml_parser", "toml_writer", "winnow", ] +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_datetime" version = "1.0.0+spec-1.1.0" @@ -3048,6 +3976,19 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-serde-structured" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0832510e9838a4ff7e45e278602ab0533686f9507bc6189e024e488602f29820" +dependencies = [ + "hash32", + "heapless", + "postcard-schema", + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.19" @@ -3125,6 +4066,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" version = "0.9.0" @@ -3249,7 +4196,7 @@ version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3258,7 +4205,7 @@ version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ - "wit-bindgen", + "wit-bindgen 0.51.0", ] [[package]] @@ -3306,6 +4253,27 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-compose" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92cda9c76ca8dcac01a8b497860c2cb15cd6f216dc07060517df5abbe82512ac" +dependencies = [ + "anyhow", + "heck", + "im-rc", + "indexmap", + "log", + "petgraph", + "serde", + "serde_derive", + "serde_yaml", + "smallvec", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", + "wat", +] + [[package]] name = "wasm-encoder" version = "0.244.0" @@ -3313,7 +4281,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", - "wasmparser", + "wasmparser 0.244.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]] @@ -3324,8 +4302,20 @@ checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", - "wasm-encoder", - "wasmparser", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasm-metadata" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da55e60097e8b37b475a0fa35c3420dd71d9eb7bd66109978ab55faf56a57efb" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder 0.245.1", + "wasmparser 0.245.1", ] [[package]] @@ -3338,6 +4328,358 @@ dependencies = [ "hashbrown 0.15.5", "indexmap", "semver", + "serde", +] + +[[package]] +name = "wasmparser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f08c9adee0428b7bddf3890fc27e015ac4b761cc608c822667102b8bfd6995e" +dependencies = [ + "bitflags", + "hashbrown 0.16.1", + "indexmap", + "semver", + "serde", +] + +[[package]] +name = "wasmprinter" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09390d7b2bd7b938e563e4bff10aa345ef2e27a3bc99135697514ef54495e68f" +dependencies = [ + "anyhow", + "termcolor", + "wasmparser 0.244.0", +] + +[[package]] +name = "wasmtime" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39bef52be4fb4c5b47d36f847172e896bc94b35c9c6a6f07117686bd16ed89a7" +dependencies = [ + "addr2line", + "async-trait", + "bitflags", + "bumpalo", + "cc", + "cfg-if", + "encoding_rs", + "futures", + "fxprof-processed-profile", + "gimli", + "ittapi", + "libc", + "log", + "mach2", + "memfd", + "object", + "once_cell", + "postcard", + "pulley-interpreter", + "rayon", + "rustix 1.1.4", + "semver", + "serde", + "serde_derive", + "serde_json", + "smallvec", + "target-lexicon", + "tempfile", + "wasm-compose", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", + "wasmtime-environ", + "wasmtime-internal-cache", + "wasmtime-internal-component-macro", + "wasmtime-internal-component-util", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", + "wasmtime-internal-fiber", + "wasmtime-internal-jit-debug", + "wasmtime-internal-jit-icache-coherence", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", + "wasmtime-internal-winch", + "wat", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-environ" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb637d5aa960ac391ca5a4cbf3e45807632e56beceeeb530e14dfa67fdfccc62" +dependencies = [ + "anyhow", + "cpp_demangle", + "cranelift-bitset", + "cranelift-entity", + "gimli", + "hashbrown 0.15.5", + "indexmap", + "log", + "object", + "postcard", + "rustc-demangle", + "semver", + "serde", + "serde_derive", + "smallvec", + "target-lexicon", + "wasm-encoder 0.244.0", + "wasmparser 0.244.0", + "wasmprinter", + "wasmtime-internal-component-util", + "wasmtime-internal-core", +] + +[[package]] +name = "wasmtime-internal-cache" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ab6c428c610ae3e7acd25ca2681b4d23672c50d8769240d9dda99b751d4deec" +dependencies = [ + "base64 0.22.1", + "directories-next", + "log", + "postcard", + "rustix 1.1.4", + "serde", + "serde_derive", + "sha2 0.10.9", + "toml 0.9.12+spec-1.1.0", + "wasmtime-environ", + "windows-sys 0.61.2", + "zstd", +] + +[[package]] +name = "wasmtime-internal-component-macro" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca768b11d5e7de017e8c3d4d444da6b4ce3906f565bcbc253d76b4ecbb5d2869" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wasmtime-internal-component-util", + "wasmtime-internal-wit-bindgen", + "wit-parser 0.244.0", +] + +[[package]] +name = "wasmtime-internal-component-util" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763f504faf96c9b409051e96a1434655eea7f56a90bed9cb1e22e22c941253fd" + +[[package]] +name = "wasmtime-internal-core" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a4a3f055a804a2f3d86e816a9df78a8fa57762212a8506164959224a40cd48" +dependencies = [ + "anyhow", + "libm", +] + +[[package]] +name = "wasmtime-internal-cranelift" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55154a91d22ad51f9551124ce7fb49ddddc6a82c4910813db4c790c97c9ccf32" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "cranelift-control", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "gimli", + "itertools 0.14.0", + "log", + "object", + "pulley-interpreter", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.244.0", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-unwinder", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-fiber" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05decfad1021ad2efcca5c1be9855acb54b6ee7158ac4467119b30b7481508e3" +dependencies = [ + "cc", + "cfg-if", + "libc", + "rustix 1.1.4", + "wasmtime-environ", + "wasmtime-internal-versioned-export-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-jit-debug" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924980c50427885fd4feed2049b88380178e567768aaabf29045b02eb262eaa7" +dependencies = [ + "cc", + "object", + "rustix 1.1.4", + "wasmtime-internal-versioned-export-macros", +] + +[[package]] +name = "wasmtime-internal-jit-icache-coherence" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c57d24e8d1334a0e5a8b600286ffefa1fc4c3e8176b110dff6fbc1f43c4a599b" +dependencies = [ + "cfg-if", + "libc", + "wasmtime-internal-core", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-internal-unwinder" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a1a144bd4393593a868ba9df09f34a6a360cb5db6e71815f20d3f649c6e6735" +dependencies = [ + "cfg-if", + "cranelift-codegen", + "log", + "object", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-internal-versioned-export-macros" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a6948b56bb00c62dbd205ea18a4f1ceccbe1e4b8479651fdb0bab2553790f20" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasmtime-internal-winch" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9130b3ab6fb01be80b27b9a2c84817af29ae8224094f2503d2afa9fea5bf9d00" +dependencies = [ + "cranelift-codegen", + "gimli", + "log", + "object", + "target-lexicon", + "wasmparser 0.244.0", + "wasmtime-environ", + "wasmtime-internal-cranelift", + "winch-codegen", +] + +[[package]] +name = "wasmtime-internal-wit-bindgen" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102d0d70dbfede00e4cc9c24e86df6d32c03bf6f5ad06b5d6c76b0a4a5004c4a" +dependencies = [ + "anyhow", + "bitflags", + "heck", + "indexmap", + "wit-parser 0.244.0", +] + +[[package]] +name = "wasmtime-wasi" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea938f6f4f11e5ffe6d8b6f34c9a994821db9511c3e9c98e535896f27d06bb92" +dependencies = [ + "async-trait", + "bitflags", + "bytes", + "cap-fs-ext", + "cap-net-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "futures", + "io-extras", + "io-lifetimes", + "rustix 1.1.4", + "system-interface", + "thiserror 2.0.18", + "tokio", + "tracing", + "url", + "wasmtime", + "wasmtime-wasi-io", + "wiggle", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasmtime-wasi-io" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71cb16a88d0443b509d6eca4298617233265179090abf03e0a8042b9b251e9da" +dependencies = [ + "async-trait", + "bytes", + "futures", + "tracing", + "wasmtime", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "245.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cf1149285569120b8ce39db8b465e8a2b55c34cbb586bd977e43e2bc7300bf" +dependencies = [ + "bumpalo", + "leb128fmt", + "memchr", + "unicode-width", + "wasm-encoder 0.245.1", +] + +[[package]] +name = "wat" +version = "1.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd48d1679b6858988cb96b154dda0ec5bbb09275b71db46057be37332d5477be" +dependencies = [ + "wast 245.0.1", ] [[package]] @@ -3349,6 +4691,46 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "wiggle" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dca2bf96d20f0c70e6741cc6c8c1a9ee4c3c0310c7ad1971242628c083cc9a5" +dependencies = [ + "bitflags", + "thiserror 2.0.18", + "tracing", + "wasmtime", + "wasmtime-environ", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0d8c016d6d3ec6dc6b8c80c23cede4ee2386ccf347d01984f7991d7659f73ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", + "wasmtime-environ", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91a267096e48857096f035fffca29e22f0bbe840af4d74a6725eb695e1782110" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3380,12 +4762,84 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "winch-codegen" +version = "42.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1977857998e4dd70d26e2bfc0618a9684a2fb65b1eca174dc13f3b3e9c2159ca" +dependencies = [ + "cranelift-assembler-x64", + "cranelift-codegen", + "gimli", + "regalloc2", + "smallvec", + "target-lexicon", + "thiserror 2.0.18", + "wasmparser 0.244.0", + "wasmtime-environ", + "wasmtime-internal-core", + "wasmtime-internal-cranelift", +] + +[[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", +] + +[[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", +] + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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" @@ -3557,13 +5011,33 @@ version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +[[package]] +name = "winx" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f3fd376f71958b862e7afb20cfe5a22830e1963462f3a17f49d82a6c1d1f42d" +dependencies = [ + "bitflags", + "windows-sys 0.59.0", +] + [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ - "wit-bindgen-rust-macro", + "wit-bindgen-rust-macro 0.51.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e915216dde3e818093168df8380a64fba25df468d626c80dd5d6a184c87e7c7" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro 0.53.1", ] [[package]] @@ -3574,7 +5048,18 @@ checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", - "wit-parser", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deda4b7e9f522d994906f6e6e0fc67965ea8660306940a776b76732be8f3933" +dependencies = [ + "anyhow", + "heck", + "wit-parser 0.245.1", ] [[package]] @@ -3588,9 +5073,25 @@ dependencies = [ "indexmap", "prettyplease", "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", + "wasm-metadata 0.244.0", + "wit-bindgen-core 0.51.0", + "wit-component 0.244.0", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863a7ab3c4dfee58db196811caeb0718b88412a0aef3d1c2b02fcbae1e37c688" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata 0.245.1", + "wit-bindgen-core 0.53.1", + "wit-component 0.245.1", ] [[package]] @@ -3604,8 +5105,23 @@ dependencies = [ "proc-macro2", "quote", "syn", - "wit-bindgen-core", - "wit-bindgen-rust", + "wit-bindgen-core 0.51.0", + "wit-bindgen-rust 0.51.0", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d14f3a9bfa3804bb0e9ab7f66da047f210eded6a1297ae3ba5805b384d64797f" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core 0.53.1", + "wit-bindgen-rust 0.53.1", ] [[package]] @@ -3621,10 +5137,29 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", + "wasm-encoder 0.244.0", + "wasm-metadata 0.244.0", + "wasmparser 0.244.0", + "wit-parser 0.244.0", +] + +[[package]] +name = "wit-component" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4894f10d2d5cbc17c77e91f86a1e48e191a788da4425293b55c98b44ba3fcac9" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder 0.245.1", + "wasm-metadata 0.245.1", + "wasmparser 0.245.1", + "wit-parser 0.245.1", ] [[package]] @@ -3642,7 +5177,38 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser", + "wasmparser 0.244.0", +] + +[[package]] +name = "wit-parser" +version = "0.245.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330698718e82983499419494dd1e3d7811a457a9bf9f69734e8c5f07a2547929" +dependencies = [ + "anyhow", + "hashbrown 0.16.1", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser 0.245.1", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror 1.0.69", + "wast 35.0.2", ] [[package]] @@ -3759,3 +5325,31 @@ name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index f0bd99838..02aca904d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "pumpkin-world", "pumpkin/", "pumpkin-data", + "pumpkin-plugin-api", ] exclude = ["pumpkin-codegen"] @@ -171,3 +172,11 @@ arc-swap = "1.8" tokio-util = "0.7.18" toml = "1.0" ureq = "3.2.0" + +wasmtime = "42.0" +wasmtime-wasi = "42.0" +wit-bindgen = "0.53" +wasmparser = "0.245" + +postcard = { version = "1.0", features = ["alloc"] } +tracing-serde-structured = "0.4" diff --git a/pumpkin-api-macros/src/lib.rs b/pumpkin-api-macros/src/lib.rs index 9b75d974c..46974d762 100644 --- a/pumpkin-api-macros/src/lib.rs +++ b/pumpkin-api-macros/src/lib.rs @@ -81,12 +81,14 @@ pub fn plugin_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { std::sync::LazyLock::new(|| std::sync::Arc::new(tokio::runtime::Runtime::new().unwrap())); #[unsafe(no_mangle)] - pub static METADATA: pumpkin::plugin::PluginMetadata = pumpkin::plugin::PluginMetadata { - name: env!("CARGO_PKG_NAME"), - version: env!("CARGO_PKG_VERSION"), - authors: env!("CARGO_PKG_AUTHORS"), - description: env!("CARGO_PKG_DESCRIPTION"), - }; + pub static METADATA: std::sync::LazyLock = std::sync::LazyLock::new(|| { + pumpkin::plugin::PluginMetadata { + name: env!("CARGO_PKG_NAME").to_string(), + version: env!("CARGO_PKG_VERSION").to_string(), + authors: env!("CARGO_PKG_AUTHORS").split(',').map(String::from).collect(), + description: env!("CARGO_PKG_DESCRIPTION").to_string(), + } + }); #[unsafe(no_mangle)] pub static PUMPKIN_API_VERSION: u32 = pumpkin::plugin::PLUGIN_API_VERSION; diff --git a/pumpkin-plugin-api/Cargo.toml b/pumpkin-plugin-api/Cargo.toml new file mode 100644 index 000000000..00231bd73 --- /dev/null +++ b/pumpkin-plugin-api/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pumpkin-plugin-api" +# This should be different then the pumpkin server verion +version = "0.1.0" +edition.workspace = true + +[dependencies] +wit-bindgen = { workspace = true } +tracing = { workspace = true } +postcard = { workspace = true } +tracing-serde-structured = { workspace = true } + +[package.metadata.component] +target = { path = "../pumpkin-plugin-wit" } diff --git a/pumpkin-plugin-api/src/commands.rs b/pumpkin-plugin-api/src/commands.rs new file mode 100644 index 000000000..ce99bc83f --- /dev/null +++ b/pumpkin-plugin-api/src/commands.rs @@ -0,0 +1,59 @@ +use std::{ + collections::BTreeMap, + sync::{ + Mutex, + atomic::{AtomicU32, Ordering}, + }, +}; + +pub use crate::wit::pumpkin::plugin::command::Command; +use crate::{ + Result, Server, + command::CommandNode, + wit::pumpkin::plugin::command::{CommandError, CommandSender, ConsumedArgs}, +}; + +pub(crate) static NEXT_COMMAND_ID: AtomicU32 = AtomicU32::new(0); +pub(crate) static COMMAND_HANDLERS: Mutex>> = + Mutex::new(BTreeMap::new()); + +pub trait CommandHandler: Send + Sync { + fn handle( + &self, + sender: CommandSender, + server: Server, + args: ConsumedArgs, + ) -> Result; +} + +impl Command { + /// Registers a command handler with the plugin. + pub fn execute(self, handler: H) -> Command { + let id = NEXT_COMMAND_ID.fetch_add(1, Ordering::Relaxed); + + COMMAND_HANDLERS + .lock() + .unwrap() + .insert(id, Box::new(handler)); + + self.execute_with_handler_id(id); + + self + } +} + +impl CommandNode { + /// Registers a command handler with the plugin. + pub fn execute(self, handler: H) -> CommandNode { + let id = NEXT_COMMAND_ID.fetch_add(1, Ordering::Relaxed); + + COMMAND_HANDLERS + .lock() + .unwrap() + .insert(id, Box::new(handler)); + + self.execute_with_handler_id(id); + + self + } +} diff --git a/pumpkin-plugin-api/src/events.rs b/pumpkin-plugin-api/src/events.rs new file mode 100644 index 000000000..3c14d7abd --- /dev/null +++ b/pumpkin-plugin-api/src/events.rs @@ -0,0 +1,107 @@ +use std::{ + collections::BTreeMap, + marker::PhantomData, + pin::Pin, + sync::{ + Mutex, + atomic::{AtomicU32, Ordering}, + }, +}; + +pub use crate::wit::pumpkin::plugin::event::{ + Event, EventPriority, PlayerJoinEventData, PlayerLeaveEventData, +}; +use crate::{Context, Result, Server, wit::pumpkin::plugin::event::EventType}; + +pub(crate) static NEXT_HANDLER_ID: AtomicU32 = AtomicU32::new(0); +pub(crate) static EVENT_HANDLERS: Mutex>> = + Mutex::new(BTreeMap::new()); + +pub trait FromIntoEvent: Sized { + const EVENT_TYPE: EventType; + + fn from_event(event: Event) -> Self; + fn into_event(self) -> Event; +} + +impl FromIntoEvent for PlayerJoinEventData { + const EVENT_TYPE: EventType = EventType::PlayerJoinEvent; + + fn from_event(event: Event) -> Self { + match event { + Event::PlayerJoinEvent(data) => data, + _ => panic!("unexpected event"), + } + } + + fn into_event(self) -> Event { + Event::PlayerJoinEvent(self) + } +} + +impl FromIntoEvent for PlayerLeaveEventData { + const EVENT_TYPE: EventType = EventType::PlayerLeaveEvent; + + fn from_event(event: Event) -> Self { + match event { + Event::PlayerLeaveEvent(data) => data, + _ => panic!("unexpected event"), + } + } + + fn into_event(self) -> Event { + Event::PlayerLeaveEvent(self) + } +} + +pub type BoxFuture<'a, T> = Pin + Send + 'a>>; +pub trait EventHandler { + fn handle(&self, server: Server, event: E) -> E; +} + +pub(crate) trait ErasedEventHandler: Send + Sync { + fn handle_erased(&self, server: Server, event: Event) -> Event; +} + +struct HandlerWrapper { + handler: H, + _phantom: PhantomData, +} + +impl + Send + Sync> ErasedEventHandler + for HandlerWrapper +{ + fn handle_erased(&self, server: Server, event: Event) -> Event { + let specific_event = E::from_event(event); + self.handler.handle(server, specific_event).into_event() + } +} + +impl Context { + /// Registers an event handler with the plugin. + /// + /// The handler must implement the [`EventHandler`] trait. + /// If the event is blocking, returning an event from the handler will modify the event. + pub fn register_event_handler< + E: FromIntoEvent + Send + Sync + 'static, + H: EventHandler + Send + Sync + 'static, + >( + &self, + handler: H, + event_priority: EventPriority, + blocking: bool, + ) -> Result { + let id = NEXT_HANDLER_ID.fetch_add(1, Ordering::Relaxed); + let wrapped = HandlerWrapper { + handler, + _phantom: PhantomData::, + }; + EVENT_HANDLERS + .lock() + .map_err(|e| e.to_string())? + .insert(id, Box::new(wrapped)); + + self.register_event(id, E::EVENT_TYPE, event_priority, blocking); + Ok(id) + } +} diff --git a/pumpkin-plugin-api/src/lib.rs b/pumpkin-plugin-api/src/lib.rs new file mode 100644 index 000000000..e2c28b32e --- /dev/null +++ b/pumpkin-plugin-api/src/lib.rs @@ -0,0 +1,141 @@ +use crate::{ + commands::COMMAND_HANDLERS, events::EVENT_HANDLERS, logging::WitSubscriber, text::TextComponent, +}; + +pub mod commands; +pub mod events; + +pub mod command { + pub use crate::wit::pumpkin::plugin::command::{ + Command, CommandError, CommandNode, CommandSender, ConsumedArgs, + }; +} + +pub use wit::pumpkin::plugin::{ + context::{Context, Server}, + text, +}; + +pub mod logging; + +mod wit { + wit_bindgen::generate!({ + skip: ["init-plugin"], + path: "../pumpkin-plugin-wit/v0.1.0", + world: "plugin", + }); + + use super::Component; + export!(Component); +} + +#[cfg(target_arch = "wasm32")] +#[unsafe(link_section = "pumpkin:api-version")] +#[used] +static API_VERSION: [u8; 5] = *b"0.1.0"; + +struct Component; +pub struct PluginMetadata { + pub name: String, + pub version: String, + pub authors: Vec, + pub description: String, +} + +impl wit::exports::pumpkin::plugin::metadata::Guest for Component { + fn get_metadata() -> wit::exports::pumpkin::plugin::metadata::PluginMetadata { + let metadata = plugin().metadata(); + wit::exports::pumpkin::plugin::metadata::PluginMetadata { + name: metadata.name, + version: metadata.version, + authors: metadata.authors, + description: metadata.description, + } + } +} + +impl wit::Guest for Component { + fn on_load(context: Context) -> Result<(), String> { + plugin().on_load(context) + } + + fn on_unload(context: Context) -> Result<(), String> { + plugin().on_unload(context) + } + + fn handle_event(event_id: u32, server: Server, event: events::Event) -> events::Event { + let handlers = EVENT_HANDLERS.lock().unwrap(); + if let Some(handler) = handlers.get(&event_id) { + handler.handle_erased(server, event) + } else { + event + } + } + + fn handle_command( + command_id: u32, + sender: command::CommandSender, + server: Server, + args: command::ConsumedArgs, + ) -> Result { + let handlers = COMMAND_HANDLERS.lock().unwrap(); + if let Some(handler) = handlers.get(&command_id) { + handler.handle(sender, server, args) + } else { + Err(command::CommandError::CommandFailed(TextComponent::text( + &format!("no handler registered for command id {command_id}"), + ))) + } + } +} + +pub type Result = core::result::Result; + +/// The trait that every Pumpkin plugin must implement. +pub trait Plugin: Send + Sync { + /// Create a new instance of the plugin. + fn new() -> Self + where + Self: Sized; + + /// Define the metadata for the plugin. + fn metadata(&self) -> PluginMetadata; + + /// Called when the plugin is loaded by the server. + fn on_load(&mut self, _context: Context) -> Result<()> { + Ok(()) + } + + /// Called when the plugin is unloaded by the server. + fn on_unload(&mut self, _context: Context) -> Result<()> { + Ok(()) + } +} + +#[doc(hidden)] +pub fn register_plugin(build_plugin: fn() -> Box) { + let _ = tracing::subscriber::set_global_default(WitSubscriber::new()); + unsafe { PLUGIN = Some((build_plugin)()) } +} + +fn plugin() -> &'static mut dyn Plugin { + #[expect(static_mut_refs)] + unsafe { + PLUGIN.as_deref_mut().unwrap() + } +} + +static mut PLUGIN: Option> = None; + +/// Registers the provided type as a Pumpkin plugin. +/// +/// The type must implement the [`Plugin`] trait. +#[macro_export] +macro_rules! register_plugin { + ($plugin_type:ty) => { + #[unsafe(export_name = "init-plugin")] + pub extern "C" fn __init_plugin() { + $crate::register_plugin(|| Box::new(<$plugin_type as $crate::Plugin>::new())); + } + }; +} diff --git a/pumpkin-plugin-api/src/logging.rs b/pumpkin-plugin-api/src/logging.rs new file mode 100644 index 000000000..1a3dbaa87 --- /dev/null +++ b/pumpkin-plugin-api/src/logging.rs @@ -0,0 +1,68 @@ +use std::sync::atomic::{AtomicU64, Ordering}; + +use tracing_serde_structured::AsSerde; + +use crate::wit; + +pub(crate) struct WitSubscriber { + next_id: AtomicU64, +} + +impl WitSubscriber { + pub fn new() -> Self { + Self { + next_id: AtomicU64::new(1), + } + } +} + +impl tracing::Subscriber for WitSubscriber { + fn enabled(&self, _metadata: &tracing::Metadata<'_>) -> bool { + true + } + + fn new_span(&self, _attrs: &tracing::span::Attributes<'_>) -> tracing::span::Id { + let id = self.next_id.fetch_add(1, Ordering::Relaxed); + tracing::span::Id::from_u64(id) + } + + fn record(&self, _span: &tracing::span::Id, _values: &tracing::span::Record<'_>) {} + + fn record_follows_from(&self, _span: &tracing::span::Id, _follows: &tracing::span::Id) {} + + fn event(&self, event: &tracing::Event<'_>) { + let serialized = + postcard::to_allocvec(&event.as_serde()).expect("failed to serialize tracing event"); + wit::pumpkin::plugin::logging::log_tracing(&serialized); + } + + fn enter(&self, _span: &tracing::span::Id) {} + + fn exit(&self, _span: &tracing::span::Id) {} +} + +/// The log severity level. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +impl LogLevel { + fn to_wit(self) -> wit::pumpkin::plugin::logging::Level { + match self { + LogLevel::Trace => wit::pumpkin::plugin::logging::Level::Trace, + LogLevel::Debug => wit::pumpkin::plugin::logging::Level::Debug, + LogLevel::Info => wit::pumpkin::plugin::logging::Level::Info, + LogLevel::Warn => wit::pumpkin::plugin::logging::Level::Warn, + LogLevel::Error => wit::pumpkin::plugin::logging::Level::Error, + } + } +} + +pub fn log(level: LogLevel, message: &str) { + wit::pumpkin::plugin::logging::log(level.to_wit(), message); +} diff --git a/pumpkin-plugin-wit/v0.1.0/command.wit b/pumpkin-plugin-wit/v0.1.0/command.wit new file mode 100644 index 000000000..b5fdec71a --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/command.wit @@ -0,0 +1,189 @@ +interface command { + use player.{player}; + use %world.{%world}; + use entity.{command-block-entity, entity}; + use common.{position, block-position, locale}; + use server.{server, difficulty}; + use text.{text-component}; + + enum gamemode { + survival, + creative, + adventure, + spectator, + } + + enum bossbar-color { + pink, + blue, + red, + green, + yellow, + purple, + white, + } + + enum bossbar-style { + no-division, + notches6, + notches10, + notches12, + notches20, + } + + enum sound-category { + master, + music, + records, + weather, + blocks, + hostile, + neutral, + players, + ambient, + voice, + ui, + } + + enum entity-anchor { + feet, + eyes, + } + + variant number { + float64(f64), + float32(f32), + int32(s32), + int64(s64), + } + + variant not-in-bounds { + lower-bound(tuple), + upper-bound(tuple), + } + + variant arg { + entities(list), + entity(entity), + players(list), + block-pos(block-position), + pos3d(position), + pos2d(tuple), + rotation(tuple), + game-mode(gamemode), + difficulty(difficulty), + item(string), + item-predicate(string), + resource-location(string), + block(string), + block-predicate(string), + bossbar-color(bossbar-color), + bossbar-style(bossbar-style), + particle(string), + msg(string), + text-component(text-component), + time(s32), + num(result), + %bool(bool), + simple(string), + sound-category(sound-category), + damage-type(string), + effect(string), + enchantment(string), + entity-anchor(entity-anchor), + } + + resource consumed-args { + get-value: func(key: string) -> arg; + } + + variant command-sender-type { + rcon(list), + console, + player(player), + command-block(tuple), + dummy, + } + + enum permission-level { + zero, + one, + two, + three, + four, + } + + resource command-sender { + get-command-sender-type: func() -> command-sender-type; + send-message: func(text: text-component); + set-success-count: func(count: s32); + is-player: func() -> bool; + is-console: func() -> bool; + as-player: func() -> option; + permission-level: func() -> permission-level; + has-permission-level: func(level: permission-level) -> bool; + has-permission: func(server: borrow, node: string) -> bool; + position: func() -> option; + %world: func() -> option<%world>; + get-locale: func() -> locale; + should-receive-feedback: func() -> bool; + should-broadcast-console-to-ops: func() -> bool; + should-track-output: func() -> bool; + } + + enum string-type { + single-word, + quotable, + greedy, + } + + variant argument-type { + %bool, + float(tuple, option>), + double(tuple, option>), + integer(tuple, option>), + long(tuple, option>), + %string(string-type), + entities, + entity, + %players, + game-profile, + block-pos, + position3d, + position2d, + block-state, + block-predicate, + item, + item-predicate, + component, + rotation, + %resource-location, + entity-anchor, + gamemode, + difficulty, + time(s32), + %resource(string), + } + + variant command-error { + invalid-consumption(option), + invalid-requirement, + permission-denied, + command-failed(text-component), + } + + resource command-node { + literal: static func(name: string) -> command-node; + argument: static func(name: string, arg-type: argument-type) -> command-node; + then: func(node: command-node); + execute-with-handler-id: func(handler-id: u32); + require-with-handler-id: func(handler-id: u32); + } + + resource command { + /// First name is primary, rest are aliases + constructor(names: list, description: string); + then: func(node: command-node); + execute-with-handler-id: func(handler-id: u32); + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/common.wit b/pumpkin-plugin-wit/v0.1.0/common.wit new file mode 100644 index 000000000..113479c62 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/common.wit @@ -0,0 +1,170 @@ +interface common { + /// Serialized text component as a postcard byte array. + /// Deprecated: use the text-component resource from the text interface instead. + type raw-text-component = list; + type block-position = tuple; + type position = tuple; + + enum named-color { + black, + dark-blue, + dark-green, + dark-aqua, + dark-red, + dark-purple, + gold, + gray, + dark-gray, + blue, + green, + aqua, + red, + light-purple, + yellow, + white, + } + + record rgb-color { + r: u8, + g: u8, + b: u8, + } + + record argb-color { + a: u8, + r: u8, + g: u8, + b: u8, + } + + enum locale { + af-za, + ar-sa, + ast-es, + az-az, + ba-ru, + bar, + be-by, + bg-bg, + br-fr, + brb, + bs-ba, + ca-es, + cs-cz, + cy-gb, + da-dk, + de-at, + de-ch, + de-de, + el-gr, + en-au, + en-ca, + en-gb, + en-nz, + en-pt, + en-ud, + en-us, + enp, + enws, + eo-uy, + es-ar, + es-cl, + es-ec, + es-es, + es-mx, + es-uy, + es-ve, + esan, + et-ee, + eu-es, + fa-ir, + fi-fi, + fil-ph, + fo-fo, + fr-ca, + fr-fr, + fra-de, + fur-it, + fy-nl, + ga-ie, + gd-gb, + gl-es, + haw-us, + he-il, + hi-in, + hr-hr, + hu-hu, + hy-am, + id-id, + ig-ng, + io-en, + is-is, + isv, + it-it, + ja-jp, + jbo-en, + ka-ge, + kk-kz, + kn-in, + ko-kr, + ksh, + kw-gb, + la-la, + lb-lu, + li-li, + lmo, + lo-la, + lol-us, + lt-lt, + lv-lv, + lzh, + mk-mk, + mn-mn, + ms-my, + mt-mt, + nah, + nds-de, + nl-be, + nl-nl, + nn-no, + no-no, + oc-fr, + ovd, + pl-pl, + pt-br, + pt-pt, + qya-aa, + ro-ro, + rpr, + ru-ru, + ry-ua, + sah-sah, + se-no, + sk-sk, + sl-si, + so-so, + sq-al, + sr-cs, + sr-sp, + sv-se, + sxu, + szl, + ta-in, + th-th, + tl-ph, + tlh-aa, + tok, + tr-tr, + tt-ru, + uk-ua, + val-es, + vec-it, + vi-vn, + yi-de, + yo-ng, + zh-cn, + zh-hk, + zh-tw, + zlm-arab, + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/context.wit b/pumpkin-plugin-wit/v0.1.0/context.wit new file mode 100644 index 000000000..4716a9365 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/context.wit @@ -0,0 +1,11 @@ +interface context { + use server.{server}; + use event.{event-type, event-priority}; + use command.{command}; + + resource context { + register-event: func(handler-id: u32, event-type: event-type, event-priority: event-priority, blocking: bool); + register-command: func(command: command, permission: string); + get-server: func() -> server; + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/entity.wit b/pumpkin-plugin-wit/v0.1.0/entity.wit new file mode 100644 index 000000000..5f395349f --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/entity.wit @@ -0,0 +1,36 @@ +interface entity { + use common.{block-position}; + + resource block-entity { + resource-location: func() -> string; + get-position: func() -> block-position; + get-id: func() -> u32; + // write-nbt: func(nbt: nbt-compound); + // from-nbt: func(nbt: nbt-compound, position: block-position) -> entity; + // tick: func(world: simple-world); + // write-internal: func(nbt: nbt-compound); + // chunk-data-nbt: func() -> option option; + // set-block-state: func(block-state: block-state-id); + // on-block-replaced: func(world: simple-world, position: block-position); + is-dirty: func() -> bool; + clear-dirty: func(); + // to-property-delegate: func() -> option; + // to-experience-container: func() -> option; + } + + resource command-block-entity { + get-block-entity: func () -> block-entity; + last-output: func() -> string; + track-output: func() -> bool; + success-count: func() -> u32; + command: func() -> string; + auto: func() -> bool; + condition-met: func() -> bool; + powered: func() -> bool; + } + + variant entity { + command-block-entity(command-block-entity), + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/event.wit b/pumpkin-plugin-wit/v0.1.0/event.wit new file mode 100644 index 000000000..247ae0a65 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/event.wit @@ -0,0 +1,34 @@ +interface event { + use player.{player}; + use text.{text-component}; + + enum event-priority { + highest, + high, + normal, + low, + lowest, + } + + record player-join-event-data { + player: player, + join-message: text-component, + cancelled: bool + } + + record player-leave-event-data { + player: player, + leave-message: text-component, + cancelled: bool + } + + enum event-type { + player-join-event, + player-leave-event, + } + + variant event { + player-join-event(player-join-event-data), + player-leave-event(player-leave-event-data), + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/log.wit b/pumpkin-plugin-wit/v0.1.0/log.wit new file mode 100644 index 000000000..aafd268bd --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/log.wit @@ -0,0 +1,14 @@ +interface logging { + enum level { + trace, + debug, + info, + warn, + error, + } + + /// log any general purpose message + log: func(level: level, message: string); + /// This function is meant to be used by the tracing crate. + log-tracing: func(event: list); +} diff --git a/pumpkin-plugin-wit/v0.1.0/metadata.wit b/pumpkin-plugin-wit/v0.1.0/metadata.wit new file mode 100644 index 000000000..f13f421e7 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/metadata.wit @@ -0,0 +1,15 @@ +/// Plugin metadata describing the plugin and its compatibility. +interface metadata { + record plugin-metadata { + /// Name of the plugin. + name: string, + /// Plugin version (semver). + version: string, + /// Plugin authors. + authors: list, + /// Short description of the plugin. + description: string, + } + + get-metadata: func() -> plugin-metadata; +} diff --git a/pumpkin-plugin-wit/v0.1.0/player.wit b/pumpkin-plugin-wit/v0.1.0/player.wit new file mode 100644 index 000000000..912087461 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/player.wit @@ -0,0 +1,5 @@ +interface player { + resource player { + get-id: func () -> string; + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/plugin.wit b/pumpkin-plugin-wit/v0.1.0/plugin.wit new file mode 100644 index 000000000..7aa3162c9 --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/plugin.wit @@ -0,0 +1,24 @@ +package pumpkin:plugin; + +world plugin { + use context.{context}; + use event.{event}; + use server.{server as server-instance}; + use command.{command-sender, consumed-args, command-error}; + + // This is what the host should provide to the plugin + import logging; + import server; + import text; + import command; + import context; + + // This is what the plugin should provide + export init-plugin: func(); + export on-load: func(context: context) -> result<_, string>; + export on-unload: func(context: context) -> result<_, string>; + export metadata; + export common; + export handle-event: func(event-id: u32, server: server-instance, event: event) -> event; + export handle-command: func(command-id: u32, sender: command-sender, server: server-instance, args: consumed-args) -> result; +} diff --git a/pumpkin-plugin-wit/v0.1.0/server.wit b/pumpkin-plugin-wit/v0.1.0/server.wit new file mode 100644 index 000000000..729a82a2a --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/server.wit @@ -0,0 +1,12 @@ +interface server { + enum difficulty { + peaceful, + easy, + normal, + hard, + } + + resource server { + get-difficulty: func() -> difficulty; + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/text.wit b/pumpkin-plugin-wit/v0.1.0/text.wit new file mode 100644 index 000000000..cad5d6f0d --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/text.wit @@ -0,0 +1,41 @@ +interface text { + use common.{named-color, rgb-color, argb-color}; + + resource text-component { + text: static func(plain: string) -> text-component; + translate: static func(key: string, %with: list) -> text-component; + + add-child: func(child: text-component); + add-text: func(text: string); + get-text: func() -> string; + encode: func() -> list; + + // Style + + color-named: func(color: named-color); + color-rgb: func(color: rgb-color); + bold: func(value: bool); + italic: func(value: bool); + underlined: func(value: bool); + strikethrough: func(value: bool); + obfuscated: func(value: bool); + /// Text inserted into chat when shift-clicked + insertion: func(text: string); + font: func(font: string); + shadow-color: func(color: argb-color); + + // Click events + + click-open-url: func(url: string); + click-run-command: func(command: string); + click-suggest-command: func(command: string); + click-copy-to-clipboard: func(text: string); + + // Hover events + + hover-show-text: func(text: text-component); + /// Item data as SNBT string + hover-show-item: func(item: string); + hover-show-entity: func(entity-type: string, id: string, name: option); + } +} diff --git a/pumpkin-plugin-wit/v0.1.0/world.wit b/pumpkin-plugin-wit/v0.1.0/world.wit new file mode 100644 index 000000000..2a5970bfc --- /dev/null +++ b/pumpkin-plugin-wit/v0.1.0/world.wit @@ -0,0 +1,5 @@ +interface %world { + resource %world { + get-id: func () -> string; + } +} diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index ebfbe5312..21ec05895 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -88,6 +88,13 @@ tokio-util = { workspace = true, features = ["rt"] } flate2.workspace = true console-subscriber = { workspace = true, optional = true } +wasmtime = { workspace = true } +wasmtime-wasi = { workspace = true } +wasmparser = { workspace = true } + +postcard = { workspace = true } +tracing-serde-structured = { workspace = true } + [dev-dependencies] tempfile.workspace = true diff --git a/pumpkin/src/command/commands/plugin.rs b/pumpkin/src/command/commands/plugin.rs index 3d9d2cad9..0061c2378 100644 --- a/pumpkin/src/command/commands/plugin.rs +++ b/pumpkin/src/command/commands/plugin.rs @@ -46,13 +46,15 @@ impl CommandExecutor for ListExecutor { for (i, metadata) in plugins.iter().enumerate() { let fmt = if i == plugins.len() - 1 { - metadata.name.to_string() + metadata.name.clone() } else { format!("{}, ", metadata.name) }; let hover_text = format!( "Version: {}\nAuthors: {}\nDescription: {}", - metadata.version, metadata.authors, metadata.description + metadata.version, + metadata.authors.join(", "), + metadata.description ); let component = TextComponent::text(fmt) .color_named(NamedColor::Green) diff --git a/pumpkin/src/command/commands/plugins.rs b/pumpkin/src/command/commands/plugins.rs index e1d67b518..093a6792e 100644 --- a/pumpkin/src/command/commands/plugins.rs +++ b/pumpkin/src/command/commands/plugins.rs @@ -31,13 +31,15 @@ impl CommandExecutor for Executor { for (i, metadata) in plugins.clone().into_iter().enumerate() { let fmt = if i == plugins.len() - 1 { - metadata.name.to_string() + metadata.name.clone() } else { format!("{}, ", metadata.name) }; let hover_text = format!( "Version: {}\nAuthors: {}\nDescription: {}", - metadata.version, metadata.authors, metadata.description + metadata.version, + metadata.authors.join(", "), + metadata.description ); let component = TextComponent::text(fmt) .color_named(NamedColor::Green) diff --git a/pumpkin/src/net/query.rs b/pumpkin/src/net/query.rs index 0a6ad322c..9d782cbe1 100644 --- a/pumpkin/src/net/query.rs +++ b/pumpkin/src/net/query.rs @@ -148,7 +148,7 @@ async fn handle_packet( .active_plugins() .await .into_iter() - .map(|meta| meta.name.to_string()) + .map(|meta| meta.name) .reduce(|acc, name| format!("{acc}, {name}")) .unwrap_or_default(); diff --git a/pumpkin/src/plugin/api/context.rs b/pumpkin/src/plugin/api/context.rs index 0d7733809..bece9fc22 100644 --- a/pumpkin/src/plugin/api/context.rs +++ b/pumpkin/src/plugin/api/context.rs @@ -4,7 +4,7 @@ use std::{ sync::{Arc, OnceLock}, }; -use crate::{LoggerOption, command::client_suggestions, plugin_log}; +use crate::{LoggerOption, command::client_suggestions, plugin::PluginMetadata, plugin_log}; use pumpkin_util::{ PermissionLvl, permission::{Permission, PermissionManager}, @@ -20,7 +20,7 @@ use crate::{ use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt}; -use super::{EventPriority, Payload, PluginMetadata}; +use super::{EventPriority, Payload}; /// The `Context` struct represents the context of a plugin, containing metadata, /// a server reference, and event handlers. @@ -30,7 +30,7 @@ use super::{EventPriority, Payload, PluginMetadata}; /// - `server`: A reference to the server on which the plugin operates. /// - `handlers`: A map of event handlers, protected by a read-write lock for safe access across threads. pub struct Context { - metadata: PluginMetadata<'static>, + metadata: PluginMetadata, pub server: Arc, pub handlers: Arc>, pub plugin_manager: Arc, @@ -49,7 +49,7 @@ impl Context { /// A new instance of `Context`. #[must_use] pub fn new( - metadata: PluginMetadata<'static>, + metadata: PluginMetadata, server: Arc, handlers: Arc>, plugin_manager: Arc, @@ -72,7 +72,7 @@ impl Context { /// A string representing the path to the data folder. #[must_use] pub fn get_data_folder(&self) -> PathBuf { - let path = Path::new("./plugins").join(self.metadata.name); + let path = Path::new("./plugins").join(&self.metadata.name); if !path.exists() { fs::create_dir_all(&path).unwrap(); } @@ -156,13 +156,12 @@ impl Context { tree: crate::command::tree::CommandTree, permission: P, ) { - let plugin_name = self.metadata.name; let permission = permission.into(); let full_permission_node = if permission.contains(':') { permission } else { - format!("{plugin_name}:{permission}") + format!("{}:{permission}", self.metadata.name) }; { @@ -209,12 +208,13 @@ impl Context { /// Register a permission for this plugin pub async fn register_permission(&self, permission: Permission) -> Result<(), String> { // Ensure the permission has the correct namespace - let plugin_name = self.metadata.name; - - if !permission.node.starts_with(&format!("{plugin_name}:")) { + if !permission + .node + .starts_with(&format!("{}:", self.metadata.name)) + { return Err(format!( "Permission {} must use the plugin's namespace ({})", - permission.node, plugin_name + permission.node, self.metadata.name )); } @@ -339,6 +339,6 @@ impl Context { } else { Level::INFO }; - plugin_log!(level, self.metadata.name, "{}", message); + plugin_log!(level, &self.metadata.name, "{}", message); } } diff --git a/pumpkin/src/plugin/api/mod.rs b/pumpkin/src/plugin/api/mod.rs index e51159be0..503657af8 100644 --- a/pumpkin/src/plugin/api/mod.rs +++ b/pumpkin/src/plugin/api/mod.rs @@ -12,15 +12,15 @@ pub use events::*; /// version, authors, and a description. It is generic over a lifetime `'s` to allow /// for string slices that are valid for the lifetime of the plugin metadata. #[derive(Debug, Clone)] -pub struct PluginMetadata<'s> { +pub struct PluginMetadata { /// The name of the plugin. - pub name: &'s str, + pub name: String, /// The version of the plugin. - pub version: &'s str, + pub version: String, /// The authors of the plugin. - pub authors: &'s str, + pub authors: Vec, /// A description of the plugin. - pub description: &'s str, + pub description: String, } /// This type represents a future for the plugin. diff --git a/pumpkin/src/plugin/loader/mod.rs b/pumpkin/src/plugin/loader/mod.rs index 807aa3cac..1ee888fb9 100644 --- a/pumpkin/src/plugin/loader/mod.rs +++ b/pumpkin/src/plugin/loader/mod.rs @@ -1,18 +1,15 @@ -use crate::plugin::api::{Plugin, PluginMetadata}; +use crate::plugin::{PluginMetadata, api::Plugin, loader::wasm::wasm_host::PluginInitError}; use std::{any::Any, path::Path, pin::Pin}; use thiserror::Error; pub mod native; +pub mod wasm; pub type PluginLoadFuture<'a> = Pin< Box< dyn Future< Output = Result< - ( - Box, - PluginMetadata<'static>, - Box, - ), + (Box, PluginMetadata, Box), LoaderError, >, > + Send @@ -69,4 +66,7 @@ pub enum LoaderError { plugin_version: u32, server_version: u32, }, + + #[error("Wasm plugin initialization error: {0}")] + WasmInitializationError(#[from] PluginInitError), } diff --git a/pumpkin/src/plugin/loader/wasm/mod.rs b/pumpkin/src/plugin/loader/wasm/mod.rs new file mode 100644 index 000000000..d633eafa1 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/mod.rs @@ -0,0 +1,65 @@ +use std::{any::Any, path::Path, sync::Arc}; + +use wasm_host::{PluginRuntime, WasmPlugin}; + +use crate::plugin::{ + Context, Plugin, PluginFuture, + loader::{PluginLoadFuture, PluginLoader, PluginUnloadFuture}, +}; + +pub mod wasm_host; + +impl Plugin for Arc { + fn on_load(&mut self, context: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { + self.as_ref() + .on_load(context) + .await + .map_err(|err| err.to_string())? + }) + } + + fn on_unload(&mut self, context: Arc) -> PluginFuture<'_, Result<(), String>> { + Box::pin(async move { + self.as_ref() + .on_unload(context) + .await + .map_err(|err| err.to_string())? + }) + } +} + +pub struct WasmPluginLoader; +impl PluginLoader for WasmPluginLoader { + fn load<'a>(&'a self, path: &'a Path) -> PluginLoadFuture<'a> { + Box::pin(async { + let path = path.to_owned(); + + let runtime = PluginRuntime::new(&path)?; + let (plugin, metadata) = runtime.init_plugin(&path).await?; + + Ok(( + Box::new(plugin) as Box, + metadata, + Box::new(()) as Box, + )) + }) + } + + fn can_load(&self, path: &Path) -> bool { + let ext = path + .extension() + .and_then(|s| s.to_str()) + .unwrap_or_default(); + + ext.eq_ignore_ascii_case("wasm") + } + + fn unload(&self, _data: Box) -> PluginUnloadFuture<'_> { + Box::pin(async { Ok(()) }) + } + + fn can_unload(&self) -> bool { + true + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs new file mode 100644 index 000000000..f8e1492f5 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/args.rs @@ -0,0 +1,85 @@ +use std::{collections::HashMap, sync::Arc}; + +use pumpkin_util::text::TextComponent; + +use crate::{command::tree::CommandTree, entity::player::Player}; + +#[derive(Clone)] +pub enum OwnedArg { + Entities(Vec>), + Entity(Arc), + Players(Vec>), + BlockPos(pumpkin_util::math::position::BlockPos), + Pos3D(pumpkin_util::math::vector3::Vector3), + Pos2D(pumpkin_util::math::vector2::Vector2), + Rotation(f32, bool, f32, bool), + GameMode(pumpkin_util::GameMode), + Difficulty(pumpkin_util::Difficulty), + CommandTree(CommandTree), + Item(String), + ItemPredicate(String), + ResourceLocation(String), + Block(String), + BlockPredicate(String), + BossbarColor(crate::world::bossbar::BossbarColor), + BossbarStyle(crate::world::bossbar::BossbarDivisions), + Particle(pumpkin_data::particle::Particle), + Msg(String), + TextComponent(TextComponent), + Time(i32), + Num( + Result< + crate::command::args::bounded_num::Number, + crate::command::args::bounded_num::NotInBounds, + >, + ), + Bool(bool), + Simple(String), + SoundCategory(pumpkin_data::sound::SoundCategory), + DamageType(pumpkin_data::damage::DamageType), + Effect(&'static pumpkin_data::effect::StatusEffect), + Enchantment(&'static pumpkin_data::Enchantment), + EntityAnchor(crate::command::args::EntityAnchor), +} + +impl OwnedArg { + #[must_use] + pub fn from_arg(arg: &crate::command::args::Arg<'_>) -> Self { + use crate::command::args::Arg; + match arg { + Arg::Entities(v) => Self::Entities(v.clone()), + Arg::Entity(e) => Self::Entity(e.clone()), + Arg::Players(v) => Self::Players(v.clone()), + Arg::BlockPos(p) => Self::BlockPos(*p), + Arg::Pos3D(v) => Self::Pos3D(*v), + Arg::Pos2D(v) => Self::Pos2D(*v), + Arg::Rotation(a, b, c, d) => Self::Rotation(*a, *b, *c, *d), + Arg::GameMode(g) => Self::GameMode(*g), + Arg::Difficulty(d) => Self::Difficulty(*d), + Arg::CommandTree(t) => Self::CommandTree(t.clone()), + Arg::Item(s) => Self::Item(s.to_string()), + Arg::ItemPredicate(s) => Self::ItemPredicate(s.to_string()), + Arg::ResourceLocation(s) => Self::ResourceLocation(s.to_string()), + Arg::Block(s) => Self::Block(s.to_string()), + Arg::BlockPredicate(s) => Self::BlockPredicate(s.to_string()), + Arg::BossbarColor(c) => Self::BossbarColor(c.clone()), + Arg::BossbarStyle(s) => Self::BossbarStyle(s.clone()), + Arg::Particle(p) => Self::Particle(*p), + Arg::Msg(m) => Self::Msg(m.clone()), + Arg::TextComponent(t) => Self::TextComponent(t.clone()), + Arg::Time(t) => Self::Time(*t), + Arg::Num(n) => Self::Num(*n), + Arg::Bool(b) => Self::Bool(*b), + Arg::Simple(s) => Self::Simple(s.to_string()), + Arg::SoundCategory(s) => Self::SoundCategory(*s), + Arg::DamageType(d) => Self::DamageType(*d), + Arg::Effect(e) => Self::Effect(e), + Arg::Enchantment(e) => Self::Enchantment(e), + Arg::EntityAnchor(a) => Self::EntityAnchor(*a), + } + } +} + +pub struct ConsumedArgsResource { + pub provider: HashMap, +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/logging.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/logging.rs new file mode 100644 index 000000000..292aa3f32 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/logging.rs @@ -0,0 +1,114 @@ +use tracing_serde_structured::{DebugRecord, SerializeValue}; + +pub async fn log_tracing(event_bytes: Vec) { + let event: tracing_serde_structured::SerializeEvent<'_> = + match postcard::from_bytes(&event_bytes) { + Ok(e) => e, + Err(e) => { + tracing::error!("[plugin] failed to deserialize tracing event: {e}"); + return; + } + }; + + let message = match &event.fields { + tracing_serde_structured::SerializeRecordFields::De(map) => map + .get(&tracing_serde_structured::CowString::Borrowed("message")) + .map(|v| match v { + SerializeValue::Debug(d) => match d { + DebugRecord::De(s) => s.as_str().to_string(), + DebugRecord::Ser(_) => String::new(), + }, + SerializeValue::Str(s) => s.as_str().to_string(), + _ => String::new(), + }) + .unwrap_or_default(), + tracing_serde_structured::SerializeRecordFields::Ser(_) => String::new(), + }; + + let target = event.metadata.target.as_str().to_string(); + let module_path = event + .metadata + .module_path + .as_deref() + .unwrap_or("") + .to_string(); + let file = event + .metadata + .file + .as_deref() + .unwrap_or("unknown") + .to_string(); + let line = event.metadata.line.unwrap_or(0); + + let extra: Vec<(&str, &tracing_serde_structured::SerializeValue)> = match &event.fields { + tracing_serde_structured::SerializeRecordFields::De(map) => map + .iter() + .filter(|(k, _)| k.as_str() != "message") + .map(|(k, v)| (k.as_str(), v)) + .collect(), + tracing_serde_structured::SerializeRecordFields::Ser(_) => vec![], + }; + + let fields_str = if extra.is_empty() { + None + } else { + Some( + extra + .iter() + .map(|(k, v)| format!("{k}={}", format_value(v))) + .collect::>() + .join(", "), + ) + }; + + macro_rules! emit { + ($level:expr) => { + match &fields_str { + None => { + tracing::event!( + $level, + plugin.target = %target, + plugin.module = %module_path, + plugin.file = %file, + plugin.line = line, + "{message}" + ); + } + Some(fields) => { + tracing::event!( + $level, + plugin.target = %target, + plugin.module = %module_path, + plugin.file = %file, + plugin.line = line, + plugin.fields = %fields, + "{message}" + ); + } + } + }; + } + + match event.metadata.level { + tracing_serde_structured::SerializeLevel::Trace => emit!(tracing::Level::TRACE), + tracing_serde_structured::SerializeLevel::Debug => emit!(tracing::Level::DEBUG), + tracing_serde_structured::SerializeLevel::Info => emit!(tracing::Level::INFO), + tracing_serde_structured::SerializeLevel::Warn => emit!(tracing::Level::WARN), + tracing_serde_structured::SerializeLevel::Error => emit!(tracing::Level::ERROR), + } +} + +fn format_value(v: &tracing_serde_structured::SerializeValue) -> String { + match v { + SerializeValue::Debug(d) => match d { + DebugRecord::De(s) => s.as_str().to_string(), + DebugRecord::Ser(args) => format!("{args:?}"), + }, + SerializeValue::Str(s) => s.as_str().to_string(), + SerializeValue::F64(f) => f.to_string(), + SerializeValue::I64(i) => i.to_string(), + SerializeValue::U64(u) => u.to_string(), + SerializeValue::Bool(b) => b.to_string(), + _ => String::from(""), + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs new file mode 100644 index 000000000..ed807d238 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/mod.rs @@ -0,0 +1,186 @@ +use std::{fs, path::Path, sync::Arc}; +use thiserror::Error; +use tokio::sync::Mutex; +use wasmtime::{Cache, CacheConfig, Engine, Store, component::Component}; + +use crate::plugin::{Context, PluginMetadata, loader::wasm::wasm_host::state::PluginHostState}; + +pub mod args; +pub mod logging; +pub mod state; +pub mod wit; + +#[derive(Error, Debug)] +pub enum PluginInitError { + #[error("Engine creation failed")] + EngineCreationFailed(wasmtime::Error), + #[error("Failed to setup linker")] + LinkerSetupFailed(wasmtime::Error), + #[error("plugin API version mismatch received plugin with version `{0}`")] + ApiVersionMismatch(String), + #[error("plugin missing pumpkin:api-version custom section")] + MissingApiVersionSection, + #[error("failed to read payload for plugin")] + FailedToReadPayload(#[from] wasmparser::BinaryReaderError), + #[error("failed to read plugin bytes")] + FailedToReadPluginBytes(#[from] std::io::Error), + #[error("plugin failed to load with error: {0}")] + PluginFailedToLoad(#[from] wasmtime::Error), +} + +pub struct PluginRuntime { + engine: Engine, + cache_dir: std::path::PathBuf, + linker_v0_1_0: wasmtime::component::Linker, +} + +pub enum PluginInstance { + V0_1_0(wit::v0_1_0::Plugin), +} + +pub struct WasmPlugin { + pub plugin_instance: PluginInstance, + pub store: Mutex>, +} + +impl PluginRuntime { + pub fn new>(path: P) -> Result { + let mut config = wasmtime::Config::new(); + config.wasm_component_model(true); + let mut path = std::path::absolute(path.as_ref()).expect("Failed to get absolute path"); + path.pop(); + path.push("cache"); + let mut cache_config = CacheConfig::new(); + cache_config.with_directory(&path); + config.cache(Some( + Cache::new(cache_config).expect("Failed to create cache"), + )); + let engine = Engine::new(&config).map_err(PluginInitError::EngineCreationFailed)?; + + let linker_v0_1_0 = + wit::v0_1_0::setup_linker(&engine).map_err(PluginInitError::LinkerSetupFailed)?; + + Ok(Self { + engine, + cache_dir: path, + linker_v0_1_0, + }) + } + + pub async fn init_plugin>( + &self, + path: P, + ) -> Result<(Arc, PluginMetadata), PluginInitError> { + let wasm_bytes = std::fs::read(&path)?; + + let api_version = probe_api_version_from_bytes(&wasm_bytes)?; + + if api_version != "0.1.0" { + return Err(PluginInitError::ApiVersionMismatch(api_version)); + } + + let component = load_component(&self.engine, &wasm_bytes, path.as_ref(), &self.cache_dir)?; + + let (wasm_plugin, metadata) = match api_version.as_str() { + "0.1.0" => { + wit::v0_1_0::init_plugin(&self.engine, &self.linker_v0_1_0, component).await? + } + _ => return Err(PluginInitError::ApiVersionMismatch(api_version)), + }; + let wasm_plugin = Arc::new(wasm_plugin); + wasm_plugin.store.lock().await.data_mut().plugin = Some(Arc::downgrade(&wasm_plugin)); + + Ok((wasm_plugin, metadata)) + } +} + +fn cache_key(wasm_path: &Path) -> Result { + let metadata = fs::metadata(wasm_path)?; + let file_name = wasm_path.file_stem().unwrap().to_string_lossy(); + let len = metadata.len(); + let modified = metadata + .modified()? + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + + Ok(format!( + "{file_name}-{len}-{modified}-{}.cwasm", + env!("CARGO_PKG_VERSION"), + )) +} + +fn load_component( + engine: &Engine, + wasm_bytes: &[u8], + wasm_path: &Path, + cache_dir: &Path, +) -> Result { + let cache_name = cache_key(wasm_path)?; + let cache_path = cache_dir.join(cache_name); + + if cache_path.exists() { + match unsafe { Component::deserialize_file(engine, &cache_path) } { + Ok(component) => return Ok(component), + Err(_) => { + let _ = fs::remove_file(&cache_path); + } + } + } + + let component = Component::new(engine, wasm_bytes)?; + fs::write(&cache_path, component.serialize()?)?; + Ok(component) +} + +/// Kind of a dumb solution, but in order to get the API version from a component, we define a custom section inside of the wasm binary itself, we then +/// parse the value in that section to get the API version. +fn probe_api_version_from_bytes(wasm_bytes: &[u8]) -> Result { + let parser = wasmparser::Parser::new(0); + for payload in parser.parse_all(wasm_bytes) { + if let wasmparser::Payload::CustomSection(reader) = payload? + && reader.name() == "pumpkin:api-version" + { + return Ok(String::from_utf8_lossy(reader.data()).to_string()); + } + } + Err(PluginInitError::MissingApiVersionSection) +} + +impl WasmPlugin { + pub async fn on_load( + &self, + context: Arc, + ) -> Result, wasmtime::Error> { + let mut store = self.store.lock().await; + + store.data_mut().server = Some(context.server.clone()); + + match self.plugin_instance { + PluginInstance::V0_1_0(ref plugin) => { + let context = store.data_mut().add_context(context)?; + plugin.call_on_load(&mut *store, context).await + } + } + } + + pub async fn on_unload( + &self, + context: Arc, + ) -> Result, wasmtime::Error> { + let mut store = self.store.lock().await; + + match self.plugin_instance { + PluginInstance::V0_1_0(ref plugin) => { + let context = store.data_mut().add_context(context)?; + plugin.call_on_unload(&mut *store, context).await + } + } + } +} + +pub trait DowncastResourceExt { + fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a E; + fn downcast_mut<'a>(&'a self, state: &'a mut PluginHostState) -> &'a mut E; + fn consume(self, state: &mut PluginHostState) -> E; +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs new file mode 100644 index 000000000..b219a7edb --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/state.rs @@ -0,0 +1,146 @@ +use std::{ + collections::HashMap, + sync::{Arc, Weak}, +}; + +use pumpkin_util::text::TextComponent; +use wasmtime::component::ResourceTable; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder, WasiCtxView, WasiView}; + +use crate::{ + command::{ + CommandSender, + args::ConsumedArgs, + tree::{CommandTree, builder::NonLeafNodeBuilder}, + }, + entity::player::Player, + plugin::{ + Context, + loader::wasm::wasm_host::{WasmPlugin, args::OwnedArg}, + }, + server::Server, +}; + +pub struct WasmResource { + pub provider: T, +} + +pub type ServerResource = WasmResource>; +pub type ContextResource = WasmResource>; +pub type PlayerResource = WasmResource>; +pub type TextComponentResource = WasmResource; +pub type CommandResource = WasmResource; +pub type CommandSenderResource = WasmResource; +pub type ConsumedArgsResource = WasmResource; +pub type CommandNodeResource = WasmResource; + +pub type OwnedConsumedArgs = HashMap; + +pub struct PluginHostState { + pub wasi_ctx: WasiCtx, + pub resource_table: ResourceTable, + pub plugin: Option>, + pub server: Option>, +} + +impl Default for PluginHostState { + fn default() -> Self { + Self::new() + } +} + +impl PluginHostState { + #[must_use] + pub fn new() -> Self { + let resource_table = ResourceTable::new(); + Self { + wasi_ctx: WasiCtxBuilder::new().build(), + resource_table, + plugin: None, + server: None, + } + } + + pub fn add_server( + &mut self, + provider: Arc, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(ServerResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_context( + &mut self, + provider: Arc, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(ContextResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_player( + &mut self, + provider: Arc, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(PlayerResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_text_component( + &mut self, + provider: TextComponent, + ) -> wasmtime::Result> { + let resource = self + .resource_table + .push(TextComponentResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_command( + &mut self, + provider: CommandTree, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(CommandResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_command_sender( + &mut self, + command_sender: CommandSender, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(CommandSenderResource { + provider: command_sender, + })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_consumed_args( + &mut self, + provider: &ConsumedArgs<'_>, + ) -> wasmtime::Result> { + let owned: HashMap = provider + .iter() + .map(|(k, v)| (k.to_string(), OwnedArg::from_arg(v))) + .collect(); + let resource = self + .resource_table + .push(ConsumedArgsResource { provider: owned })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } + + pub fn add_command_node( + &mut self, + provider: NonLeafNodeBuilder, + ) -> wasmtime::Result> { + let resource = self.resource_table.push(CommandNodeResource { provider })?; + Ok(wasmtime::component::Resource::new_own(resource.rep())) + } +} + +impl WasiView for PluginHostState { + fn ctx(&mut self) -> WasiCtxView<'_> { + WasiCtxView { + ctx: &mut self.wasi_ctx, + table: &mut self.resource_table, + } + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/mod.rs new file mode 100644 index 000000000..babf1c8b0 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/mod.rs @@ -0,0 +1 @@ +pub mod v0_1_0; diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/executor.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/executor.rs new file mode 100644 index 000000000..79f94c115 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/executor.rs @@ -0,0 +1,80 @@ +use std::sync::Arc; + +use pumpkin_util::text::{ + TextComponent, + color::{Color, NamedColor}, +}; + +use crate::{ + command::{CommandExecutor, dispatcher::CommandError}, + plugin::loader::wasm::wasm_host::{ + DowncastResourceExt, PluginInstance, WasmPlugin, + wit::v0_1_0::pumpkin::plugin::command::CommandError as CommandErrorWit, + }, + server::Server, +}; + +pub struct WasmCommandExecutor { + pub handler_id: u32, + pub plugin: Arc, + pub server: Arc, +} + +impl CommandExecutor for WasmCommandExecutor { + fn execute<'a>( + &'a self, + sender: &'a crate::command::CommandSender, + _server: &'a crate::server::Server, + args: &'a crate::command::args::ConsumedArgs<'a>, + ) -> crate::command::CommandResult<'a> { + Box::pin(async move { + let mut store = self.plugin.store.lock().await; + + let sender_resource = store.data_mut().add_command_sender(sender.clone()).unwrap(); + let server_resource = store.data_mut().add_server(self.server.clone()).unwrap(); + let args_resource = store.data_mut().add_consumed_args(args).unwrap(); + + match self.plugin.plugin_instance { + PluginInstance::V0_1_0(ref plugin) => { + let result = plugin + .call_handle_command( + &mut *store, + self.handler_id, + sender_resource, + server_resource, + args_resource, + ) + .await + .map_err(|e| { + CommandError::CommandFailed( + TextComponent::text(format!( + "Wasm command failed with following error: {e}" + )) + .color(Color::Named(NamedColor::Red)), + ) + })?; + + match result { + Ok(value) => Ok(value), + Err(err) => match err { + CommandErrorWit::InvalidConsumption(value) => { + Err(CommandError::InvalidConsumption(value)) + } + CommandErrorWit::InvalidRequirement => { + Err(CommandError::InvalidRequirement) + } + CommandErrorWit::PermissionDenied => { + Err(CommandError::PermissionDenied) + } + CommandErrorWit::CommandFailed(resource) => { + Err(CommandError::CommandFailed( + resource.consume(store.data_mut()).provider, + )) + } + }, + } + } + } + }) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/mod.rs new file mode 100644 index 000000000..a23c4ac64 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/commands/mod.rs @@ -0,0 +1,656 @@ +use wasmtime::component::Resource; + +use crate::{ + command::{ + args::{ + GetClientSideArgParser, + block::{BlockArgumentConsumer, BlockPredicateArgumentConsumer}, + bool::BoolArgConsumer, + bounded_num::{BoundedNumArgumentConsumer, ToFromNumber}, + difficulty::DifficultyArgumentConsumer, + entities::EntitiesArgumentConsumer, + entity::EntityArgumentConsumer, + entity_anchor::EntityAnchorArgumentConsumer, + gamemode::GamemodeArgumentConsumer, + message::MsgArgConsumer, + players::PlayersArgumentConsumer, + position_2d::Position2DArgumentConsumer, + position_3d::Position3DArgumentConsumer, + position_block::BlockPosArgumentConsumer, + resource::item::{ItemArgumentConsumer, ItemPredicateArgumentConsumer}, + resource_location::ResourceLocationArgumentConsumer, + rotation::RotationArgumentConsumer, + simple::SimpleArgConsumer, + textcomponent::TextComponentArgConsumer, + time::TimeArgumentConsumer, + }, + tree::{ + CommandTree, + builder::{argument, literal}, + }, + }, + plugin::loader::wasm::wasm_host::{ + DowncastResourceExt, + state::{ + CommandNodeResource, CommandSenderResource, PluginHostState, TextComponentResource, + }, + wit::v0_1_0::{ + commands::executor::WasmCommandExecutor, + pumpkin::{ + self, + plugin::{ + command::{ + Arg, ArgumentType, Command, CommandNode, CommandSender, CommandSenderType, + ConsumedArgs, PermissionLevel, StringType, + }, + common::{Locale, Position}, + player::Player, + server::Server, + text::TextComponent, + world::World, + }, + }, + }, + }, +}; + +pub mod executor; + +impl pumpkin::plugin::command::Host for PluginHostState {} + +impl pumpkin::plugin::command::HostConsumedArgs for PluginHostState { + async fn get_value(&mut self, _consumed_args: Resource, _key: String) -> Arg { + todo!() + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.resource_table + .delete::( + Resource::new_own(rep.rep()), + )?; + Ok(()) + } +} + +impl pumpkin::plugin::command::HostCommand for PluginHostState { + async fn new(&mut self, names: Vec, description: String) -> Resource { + self.add_command(CommandTree::new(names, description)) + .unwrap() + } + + async fn then(&mut self, command: Resource, node: Resource) -> () { + let node_resource = node.consume(self); + let command_resource = self + .resource_table + .get_mut::( + &Resource::new_own(command.rep()), + ) + .expect("invalid command resource handle"); + + command_resource.provider = command_resource + .provider + .clone() + .then(node_resource.provider); + } + + async fn execute_with_handler_id(&mut self, command: Resource, handler_id: u32) -> () { + let plugin = self + .plugin + .as_ref() + .expect("plugin should always be initialized here") + .upgrade() + .expect("plugin has been dropped"); + + let server = self + .server + .clone() + .expect("server should be set before command registration"); + + let executor = WasmCommandExecutor { + handler_id, + plugin, + server, + }; + + let command_resource = self + .resource_table + .get_mut::( + &Resource::new_own(command.rep()), + ) + .expect("invalid command resource handle"); + + command_resource.provider = command_resource.provider.clone().execute(executor); + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.resource_table + .delete::( + Resource::new_own(rep.rep()), + )?; + Ok(()) + } +} + +impl pumpkin::plugin::command::HostCommandSender for PluginHostState { + async fn get_command_sender_type( + &mut self, + _command_sender: Resource, + ) -> CommandSenderType { + todo!() + } + + async fn send_message( + &mut self, + command_sender: Resource, + text: Resource, + ) -> () { + let text_resource = self + .resource_table + .get::(&Resource::new_own(text.rep())) + .expect("invalid text-component resource handle"); + let component = text_resource.provider.clone(); + + let sender_resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + sender_resource.provider.send_message(component).await; + } + + async fn set_success_count(&mut self, command_sender: Resource, count: i32) { + let resource = self + .resource_table + .get_mut::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + resource.provider.set_success_count(count as u32); + } + + async fn is_player(&mut self, command_sender: Resource) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + matches!(resource.provider, crate::command::CommandSender::Player(_)) + } + + async fn is_console(&mut self, command_sender: Resource) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + matches!( + resource.provider, + crate::command::CommandSender::Console | crate::command::CommandSender::Rcon(_) + ) + } + + async fn as_player( + &mut self, + command_sender: Resource, + ) -> Option> { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + if let crate::command::CommandSender::Player(player) = &resource.provider { + let player = player.clone(); + Some(self.add_player(player).unwrap()) + } else { + None + } + } + + async fn permission_level( + &mut self, + command_sender: Resource, + ) -> PermissionLevel { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + match resource.provider.permission_lvl() { + pumpkin_util::PermissionLvl::Zero => PermissionLevel::Zero, + pumpkin_util::PermissionLvl::One => PermissionLevel::One, + pumpkin_util::PermissionLvl::Two => PermissionLevel::Two, + pumpkin_util::PermissionLvl::Three => PermissionLevel::Three, + pumpkin_util::PermissionLvl::Four => PermissionLevel::Four, + } + } + + async fn has_permission_level( + &mut self, + command_sender: Resource, + level: PermissionLevel, + ) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + let required = match level { + PermissionLevel::Zero => pumpkin_util::PermissionLvl::Zero, + PermissionLevel::One => pumpkin_util::PermissionLvl::One, + PermissionLevel::Two => pumpkin_util::PermissionLvl::Two, + PermissionLevel::Three => pumpkin_util::PermissionLvl::Three, + PermissionLevel::Four => pumpkin_util::PermissionLvl::Four, + }; + + resource.provider.permission_lvl() >= required + } + + async fn has_permission( + &mut self, + command_sender: Resource, + server: Resource, + node: String, + ) -> bool { + let sender_resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + let server_resource = self + .resource_table + .get::( + &Resource::new_own(server.rep()), + ) + .expect("invalid server resource handle"); + + sender_resource + .provider + .has_permission(&server_resource.provider, &node) + .await + } + + async fn position(&mut self, command_sender: Resource) -> Option { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + resource + .provider + .position() + .map(|pos| (pos.x, pos.y, pos.z)) + } + + async fn world(&mut self, command_sender: Resource) -> Option> { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + if let Some(world) = resource.provider.world() { + Some( + self.resource_table + .push( + crate::plugin::loader::wasm::wasm_host::state::WasmResource { + provider: world, + }, + ) + .map(|r| wasmtime::component::Resource::new_own(r.rep())) + .unwrap(), + ) + } else { + None + } + } + + #[allow(clippy::too_many_lines)] + async fn get_locale(&mut self, command_sender: Resource) -> Locale { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + match resource.provider.get_locale() { + pumpkin_util::translation::Locale::AfZa => Locale::AfZa, + pumpkin_util::translation::Locale::ArSa => Locale::ArSa, + pumpkin_util::translation::Locale::AstEs => Locale::AstEs, + pumpkin_util::translation::Locale::AzAz => Locale::AzAz, + pumpkin_util::translation::Locale::BaRu => Locale::BaRu, + pumpkin_util::translation::Locale::Bar => Locale::Bar, + pumpkin_util::translation::Locale::BeBy => Locale::BeBy, + pumpkin_util::translation::Locale::BgBg => Locale::BgBg, + pumpkin_util::translation::Locale::BrFr => Locale::BrFr, + pumpkin_util::translation::Locale::Brb => Locale::Brb, + pumpkin_util::translation::Locale::BsBa => Locale::BsBa, + pumpkin_util::translation::Locale::CaEs => Locale::CaEs, + pumpkin_util::translation::Locale::CsCz => Locale::CsCz, + pumpkin_util::translation::Locale::CyGb => Locale::CyGb, + pumpkin_util::translation::Locale::DaDk => Locale::DaDk, + pumpkin_util::translation::Locale::DeAt => Locale::DeAt, + pumpkin_util::translation::Locale::DeCh => Locale::DeCh, + pumpkin_util::translation::Locale::DeDe => Locale::DeDe, + pumpkin_util::translation::Locale::ElGr => Locale::ElGr, + pumpkin_util::translation::Locale::EnAu => Locale::EnAu, + pumpkin_util::translation::Locale::EnCa => Locale::EnCa, + pumpkin_util::translation::Locale::EnGb => Locale::EnGb, + pumpkin_util::translation::Locale::EnNz => Locale::EnNz, + pumpkin_util::translation::Locale::EnPt => Locale::EnPt, + pumpkin_util::translation::Locale::EnUd => Locale::EnUd, + pumpkin_util::translation::Locale::EnUs => Locale::EnUs, + pumpkin_util::translation::Locale::Enp => Locale::Enp, + pumpkin_util::translation::Locale::Enws => Locale::Enws, + pumpkin_util::translation::Locale::EoUy => Locale::EoUy, + pumpkin_util::translation::Locale::EsAr => Locale::EsAr, + pumpkin_util::translation::Locale::EsCl => Locale::EsCl, + pumpkin_util::translation::Locale::EsEc => Locale::EsEc, + pumpkin_util::translation::Locale::EsEs => Locale::EsEs, + pumpkin_util::translation::Locale::EsMx => Locale::EsMx, + pumpkin_util::translation::Locale::EsUy => Locale::EsUy, + pumpkin_util::translation::Locale::EsVe => Locale::EsVe, + pumpkin_util::translation::Locale::Esan => Locale::Esan, + pumpkin_util::translation::Locale::EtEe => Locale::EtEe, + pumpkin_util::translation::Locale::EuEs => Locale::EuEs, + pumpkin_util::translation::Locale::FaIr => Locale::FaIr, + pumpkin_util::translation::Locale::FiFi => Locale::FiFi, + pumpkin_util::translation::Locale::FilPh => Locale::FilPh, + pumpkin_util::translation::Locale::FoFo => Locale::FoFo, + pumpkin_util::translation::Locale::FrCa => Locale::FrCa, + pumpkin_util::translation::Locale::FrFr => Locale::FrFr, + pumpkin_util::translation::Locale::FraDe => Locale::FraDe, + pumpkin_util::translation::Locale::FurIt => Locale::FurIt, + pumpkin_util::translation::Locale::FyNl => Locale::FyNl, + pumpkin_util::translation::Locale::GaIe => Locale::GaIe, + pumpkin_util::translation::Locale::GdGb => Locale::GdGb, + pumpkin_util::translation::Locale::GlEs => Locale::GlEs, + pumpkin_util::translation::Locale::HawUs => Locale::HawUs, + pumpkin_util::translation::Locale::HeIl => Locale::HeIl, + pumpkin_util::translation::Locale::HiIn => Locale::HiIn, + pumpkin_util::translation::Locale::HrHr => Locale::HrHr, + pumpkin_util::translation::Locale::HuHu => Locale::HuHu, + pumpkin_util::translation::Locale::HyAm => Locale::HyAm, + pumpkin_util::translation::Locale::IdId => Locale::IdId, + pumpkin_util::translation::Locale::IgNg => Locale::IgNg, + pumpkin_util::translation::Locale::IoEn => Locale::IoEn, + pumpkin_util::translation::Locale::IsIs => Locale::IsIs, + pumpkin_util::translation::Locale::Isv => Locale::Isv, + pumpkin_util::translation::Locale::ItIt => Locale::ItIt, + pumpkin_util::translation::Locale::JaJp => Locale::JaJp, + pumpkin_util::translation::Locale::JboEn => Locale::JboEn, + pumpkin_util::translation::Locale::KaGe => Locale::KaGe, + pumpkin_util::translation::Locale::KkKz => Locale::KkKz, + pumpkin_util::translation::Locale::KnIn => Locale::KnIn, + pumpkin_util::translation::Locale::KoKr => Locale::KoKr, + pumpkin_util::translation::Locale::Ksh => Locale::Ksh, + pumpkin_util::translation::Locale::KwGb => Locale::KwGb, + pumpkin_util::translation::Locale::LaLa => Locale::LaLa, + pumpkin_util::translation::Locale::LbLu => Locale::LbLu, + pumpkin_util::translation::Locale::LiLi => Locale::LiLi, + pumpkin_util::translation::Locale::Lmo => Locale::Lmo, + pumpkin_util::translation::Locale::LoLa => Locale::LoLa, + pumpkin_util::translation::Locale::LolUs => Locale::LolUs, + pumpkin_util::translation::Locale::LtLt => Locale::LtLt, + pumpkin_util::translation::Locale::LvLv => Locale::LvLv, + pumpkin_util::translation::Locale::Lzh => Locale::Lzh, + pumpkin_util::translation::Locale::MkMk => Locale::MkMk, + pumpkin_util::translation::Locale::MnMn => Locale::MnMn, + pumpkin_util::translation::Locale::MsMy => Locale::MsMy, + pumpkin_util::translation::Locale::MtMt => Locale::MtMt, + pumpkin_util::translation::Locale::Nah => Locale::Nah, + pumpkin_util::translation::Locale::NdsDe => Locale::NdsDe, + pumpkin_util::translation::Locale::NlBe => Locale::NlBe, + pumpkin_util::translation::Locale::NlNl => Locale::NlNl, + pumpkin_util::translation::Locale::NnNo => Locale::NnNo, + pumpkin_util::translation::Locale::NoNo => Locale::NoNo, + pumpkin_util::translation::Locale::OcFr => Locale::OcFr, + pumpkin_util::translation::Locale::Ovd => Locale::Ovd, + pumpkin_util::translation::Locale::PlPl => Locale::PlPl, + pumpkin_util::translation::Locale::PtBr => Locale::PtBr, + pumpkin_util::translation::Locale::PtPt => Locale::PtPt, + pumpkin_util::translation::Locale::QyaAa => Locale::QyaAa, + pumpkin_util::translation::Locale::RoRo => Locale::RoRo, + pumpkin_util::translation::Locale::Rpr => Locale::Rpr, + pumpkin_util::translation::Locale::RuRu => Locale::RuRu, + pumpkin_util::translation::Locale::RyUa => Locale::RyUa, + pumpkin_util::translation::Locale::SahSah => Locale::SahSah, + pumpkin_util::translation::Locale::SeNo => Locale::SeNo, + pumpkin_util::translation::Locale::SkSk => Locale::SkSk, + pumpkin_util::translation::Locale::SlSi => Locale::SlSi, + pumpkin_util::translation::Locale::SoSo => Locale::SoSo, + pumpkin_util::translation::Locale::SqAl => Locale::SqAl, + pumpkin_util::translation::Locale::SrCs => Locale::SrCs, + pumpkin_util::translation::Locale::SrSp => Locale::SrSp, + pumpkin_util::translation::Locale::SvSe => Locale::SvSe, + pumpkin_util::translation::Locale::Sxu => Locale::Sxu, + pumpkin_util::translation::Locale::Szl => Locale::Szl, + pumpkin_util::translation::Locale::TaIn => Locale::TaIn, + pumpkin_util::translation::Locale::ThTh => Locale::ThTh, + pumpkin_util::translation::Locale::TlPh => Locale::TlPh, + pumpkin_util::translation::Locale::TlhAa => Locale::TlhAa, + pumpkin_util::translation::Locale::Tok => Locale::Tok, + pumpkin_util::translation::Locale::TrTr => Locale::TrTr, + pumpkin_util::translation::Locale::TtRu => Locale::TtRu, + pumpkin_util::translation::Locale::UkUa => Locale::UkUa, + pumpkin_util::translation::Locale::ValEs => Locale::ValEs, + pumpkin_util::translation::Locale::VecIt => Locale::VecIt, + pumpkin_util::translation::Locale::ViVn => Locale::ViVn, + pumpkin_util::translation::Locale::YiDe => Locale::YiDe, + pumpkin_util::translation::Locale::YoNg => Locale::YoNg, + pumpkin_util::translation::Locale::ZhCn => Locale::ZhCn, + pumpkin_util::translation::Locale::ZhHk => Locale::ZhHk, + pumpkin_util::translation::Locale::ZhTw => Locale::ZhTw, + pumpkin_util::translation::Locale::ZlmArab => Locale::ZlmArab, + } + } + + async fn should_receive_feedback(&mut self, command_sender: Resource) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + resource.provider.should_receive_feedback() + } + + async fn should_broadcast_console_to_ops( + &mut self, + command_sender: Resource, + ) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + resource.provider.should_broadcast_console_to_ops() + } + + async fn should_track_output(&mut self, command_sender: Resource) -> bool { + let resource = self + .resource_table + .get::(&Resource::new_own(command_sender.rep())) + .expect("invalid command-sender resource handle"); + + resource.provider.should_track_output() + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.resource_table + .delete::(Resource::new_own(rep.rep()))?; + Ok(()) + } +} + +impl DowncastResourceExt for Resource { + fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a CommandNodeResource { + state + .resource_table + .get_any_mut(self.rep()) + .expect("invalid command-node resource handle") + .downcast_ref() + .expect("resource type mismatch") + } + + fn downcast_mut<'a>(&'a self, state: &'a mut PluginHostState) -> &'a mut CommandNodeResource { + state + .resource_table + .get_any_mut(self.rep()) + .expect("invalid command-node resource handle") + .downcast_mut() + .expect("resource type mismatch") + } + + fn consume(self, state: &mut PluginHostState) -> CommandNodeResource { + state + .resource_table + .delete(Resource::new_own(self.rep())) + .expect("invalid command-node resource handle") + } +} + +fn bounded_num_argument( + state: &mut PluginHostState, + name: String, + min: Option, + max: Option, +) -> Resource +where + BoundedNumArgumentConsumer: GetClientSideArgParser, +{ + let mut consumer = BoundedNumArgumentConsumer::::new(); + if let Some(min) = min { + consumer = consumer.min(min); + } + if let Some(max) = max { + consumer = consumer.max(max); + } + state.add_command_node(argument(name, consumer)).unwrap() +} + +impl pumpkin::plugin::command::HostCommandNode for PluginHostState { + async fn literal(&mut self, name: String) -> Resource { + self.add_command_node(literal(name)).unwrap() + } + + async fn argument(&mut self, name: String, arg_type: ArgumentType) -> Resource { + match arg_type { + ArgumentType::Bool => self + .add_command_node(argument(name, BoolArgConsumer)) + .unwrap(), + ArgumentType::Float((min, max)) => bounded_num_argument(self, name, min, max), + ArgumentType::Double((min, max)) => bounded_num_argument(self, name, min, max), + ArgumentType::Integer((min, max)) => bounded_num_argument(self, name, min, max), + ArgumentType::Long((min, max)) => bounded_num_argument(self, name, min, max), + ArgumentType::String(string_type) => match string_type { + StringType::SingleWord | StringType::Quotable => self + .add_command_node(argument(name, SimpleArgConsumer)) + .unwrap(), + StringType::Greedy => self + .add_command_node(argument(name, MsgArgConsumer)) + .unwrap(), + }, + ArgumentType::Entities => self + .add_command_node(argument(name, EntitiesArgumentConsumer)) + .unwrap(), + ArgumentType::Entity => self + .add_command_node(argument(name, EntityArgumentConsumer)) + .unwrap(), + ArgumentType::Players | ArgumentType::GameProfile => self + .add_command_node(argument(name, PlayersArgumentConsumer)) + .unwrap(), + ArgumentType::BlockPos => self + .add_command_node(argument(name, BlockPosArgumentConsumer)) + .unwrap(), + ArgumentType::Position3d => self + .add_command_node(argument(name, Position3DArgumentConsumer)) + .unwrap(), + ArgumentType::Position2d => self + .add_command_node(argument(name, Position2DArgumentConsumer)) + .unwrap(), + ArgumentType::BlockState => self + .add_command_node(argument(name, BlockArgumentConsumer)) + .unwrap(), + ArgumentType::BlockPredicate => self + .add_command_node(argument(name, BlockPredicateArgumentConsumer)) + .unwrap(), + ArgumentType::Item => self + .add_command_node(argument(name, ItemArgumentConsumer)) + .unwrap(), + ArgumentType::ItemPredicate => self + .add_command_node(argument(name, ItemPredicateArgumentConsumer)) + .unwrap(), + ArgumentType::Component => self + .add_command_node(argument(name, TextComponentArgConsumer)) + .unwrap(), + ArgumentType::Rotation => self + .add_command_node(argument(name, RotationArgumentConsumer)) + .unwrap(), + ArgumentType::ResourceLocation | ArgumentType::Resource(_) => self + .add_command_node(argument(name, ResourceLocationArgumentConsumer)) + .unwrap(), + ArgumentType::EntityAnchor => self + .add_command_node(argument(name, EntityAnchorArgumentConsumer)) + .unwrap(), + ArgumentType::Gamemode => self + .add_command_node(argument(name, GamemodeArgumentConsumer)) + .unwrap(), + ArgumentType::Difficulty => self + .add_command_node(argument(name, DifficultyArgumentConsumer)) + .unwrap(), + ArgumentType::Time(_) => self + .add_command_node(argument(name, TimeArgumentConsumer)) + .unwrap(), + } + } + + async fn then( + &mut self, + self_command_node: Resource, + node: Resource, + ) { + let child_resource = node.consume(self); + let parent_resource = self_command_node.downcast_mut(self); + let builder = std::mem::replace(&mut parent_resource.provider, literal("")); + parent_resource.provider = builder.then(child_resource.provider); + } + + async fn execute_with_handler_id( + &mut self, + command_node: Resource, + handler_id: u32, + ) { + let plugin = self + .plugin + .as_ref() + .expect("plugin should always be initialized here") + .upgrade() + .expect("plugin has been dropped"); + + let server = self + .server + .clone() + .expect("server should be set before command registration"); + + let executor = WasmCommandExecutor { + handler_id, + plugin, + server, + }; + + let resource = command_node.downcast_mut(self); + // Unless we make the native command registration code less convenient to use, this is our best option + let builder = std::mem::replace(&mut resource.provider, literal("")); + resource.provider = builder.execute(executor); + } + + async fn require_with_handler_id( + &mut self, + _command_node: Resource, + _handler_id: u32, + ) { + todo!() + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + self.resource_table + .delete::(Resource::new_own(rep.rep()))?; + Ok(()) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/common.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/common.rs new file mode 100644 index 000000000..fcd52566c --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/common.rs @@ -0,0 +1,3 @@ +use crate::plugin::loader::wasm::wasm_host::{state::PluginHostState, wit::v0_1_0::pumpkin}; + +impl pumpkin::plugin::common::Host for PluginHostState {} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/context.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/context.rs new file mode 100644 index 000000000..7dde879c6 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/context.rs @@ -0,0 +1,119 @@ +use std::sync::Arc; + +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::{CommandResource, ContextResource, PluginHostState}, + wit::v0_1_0::{ + events::WasmPluginV0_1_0EventHandler, + pumpkin::{ + self, + plugin::{ + command::Command, + context::Context, + event::{EventPriority, EventType}, + server::Server, + }, + }, + }, +}; + +impl pumpkin::plugin::context::Host for PluginHostState {} + +impl pumpkin::plugin::context::HostContext for PluginHostState { + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } + + async fn get_server(&mut self, context: Resource) -> Resource { + let resource = self + .resource_table + .get_any_mut(context.rep()) + .expect("invalid context resource handle") + .downcast_ref::() + .expect("resource type mismatch"); + let server_provider = resource.provider.server.clone(); + self.add_server(server_provider) + .expect("failed to add server resource") + } + + async fn register_event( + &mut self, + context: Resource, + handler_id: u32, + event_type: EventType, + event_priority: EventPriority, + blocking: bool, + ) { + let resource = self + .resource_table + .get_any_mut(context.rep()) + .expect("invalid context resource handle") + .downcast_ref::() + .expect("resource type mismatch"); + + let priority = match event_priority { + EventPriority::Highest => crate::plugin::EventPriority::Highest, + EventPriority::High => crate::plugin::EventPriority::High, + EventPriority::Normal => crate::plugin::EventPriority::Normal, + EventPriority::Low => crate::plugin::EventPriority::Low, + EventPriority::Lowest => crate::plugin::EventPriority::Lowest, + }; + + let plugin = self + .plugin + .as_ref() + .expect("plugin should always be initialized here") + .upgrade() + .expect("plugin has been dropped"); + + let handler = Arc::new(WasmPluginV0_1_0EventHandler { handler_id, plugin }); + + match event_type { + EventType::PlayerJoinEvent => { + resource + .provider + .register_event::( + handler, priority, blocking, + ) + .await; + } + EventType::PlayerLeaveEvent => { + resource + .provider + .register_event::( + handler, priority, blocking, + ) + .await; + } + } + } + + async fn register_command( + &mut self, + context: Resource, + command: Resource, + permission: String, + ) { + let command = self + .resource_table + .delete::(Resource::new_own(command.rep())) + .expect("invalid command resource handle") + .provider; + + let context_resource = self + .resource_table + .get_any_mut(context.rep()) + .expect("invalid context resource handle") + .downcast_ref::() + .expect("resource type mismatch"); + + context_resource + .provider + .register_command(command, permission) + .await; + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs new file mode 100644 index 000000000..7c1749ce7 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/entity.rs @@ -0,0 +1,81 @@ +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::PluginHostState, + wit::v0_1_0::pumpkin::{ + self, + plugin::{ + common::BlockPosition, + entity::{BlockEntity, CommandBlockEntity}, + }, + }, +}; + +impl pumpkin::plugin::entity::Host for PluginHostState {} + +impl pumpkin::plugin::entity::HostBlockEntity for PluginHostState { + async fn resource_location(&mut self, _block_entity: Resource) -> String { + todo!() + } + + async fn get_position(&mut self, _block_entity: Resource) -> BlockPosition { + todo!() + } + + async fn get_id(&mut self, _block_entity: Resource) -> u32 { + todo!() + } + + async fn is_dirty(&mut self, _block_entity: Resource) -> bool { + todo!() + } + + async fn clear_dirty(&mut self, _block_entity: Resource) { + todo!() + } + + async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { + todo!() + } +} + +impl pumpkin::plugin::entity::HostCommandBlockEntity for PluginHostState { + async fn get_block_entity( + &mut self, + _command_block_entity: Resource, + ) -> Resource { + todo!() + } + + async fn last_output(&mut self, _command_block_entity: Resource) -> String { + todo!() + } + + async fn track_output(&mut self, _command_block_entity: Resource) -> bool { + todo!() + } + + async fn success_count(&mut self, _command_block_entity: Resource) -> u32 { + todo!() + } + + async fn command(&mut self, _command_block_entity: Resource) -> String { + todo!() + } + + async fn auto(&mut self, _command_block_entity: Resource) -> bool { + todo!() + } + + async fn condition_met(&mut self, _command_block_entity: Resource) -> bool { + todo!() + } + + async fn powered(&mut self, _command_block_entity: Resource) -> bool { + todo!() + } + + async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { + todo!() + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/mod.rs new file mode 100644 index 000000000..0389b3c4c --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/mod.rs @@ -0,0 +1,74 @@ +use std::sync::Arc; + +use crate::{ + plugin::{ + BoxFuture, EventHandler, Payload, + loader::wasm::wasm_host::{ + PluginInstance, WasmPlugin, + state::PluginHostState, + wit::{self, v0_1_0::pumpkin}, + }, + }, + server::Server, +}; + +pub mod player; + +impl pumpkin::plugin::event::Host for PluginHostState {} + +pub struct WasmPluginV0_1_0EventHandler { + pub handler_id: u32, + pub plugin: Arc, +} + +pub trait ToFromV0_1_0WasmEvent { + fn to_v0_1_0_wasm_event( + &self, + state: &mut PluginHostState, + ) -> wit::v0_1_0::pumpkin::plugin::event::Event; + + fn from_v0_1_0_wasm_event( + event: wit::v0_1_0::pumpkin::plugin::event::Event, + state: &mut PluginHostState, + ) -> Self; +} + +impl EventHandler for WasmPluginV0_1_0EventHandler { + fn handle<'a>(&'a self, server: &'a Arc, event: &'a E) -> BoxFuture<'a, ()> { + Box::pin(async { + let mut store = self.plugin.store.lock().await; + let event = event.to_v0_1_0_wasm_event(store.data_mut()); + match self.plugin.plugin_instance { + PluginInstance::V0_1_0(ref plugin) => { + let server = store.data_mut().add_server(server.clone()).unwrap(); + plugin + .call_handle_event(&mut *store, self.handler_id, server, event) + .await + .unwrap(); + } + } + }) + } + + fn handle_blocking<'a>( + &'a self, + server: &'a Arc, + event: &'a mut E, + ) -> BoxFuture<'a, ()> { + Box::pin(async { + let mut store = self.plugin.store.lock().await; + let wasm_event = event.to_v0_1_0_wasm_event(store.data_mut()); + match self.plugin.plugin_instance { + PluginInstance::V0_1_0(ref plugin) => { + let server = store.data_mut().add_server(server.clone()).unwrap(); + let returned_event = plugin + .call_handle_event(&mut *store, self.handler_id, server, wasm_event) + .await + .unwrap(); + + *event = E::from_v0_1_0_wasm_event(returned_event, store.data_mut()); + } + } + }) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/player.rs new file mode 100644 index 000000000..23f5e3e82 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/events/player.rs @@ -0,0 +1,100 @@ +use wasmtime::component::Resource; + +use crate::plugin::{ + loader::wasm::wasm_host::{ + state::{PlayerResource, PluginHostState, TextComponentResource}, + wit::v0_1_0::{ + events::ToFromV0_1_0WasmEvent, + pumpkin::plugin::event::{Event, PlayerJoinEventData, PlayerLeaveEventData}, + }, + }, + player::{player_join::PlayerJoinEvent, player_leave::PlayerLeaveEvent}, +}; + +impl ToFromV0_1_0WasmEvent for PlayerJoinEvent { + fn to_v0_1_0_wasm_event(&self, state: &mut PluginHostState) -> Event { + let player_resource = state + .add_player(self.player.clone()) + .expect("failed to add player resource"); + + let text_component_resource = state.add_text_component(self.join_message.clone()).unwrap(); + + Event::PlayerJoinEvent(PlayerJoinEventData { + player: player_resource, + join_message: text_component_resource, + cancelled: self.cancelled, + }) + } + + fn from_v0_1_0_wasm_event( + event: crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::event::Event, + state: &mut PluginHostState, + ) -> Self { + #[allow(clippy::match_wildcard_for_single_variants)] + match event { + Event::PlayerJoinEvent(data) => { + let player_resource = state + .resource_table + .delete::(Resource::new_own(data.player.rep())) + .unwrap(); + + let text_component_resource = state + .resource_table + .delete::(Resource::new_own(data.join_message.rep())) + .unwrap(); + + Self { + player: player_resource.provider, + join_message: text_component_resource.provider, + cancelled: data.cancelled, + } + } + _ => panic!("unexpected event type"), + } + } +} + +impl ToFromV0_1_0WasmEvent for PlayerLeaveEvent { + fn to_v0_1_0_wasm_event(&self, state: &mut PluginHostState) -> Event { + let player_resource = state + .add_player(self.player.clone()) + .expect("failed to add player resource"); + + let text_component_resource = state + .add_text_component(self.leave_message.clone()) + .unwrap(); + + Event::PlayerLeaveEvent(PlayerLeaveEventData { + player: player_resource, + leave_message: text_component_resource, + cancelled: self.cancelled, + }) + } + + fn from_v0_1_0_wasm_event( + event: crate::plugin::loader::wasm::wasm_host::wit::v0_1_0::pumpkin::plugin::event::Event, + state: &mut PluginHostState, + ) -> Self { + #[allow(clippy::match_wildcard_for_single_variants)] + match event { + Event::PlayerLeaveEvent(data) => { + let player_resource = state + .resource_table + .delete::(Resource::new_own(data.player.rep())) + .unwrap(); + + let text_component_resource = state + .resource_table + .delete::(Resource::new_own(data.leave_message.rep())) + .unwrap(); + + Self { + player: player_resource.provider, + leave_message: text_component_resource.provider, + cancelled: data.cancelled, + } + } + _ => panic!("unexpected event type"), + } + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/logging.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/logging.rs new file mode 100644 index 000000000..5f80fe8b3 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/logging.rs @@ -0,0 +1,19 @@ +use crate::plugin::loader::wasm::wasm_host::{ + logging::log_tracing, state::PluginHostState, wit::v0_1_0::pumpkin, +}; + +impl pumpkin::plugin::logging::Host for PluginHostState { + async fn log(&mut self, level: pumpkin::plugin::logging::Level, message: String) { + match level { + pumpkin::plugin::logging::Level::Trace => tracing::trace!("[plugin] {message}"), + pumpkin::plugin::logging::Level::Debug => tracing::debug!("[plugin] {message}"), + pumpkin::plugin::logging::Level::Info => tracing::info!("[plugin] {message}"), + pumpkin::plugin::logging::Level::Warn => tracing::warn!("[plugin] {message}"), + pumpkin::plugin::logging::Level::Error => tracing::error!("[plugin] {message}"), + } + } + + async fn log_tracing(&mut self, event: Vec) { + log_tracing(event).await; + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs new file mode 100644 index 000000000..ae18fb259 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/mod.rs @@ -0,0 +1,71 @@ +use crate::plugin::{ + PluginMetadata, + loader::wasm::wasm_host::{PluginInstance, WasmPlugin, state::PluginHostState}, +}; +use tokio::sync::Mutex; +use wasmtime::component::{Component, HasData, Linker, bindgen}; +use wasmtime::{Engine, Store}; + +pub mod commands; +pub mod common; +pub mod context; +pub mod entity; +pub mod events; +pub mod logging; +pub mod player; +pub mod server; +pub mod text; +pub mod world; + +bindgen!({ + path: "../pumpkin-plugin-wit/v0.1.0", + world: "plugin", + imports: { default: async }, + exports: { default: async }, +}); + +struct PluginHostComponent; + +impl HasData for PluginHostComponent { + type Data<'a> = &'a mut PluginHostState; +} + +pub fn setup_linker(engine: &Engine) -> wasmtime::Result> { + let mut linker = Linker::new(engine); + wasmtime_wasi::p2::add_to_linker_async(&mut linker)?; + Plugin::add_to_linker::<_, PluginHostComponent>(&mut linker, |state: &mut PluginHostState| { + state + })?; + Ok(linker) +} + +pub async fn init_plugin( + engine: &Engine, + linker: &Linker, + component: Component, +) -> wasmtime::Result<(WasmPlugin, PluginMetadata)> { + let mut store = Store::new(engine, PluginHostState::new()); + let plugin = Plugin::instantiate_async(&mut store, &component, linker).await?; + + plugin.call_init_plugin(&mut store).await?; + + let metadata = plugin + .pumpkin_plugin_metadata() + .call_get_metadata(&mut store) + .await?; + + let metadata = PluginMetadata { + name: metadata.name, + version: metadata.version, + authors: metadata.authors, + description: metadata.description, + }; + + Ok(( + WasmPlugin { + plugin_instance: PluginInstance::V0_1_0(plugin), + store: Mutex::new(store), + }, + metadata, + )) +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs new file mode 100644 index 000000000..5302785a5 --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/player.rs @@ -0,0 +1,26 @@ +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::{PlayerResource, PluginHostState}, + wit::v0_1_0::pumpkin::{self, plugin::player::Player}, +}; + +impl pumpkin::plugin::player::Host for PluginHostState {} +impl pumpkin::plugin::player::HostPlayer for PluginHostState { + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } + + async fn get_id(&mut self, player: Resource) -> String { + let resource = self + .resource_table + .get_any_mut(player.rep()) + .expect("invalid player resource handle") + .downcast_ref::() + .expect("resource type mismatch"); + resource.provider.gameprofile.id.to_string() + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs new file mode 100644 index 000000000..3cee7ea3e --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/server.rs @@ -0,0 +1,36 @@ +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::{PluginHostState, ServerResource}, + wit::v0_1_0::pumpkin::{ + self, + plugin::server::{Difficulty, Server}, + }, +}; + +impl pumpkin::plugin::server::Host for PluginHostState {} + +impl pumpkin::plugin::server::HostServer for PluginHostState { + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } + + async fn get_difficulty(&mut self, server: Resource) -> Difficulty { + let resource: &ServerResource = self + .resource_table + .get_any_mut(server.rep()) + .expect("invalid server resource handle") + .downcast_ref::() + .expect("resource type mismatch"); + + match resource.provider.get_difficulty() { + pumpkin_util::Difficulty::Peaceful => Difficulty::Peaceful, + pumpkin_util::Difficulty::Easy => Difficulty::Easy, + pumpkin_util::Difficulty::Normal => Difficulty::Normal, + pumpkin_util::Difficulty::Hard => Difficulty::Hard, + } + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/text.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/text.rs new file mode 100644 index 000000000..e9efa3b8b --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/text.rs @@ -0,0 +1,292 @@ +use std::borrow::Cow; + +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + DowncastResourceExt, + state::{PluginHostState, TextComponentResource}, + wit::v0_1_0::pumpkin::{ + self, + plugin::text::{ArgbColor, NamedColor, RgbColor, TextComponent}, + }, +}; + +use pumpkin_util::text::{ + click::ClickEvent, + color::{self, Color}, + hover::HoverEvent, +}; + +impl pumpkin::plugin::text::Host for PluginHostState {} + +// TODO - Change the pumpkin_util::text::TextComponent to use &mut self instead of self for the builder pattern. +// right now we have to do a bunch of cloning due to the fact that the builder pattern doesn't accept &mut self. +impl DowncastResourceExt for Resource { + fn downcast_ref<'a>(&'a self, state: &'a mut PluginHostState) -> &'a TextComponentResource { + state + .resource_table + .get_any_mut(self.rep()) + .expect("invalid text-component resource handle") + .downcast_ref::() + .expect("resource type mismatch") + } + + fn downcast_mut<'a>(&'a self, state: &'a mut PluginHostState) -> &'a mut TextComponentResource { + state + .resource_table + .get_any_mut(self.rep()) + .expect("invalid text-component resource handle") + .downcast_mut::() + .expect("resource type mismatch") + } + + fn consume(self, state: &mut PluginHostState) -> TextComponentResource { + state + .resource_table + .delete::(Resource::new_own(self.rep())) + .expect("invalid text-component resource handle") + } +} + +const fn map_named_color(color: NamedColor) -> color::NamedColor { + match color { + NamedColor::Black => color::NamedColor::Black, + NamedColor::DarkBlue => color::NamedColor::DarkBlue, + NamedColor::DarkGreen => color::NamedColor::DarkGreen, + NamedColor::DarkAqua => color::NamedColor::DarkAqua, + NamedColor::DarkRed => color::NamedColor::DarkRed, + NamedColor::DarkPurple => color::NamedColor::DarkPurple, + NamedColor::Gold => color::NamedColor::Gold, + NamedColor::Gray => color::NamedColor::Gray, + NamedColor::DarkGray => color::NamedColor::DarkGray, + NamedColor::Blue => color::NamedColor::Blue, + NamedColor::Green => color::NamedColor::Green, + NamedColor::Aqua => color::NamedColor::Aqua, + NamedColor::Red => color::NamedColor::Red, + NamedColor::LightPurple => color::NamedColor::LightPurple, + NamedColor::Yellow => color::NamedColor::Yellow, + NamedColor::White => color::NamedColor::White, + } +} + +impl pumpkin::plugin::text::HostTextComponent for PluginHostState { + async fn text(&mut self, plain: String) -> Resource { + let tc = pumpkin_util::text::TextComponent::text(plain); + self.add_text_component(tc).unwrap() + } + + async fn translate( + &mut self, + key: String, + with: Vec>, + ) -> Resource { + let with: Vec = + with.into_iter().map(|r| r.consume(self).provider).collect(); + let tc = pumpkin_util::text::TextComponent::translate(key, with); + self.add_text_component(tc).unwrap() + } + + async fn add_child( + &mut self, + text_component: Resource, + child: Resource, + ) { + let child = child.consume(self).provider; + let parent = &mut text_component.downcast_mut(self).provider; + *parent = parent.clone().add_child(child); + } + + async fn add_text(&mut self, text_component: Resource, text: String) { + let parent = &mut text_component.downcast_mut(self).provider; + *parent = parent.clone().add_text(text); + } + + async fn get_text(&mut self, text_component: Resource) -> String { + text_component + .downcast_ref(self) + .provider + .clone() + .get_text() + } + + async fn encode(&mut self, text_component: Resource) -> Vec { + text_component + .downcast_ref(self) + .provider + .encode() + .into_vec() + } + + async fn color_named(&mut self, text_component: Resource, color: NamedColor) { + text_component.downcast_mut(self).provider.0.style.color = + Some(Color::Named(map_named_color(color))); + } + + async fn color_rgb(&mut self, text_component: Resource, color: RgbColor) { + text_component.downcast_mut(self).provider.0.style.color = + Some(Color::Rgb(color::RGBColor::new(color.r, color.g, color.b))); + } + + async fn bold(&mut self, text_component: Resource, value: bool) { + text_component.downcast_mut(self).provider.0.style.bold = Some(value); + } + + async fn italic(&mut self, text_component: Resource, value: bool) { + text_component.downcast_mut(self).provider.0.style.italic = Some(value); + } + + async fn underlined(&mut self, text_component: Resource, value: bool) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .underlined = Some(value); + } + + async fn strikethrough(&mut self, text_component: Resource, value: bool) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .strikethrough = Some(value); + } + + async fn obfuscated(&mut self, text_component: Resource, value: bool) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .obfuscated = Some(value); + } + + async fn insertion(&mut self, text_component: Resource, text: String) { + text_component.downcast_mut(self).provider.0.style.insertion = Some(text); + } + + async fn font(&mut self, text_component: Resource, font: String) { + text_component.downcast_mut(self).provider.0.style.font = Some(font); + } + + async fn shadow_color(&mut self, text_component: Resource, color: ArgbColor) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .shadow_color = Some(color::ARGBColor::new(color.a, color.r, color.g, color.b)); + } + + async fn click_open_url(&mut self, text_component: Resource, url: String) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .click_event = Some(ClickEvent::OpenUrl { + url: Cow::Owned(url), + }); + } + + async fn click_run_command( + &mut self, + text_component: Resource, + command: String, + ) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .click_event = Some(ClickEvent::RunCommand { + command: Cow::Owned(command), + }); + } + + async fn click_suggest_command( + &mut self, + text_component: Resource, + command: String, + ) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .click_event = Some(ClickEvent::SuggestCommand { + command: Cow::Owned(command), + }); + } + + async fn click_copy_to_clipboard( + &mut self, + text_component: Resource, + text: String, + ) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .click_event = Some(ClickEvent::CopyToClipboard { + value: Cow::Owned(text), + }); + } + + async fn hover_show_text( + &mut self, + text_component: Resource, + text: Resource, + ) { + let hover_tc = text.consume(self).provider; + text_component + .downcast_mut(self) + .provider + .0 + .style + .hover_event = Some(HoverEvent::ShowText { + value: vec![hover_tc.0], + }); + } + + async fn hover_show_item(&mut self, text_component: Resource, item: String) { + text_component + .downcast_mut(self) + .provider + .0 + .style + .hover_event = Some(HoverEvent::ShowItem { + id: Cow::Owned(item), + count: None, + }); + } + + async fn hover_show_entity( + &mut self, + text_component: Resource, + entity_type: String, + id: String, + name: Option>, + ) { + let name = name.map(|r| vec![r.consume(self).provider.0]); + text_component + .downcast_mut(self) + .provider + .0 + .style + .hover_event = Some(HoverEvent::ShowEntity { + id: Cow::Owned(entity_type), + uuid: Cow::Owned(id), + name, + }); + } + + async fn drop(&mut self, rep: Resource) -> wasmtime::Result<()> { + let _ = self + .resource_table + .delete::(Resource::new_own(rep.rep())); + Ok(()) + } +} diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs new file mode 100644 index 000000000..e713977ca --- /dev/null +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1_0/world.rs @@ -0,0 +1,18 @@ +use wasmtime::component::Resource; + +use crate::plugin::loader::wasm::wasm_host::{ + state::PluginHostState, + wit::v0_1_0::pumpkin::{self, plugin::world::World}, +}; + +impl pumpkin::plugin::world::Host for PluginHostState {} + +impl pumpkin::plugin::world::HostWorld for PluginHostState { + async fn get_id(&mut self, _world: Resource) -> String { + todo!() + } + + async fn drop(&mut self, _rep: Resource) -> wasmtime::Result<()> { + todo!() + } +} diff --git a/pumpkin/src/plugin/mod.rs b/pumpkin/src/plugin/mod.rs index 65bc820c0..d1998438f 100644 --- a/pumpkin/src/plugin/mod.rs +++ b/pumpkin/src/plugin/mod.rs @@ -14,7 +14,7 @@ use tracing::{error, info}; pub mod api; pub mod loader; -use crate::{LOGGER_IMPL, server::Server}; +use crate::{LOGGER_IMPL, plugin::loader::wasm::WasmPluginLoader, server::Server}; pub use api::*; pub type BoxFuture<'a, T> = Pin + Send + 'a>>; @@ -176,7 +176,7 @@ pub struct PluginManager { /// OS specific issues /// - Windows: Plugin cannot be unloaded, it can be only active or not struct LoadedPlugin { - metadata: PluginMetadata<'static>, + metadata: PluginMetadata, instance: Option>, loader: Arc, loader_data: Option>, @@ -207,7 +207,10 @@ impl Default for PluginManager { fn default() -> Self { Self { plugins: RwLock::new(Vec::new()), - loaders: RwLock::new(vec![Arc::new(NativePluginLoader)]), + loaders: RwLock::new(vec![ + Arc::new(NativePluginLoader), + Arc::new(WasmPluginLoader), + ]), server: RwLock::new(None), handlers: Arc::new(RwLock::new(HashMap::new())), unloaded_files: RwLock::new(HashSet::new()), @@ -233,7 +236,7 @@ impl PluginManager { plugins .iter() .filter(|p| p.is_active) - .map(|p| p.metadata.name.to_string()) + .map(|p| p.metadata.name.clone()) .collect() }; @@ -309,8 +312,9 @@ impl PluginManager { } // Start loading plugin concurrently - if let Ok(task) = self.start_loading_plugin(&path).await { - load_tasks.push(task); + match self.start_loading_plugin(&path).await { + Ok(task) => load_tasks.push(task), + Err(err) => error!("{}", err), } } @@ -334,7 +338,7 @@ impl PluginManager { self.plugin_states .write() .await - .insert(metadata.name.to_string(), PluginState::Loading); + .insert(metadata.name.clone(), PluginState::Loading); let self_ref = self .self_ref @@ -380,7 +384,7 @@ impl PluginManager { // Spawn async task for plugin initialization let self_ref_clone = Arc::clone(&self_ref); let state_notify = Arc::clone(&self.state_notify); - let plugin_name = metadata.name.to_string(); + let plugin_name = metadata.name.clone(); let loader_clone = loader.clone(); let task = tokio::spawn(async move { @@ -503,7 +507,7 @@ impl PluginManager { /// Get list of active plugins #[must_use] - pub async fn active_plugins(&self) -> Vec> { + pub async fn active_plugins(&self) -> Vec { let plugins = self.plugins.read().await; plugins .iter() @@ -521,7 +525,7 @@ impl PluginManager { /// Get list of loaded plugins #[must_use] - pub async fn loaded_plugins(&self) -> Vec> { + pub async fn loaded_plugins(&self) -> Vec { let plugins = self.plugins.read().await; plugins.iter().map(|p| p.metadata.clone()).collect() }