diff --git a/Cargo.lock b/Cargo.lock index 965a3d06e..e2f9b619b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -544,9 +544,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d262e149917187838d5b42777c8253bcb64500067342904e7d429499a6f277e" +checksum = "509591b7bcd67f4ef775afad7662703b4935daaa6ec0e5605cfb1090b32a2b6d" dependencies = [ "find-msvc-tools", "jobserver", @@ -1602,9 +1602,9 @@ dependencies = [ [[package]] name = "error-code" -version = "3.3.2" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" [[package]] name = "event-listener" @@ -1675,9 +1675,9 @@ checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" [[package]] name = "find-msvc-tools" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b73573e6edcd2af0cdf47bd6cb58f0b3839491263c314eaad1ccf24430e1de" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" [[package]] name = "flate2" @@ -1732,9 +1732,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a88cf1f829d945f548cf8fec32c61b1f202b6d93b45848602fc02af4b12ad218" +checksum = "9a31d2a3fbaaeb2af2368bbdd904aa8e812d3c04a1ee10d3171f52d556e5d0a3" dependencies = [ "futures-channel", "futures-core", @@ -1747,9 +1747,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "262590f4fe6afeb0bc83be1daa64e52657fe185690a958af7f3ad0e92085c5ae" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" dependencies = [ "futures-core", "futures-sink", @@ -1757,15 +1757,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" [[package]] name = "futures-executor" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6754879cc9f2c66f88c6e5c35344bb0bdb0708b0352b1201815667c7eabc7458" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" dependencies = [ "futures-core", "futures-task", @@ -1774,38 +1774,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" [[package]] name = "futures-macro" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d6d3cde68c518367be28956066ddfef33813991b77a55005a69dae04bf3b10b" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] name = "futures-sink" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" [[package]] name = "futures-task" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" [[package]] name = "futures-util" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" dependencies = [ "futures-channel", "futures-core", @@ -1840,18 +1840,6 @@ 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 5.3.0", - "wasip2", -] - [[package]] name = "getrandom" version = "0.4.3" @@ -1860,7 +1848,7 @@ checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", "libc", - "r-efi 6.0.0", + "r-efi", "rand_core 0.10.1", ] @@ -2058,9 +2046,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" dependencies = [ "bytes", "futures-core", @@ -2155,9 +2143,9 @@ dependencies = [ [[package]] name = "icu_collections" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" dependencies = [ "displaydoc", "potential_utf", @@ -2169,9 +2157,9 @@ dependencies = [ [[package]] name = "icu_locale_core" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" dependencies = [ "displaydoc", "litemap", @@ -2182,9 +2170,9 @@ dependencies = [ [[package]] name = "icu_normalizer" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" dependencies = [ "icu_collections", "icu_normalizer_data", @@ -2196,16 +2184,17 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" [[package]] name = "icu_properties" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" dependencies = [ + "displaydoc", "icu_collections", "icu_locale_core", "icu_properties_data", @@ -2216,15 +2205,15 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" [[package]] name = "icu_provider" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +checksum = "92a7ed671a6aad807a8651a2e1782a6598fda9ce5185dd8158549e95a91c6428" dependencies = [ "displaydoc", "icu_locale_core", @@ -2289,9 +2278,9 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.4" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "153be1941a183ec9ccd095ddbe17a8b8d435ef6c76e9e02451b933c3999af2c8" +checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592" dependencies = [ "bitflags 2.13.1", "inotify-sys", @@ -2470,9 +2459,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2026a5056764a10b2bf5d56488cba40da507f5493a6a429340e2004d9ed085fa" +checksum = "28d0a00925a9f930d679b6789b721e3a7f9ed110f41b86d2497caa780c3a070a" dependencies = [ "libc", ] @@ -2485,9 +2474,9 @@ checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" [[package]] name = "litrs" @@ -2773,9 +2762,9 @@ dependencies = [ [[package]] name = "num-integer" -version = "0.1.46" +version = "0.1.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" dependencies = [ "num-traits", ] @@ -2993,9 +2982,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7df728be843c7070fab6ab7c328c4e9e9d78e23bf749c0669c86ee7ebfa050a2" +checksum = "5a07a60cc7a4d00c91f95c685609d1d2f79050e6804b70ebedd7650f0b839bcf" dependencies = [ "memchr", "ucd-trie", @@ -3003,9 +2992,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2dd6fc3b26b3462ee188aac870f5a41d398f1cd5e2408d16531bd71c9591fd" +checksum = "b3a83744a5c8455b8b3e0dc5031362780a347c878bdd11584d1a8984228cc88d" dependencies = [ "pest", "pest_generator", @@ -3013,9 +3002,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a7a9205cfb6f596a9e8b689c0a15f9ceb7a1aafae7aaf788150ac65b29975b6" +checksum = "e0cd3451aa3de60d4b9a1e736885e4dea6b31617598026f12256ad566d63304a" dependencies = [ "pest", "pest_meta", @@ -3026,9 +3015,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.8.8" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85abd351c0de1e8384fc791a0737111a350394937e92b956b743dac12429f57c" +checksum = "e04d3a0849e241d7dfce834c83b1c5edc8622009e8dd51a12ba1927c32f05496" dependencies = [ "pest", ] @@ -3134,9 +3123,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.33" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" [[package]] name = "png" @@ -3194,9 +3183,9 @@ dependencies = [ [[package]] name = "potential_utf" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" dependencies = [ "zerovec", ] @@ -3207,15 +3196,6 @@ 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 = "pretty_assertions" version = "1.4.1" @@ -3414,7 +3394,7 @@ dependencies = [ "pumpkin-protocol", "pumpkin-util", "pumpkin-world", - "rand 0.10.2", + "rand", "rayon", "rsa", "rustc-hash", @@ -3503,7 +3483,7 @@ dependencies = [ "phf", "pumpkin-nbt", "pumpkin-util", - "rand 0.10.2", + "rand", "serde", ] @@ -3513,7 +3493,7 @@ version = "0.1.0-dev+26.2-26.40" dependencies = [ "clap", "colored", - "rand 0.9.5", + "rand", "tokio", ] @@ -3525,7 +3505,7 @@ dependencies = [ "pumpkin-protocol", "pumpkin-util", "pumpkin-world", - "rand 0.10.2", + "rand", "thiserror 2.0.20", "tokio", "tracing", @@ -3566,7 +3546,7 @@ dependencies = [ "serde_json", "tracing", "tracing-serde-structured", - "wit-bindgen 0.60.0", + "wit-bindgen", ] [[package]] @@ -3640,7 +3620,7 @@ dependencies = [ "pumpkin-data", "pumpkin-nbt", "pumpkin-util", - "rand 0.10.2", + "rand", "rayon", "rustc-hash", "ruzstd", @@ -3686,12 +3666,6 @@ dependencies = [ "proc-macro2", ] -[[package]] -name = "r-efi" -version = "5.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - [[package]] name = "r-efi" version = "6.0.0" @@ -3707,16 +3681,6 @@ dependencies = [ "ptr_meta", ] -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_chacha", - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.2" @@ -3728,16 +3692,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.5", -] - [[package]] name = "rand_core" version = "0.6.4" @@ -3747,15 +3701,6 @@ dependencies = [ "getrandom 0.2.17", ] -[[package]] -name = "rand_core" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_core" version = "0.10.1" @@ -3960,7 +3905,7 @@ dependencies = [ "bytes", "hex", "log", - "rand 0.10.2", + "rand", "rcgen", "ring", "rtc-datachannel", @@ -4017,7 +3962,7 @@ dependencies = [ "log", "p256", "p384 0.13.1", - "rand 0.10.2", + "rand", "rand_core 0.6.4", "rcgen", "ring", @@ -4041,7 +3986,7 @@ dependencies = [ "bytes", "crc", "log", - "rand 0.10.2", + "rand", "rtc-mdns", "rtc-shared", "rtc-stun", @@ -4058,7 +4003,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f66ea5650a91773d10e4d98729f6fe256681ec1314c7ded5839fa34867305f79" dependencies = [ "log", - "rand 0.10.2", + "rand", "rtc-interceptor-derive", "rtc-rtcp", "rtc-rtp", @@ -4098,7 +4043,7 @@ checksum = "4fbc2f460a321eccdc0907819f8fc7027036edcbd94620c3328c1aefb070dfe5" dependencies = [ "byteorder", "bytes", - "rand 0.10.2", + "rand", "rtc-rtp", "rtc-shared", "thiserror 2.0.20", @@ -4122,7 +4067,7 @@ checksum = "aac1a05add837d45d562f48bc769c9b264440741aab319fed48ce6a696e6aa0c" dependencies = [ "bytes", "memchr", - "rand 0.10.2", + "rand", "rtc-shared", "serde", ] @@ -4136,7 +4081,7 @@ dependencies = [ "bytes", "crc32c", "log", - "rand 0.10.2", + "rand", "rtc-shared", "rustc-hash", "slab", @@ -4149,7 +4094,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0379bb552d4813e042d376db99f2005038017fc7864676fc835334b810db015" dependencies = [ - "rand 0.10.2", + "rand", "rtc-shared", "url", ] @@ -4166,7 +4111,7 @@ dependencies = [ "bytes", "nix", "p256", - "rand 0.10.2", + "rand", "rcgen", "sec1 0.7.3", "serde", @@ -4206,7 +4151,7 @@ dependencies = [ "crc", "lazy_static", "md-5", - "rand 0.10.2", + "rand", "ring", "rtc-shared", "sansio", @@ -4316,9 +4261,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.13" +version = "0.103.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +checksum = "0527518605e68109d875e248ea259b6758801cf165e4b2c2733ae3b51f12535a" dependencies = [ "ring", "rustls-pki-types", @@ -4872,9 +4817,9 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" dependencies = [ "displaydoc", "zerovec", @@ -5295,9 +5240,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.24.0" +version = "1.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9" dependencies = [ "getrandom 0.4.3", "js-sys", @@ -5343,15 +5288,6 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" -[[package]] -name = "wasip2" -version = "1.0.4+wasi-0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" -dependencies = [ - "wit-bindgen 0.57.1", -] - [[package]] name = "wasm-bindgen" version = "0.2.127" @@ -5464,17 +5400,6 @@ dependencies = [ "semver", ] -[[package]] -name = "wasmparser" -version = "0.255.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b" -dependencies = [ - "bitflags 2.13.1", - "indexmap", - "semver", -] - [[package]] name = "wasmparser" version = "0.256.0" @@ -5740,7 +5665,7 @@ dependencies = [ "cfg-if", "futures", "io-lifetimes 3.0.1", - "rand 0.10.2", + "rand", "rustix", "thiserror 2.0.20", "tokio", @@ -6142,12 +6067,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "wit-bindgen" -version = "0.57.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" - [[package]] name = "wit-bindgen" version = "0.60.0" @@ -6220,15 +6139,15 @@ dependencies = [ [[package]] name = "wit-encoder" -version = "0.255.0" +version = "0.256.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cebd2d14c3296dada506c67c1ee5c9440811a2fe88a83367a606ccbd30ebfde" +checksum = "b8085a1e724fa8c48f9f5489b9d8f121eb9fc2554ebcdaf6936256d854b24390" dependencies = [ "id-arena", "pretty_assertions", "semver", "serde", - "wit-parser 0.255.0", + "wit-parser 0.256.0", ] [[package]] @@ -6271,9 +6190,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.255.0" +version = "0.256.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5f6371fc71f15730b756c1dea3562a67adab1a7e519c4ca010173d883695bb" +checksum = "fa073dadefba859cb03f689a0c2e3efc51f883bf86b907eaa9709bfd9e3a00a9" dependencies = [ "anyhow", "hashbrown 0.17.1", @@ -6285,7 +6204,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-ident", - "wasmparser 0.255.0", + "wasmparser 0.256.0", ] [[package]] @@ -6301,9 +6220,9 @@ dependencies = [ [[package]] name = "writeable" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" [[package]] name = "x25519-dalek" @@ -6460,9 +6379,9 @@ dependencies = [ [[package]] name = "zerotrie" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" dependencies = [ "displaydoc", "yoke", @@ -6471,9 +6390,9 @@ dependencies = [ [[package]] name = "zerovec" -version = "0.11.6" +version = "0.11.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +checksum = "94b5c6b5976d66c1d703c4fd17d3f5e43c8cedaacf604961b171adc7130896d8" dependencies = [ "yoke", "zerofrom", @@ -6482,13 +6401,13 @@ dependencies = [ [[package]] name = "zerovec-derive" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +checksum = "47402523226a02bfe5230160dc3ccc089aa6f6f19e7fcbb4e6f824bbb1b4aa62" dependencies = [ "proc-macro2", "quote", - "syn 2.0.119", + "syn 3.0.3", ] [[package]] diff --git a/crates/pumpkin-config/src/lib.rs b/crates/pumpkin-config/src/lib.rs index 7f260236f..02116895f 100644 --- a/crates/pumpkin-config/src/lib.rs +++ b/crates/pumpkin-config/src/lib.rs @@ -12,7 +12,7 @@ use recipe::RecipeConfig; use serde::{Deserialize, Serialize, de::DeserializeOwned}; use std::path::PathBuf; -use std::{fs, num::NonZeroU8, path::Path}; +use std::{fs, num::NonZero, path::Path}; use tracing::{debug, error, warn}; /// Fun and experimental configuration options. @@ -87,8 +87,8 @@ impl LoadConfiguration for PumpkinConfig { self.basic.validate(); self.advanced.validate(); - let min_vd = NonZeroU8::MIN; - let Some(max_vd) = NonZeroU8::new(64) else { + let min_vd = NonZero::::MIN; + let Some(max_vd) = NonZero::new(64) else { return; }; diff --git a/crates/pumpkin-config/src/networking/bedrock.rs b/crates/pumpkin-config/src/networking/bedrock.rs index 420da2e6c..1e2a5c7ce 100644 --- a/crates/pumpkin-config/src/networking/bedrock.rs +++ b/crates/pumpkin-config/src/networking/bedrock.rs @@ -1,7 +1,7 @@ use crate::CompressionConfig; use serde::{Deserialize, Serialize}; use std::net::{IpAddr, SocketAddr}; -use std::num::NonZeroU8; +use std::num::NonZero; use std::path::PathBuf; /// Configuration for Bedrock authentication. @@ -71,9 +71,9 @@ pub struct BedrockConfig { /// The maximum number of players allowed on the server. Specifying `0` disables the limit. pub max_players: u32, /// The maximum view distance for players. - pub view_distance: NonZeroU8, + pub view_distance: NonZero, /// The maximum simulated view distance. - pub simulation_distance: NonZeroU8, + pub simulation_distance: NonZero, /// Bedrock Edition packet compression settings. pub compression: CompressionConfig, /// Message of the Day; the server's description displayed on the status screen. @@ -88,8 +88,8 @@ pub struct BedrockConfig { impl Default for BedrockConfig { fn default() -> Self { - let view_distance = NonZeroU8::new(16).unwrap_or(NonZeroU8::MIN); - let simulation_distance = NonZeroU8::new(10).unwrap_or(NonZeroU8::MIN); + let view_distance = NonZero::new(16).unwrap_or(NonZero::::MIN); + let simulation_distance = NonZero::new(10).unwrap_or(NonZero::::MIN); Self { enabled: true, online_mode: true, diff --git a/crates/pumpkin-config/src/networking/java.rs b/crates/pumpkin-config/src/networking/java.rs index 18cb9153b..3e40f07ba 100644 --- a/crates/pumpkin-config/src/networking/java.rs +++ b/crates/pumpkin-config/src/networking/java.rs @@ -1,7 +1,7 @@ use crate::{AuthenticationConfig, CompressionConfig}; use serde::{Deserialize, Serialize}; use std::net::SocketAddr; -use std::num::NonZeroU8; +use std::num::NonZero; /// Configuration for Java Edition client connections. #[derive(Deserialize, Serialize, Clone)] @@ -18,9 +18,9 @@ pub struct JavaConfig { /// The maximum number of players allowed on the server. Specifying `0` disables the limit. pub max_players: u32, /// The maximum view distance for players. - pub view_distance: NonZeroU8, + pub view_distance: NonZero, /// The maximum simulated view distance. - pub simulation_distance: NonZeroU8, + pub simulation_distance: NonZero, /// Java Edition packet compression settings. pub compression: CompressionConfig, /// Message of the Day; the server's description displayed on the status screen. @@ -34,8 +34,8 @@ impl Default for JavaConfig { let address = "0.0.0.0:25565" .parse() .unwrap_or_else(|_| std::net::SocketAddr::from(([0, 0, 0, 0], 25565))); - let view_distance = NonZeroU8::new(16).unwrap_or(NonZeroU8::MIN); - let simulation_distance = NonZeroU8::new(10).unwrap_or(NonZeroU8::MIN); + let view_distance = NonZero::new(16).unwrap_or(NonZero::::MIN); + let simulation_distance = NonZero::new(10).unwrap_or(NonZero::::MIN); Self { enabled: true, address, diff --git a/crates/pumpkin-data/src/generated/carver.rs b/crates/pumpkin-data/src/generated/carver.rs index fb5e6d6b7..e1bc83663 100644 --- a/crates/pumpkin-data/src/generated/carver.rs +++ b/crates/pumpkin-data/src/generated/carver.rs @@ -21,7 +21,7 @@ pub struct TrapezoidHeightProvider { pub struct VeryBiasedToBottomHeightProvider { pub min_inclusive: YOffset, pub max_inclusive: YOffset, - pub inner: Option, + pub inner: Option>, } pub struct CaveCarverConfig { pub horizontal_radius_multiplier: FloatProvider, diff --git a/crates/pumpkin-data/src/generated/chunk_gen_settings.rs b/crates/pumpkin-data/src/generated/chunk_gen_settings.rs index a6bd1fcb8..9306a197a 100644 --- a/crates/pumpkin-data/src/generated/chunk_gen_settings.rs +++ b/crates/pumpkin-data/src/generated/chunk_gen_settings.rs @@ -6,7 +6,7 @@ use crate::dimension::Dimension; use pumpkin_util::random::RandomDeriver; use pumpkin_util::y_offset::Absolute; use pumpkin_util::y_offset::YOffset; -use std::{cell::RefCell, num::NonZeroUsize}; +use std::cell::RefCell; pub struct GenerationSettings { pub aquifers_enabled: bool, pub ore_veins_enabled: bool, diff --git a/crates/pumpkin-data/src/generated/placed_features_generated.rs b/crates/pumpkin-data/src/generated/placed_features_generated.rs index ddc832543..f13aeb651 100644 --- a/crates/pumpkin-data/src/generated/placed_features_generated.rs +++ b/crates/pumpkin-data/src/generated/placed_features_generated.rs @@ -6359,7 +6359,7 @@ fn build_placed_features() height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }), max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }), - inner: std::num::NonZeroU32::new(8u32), + inner: std::num::NonZero::new(8u32), }), }), PlacementModifier::Biome(BiomePlacementModifier), @@ -6381,7 +6381,7 @@ fn build_placed_features() height: HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { min_inclusive: YOffset::AboveBottom(AboveBottom { above_bottom: 0i8 }), max_inclusive: YOffset::BelowTop(BelowTop { below_top: 8i8 }), - inner: std::num::NonZeroU32::new(8u32), + inner: std::num::NonZero::new(8u32), }), }), PlacementModifier::Biome(BiomePlacementModifier), diff --git a/crates/pumpkin-data/src/generated/tracked_data.rs b/crates/pumpkin-data/src/generated/tracked_data.rs index 188474365..eeb382832 100644 --- a/crates/pumpkin-data/src/generated/tracked_data.rs +++ b/crates/pumpkin-data/src/generated/tracked_data.rs @@ -552,6 +552,42 @@ impl TrackedData { v26_1: 16u8, v26_2: 16u8, }; + pub const CAT_COLLAR_COLOR: TrackedId = TrackedId { + v1_21: 22u8, + v1_21_2: 22u8, + v1_21_4: 22u8, + v1_21_5: 22u8, + v1_21_6: 22u8, + v1_21_7: 22u8, + v1_21_9: 22u8, + v1_21_11: 22u8, + v26_1: 23u8, + v26_2: 23u8, + }; + pub const CAT_COLLAR_COLOR_ID: TrackedId = TrackedId { + v1_21: 22u8, + v1_21_2: 22u8, + v1_21_4: 22u8, + v1_21_5: 22u8, + v1_21_6: 22u8, + v1_21_7: 22u8, + v1_21_9: 22u8, + v1_21_11: 22u8, + v26_1: 23u8, + v26_2: 23u8, + }; + pub const CAT_SOUND_VARIANT_ID: TrackedId = TrackedId { + v1_21: 255u8, + v1_21_2: 255u8, + v1_21_4: 255u8, + v1_21_5: 23u8, + v1_21_6: 23u8, + v1_21_7: 23u8, + v1_21_9: 23u8, + v1_21_11: 23u8, + v26_1: 24u8, + v26_2: 24u8, + }; pub const CAT_VARIANT: TrackedId = TrackedId { v1_21: 19u8, v1_21_2: 19u8, @@ -561,8 +597,20 @@ impl TrackedData { v1_21_7: 19u8, v1_21_9: 19u8, v1_21_11: 19u8, - v26_1: 255u8, - v26_2: 255u8, + v26_1: 20u8, + v26_2: 20u8, + }; + pub const CAT_VARIANT_ID: TrackedId = TrackedId { + v1_21: 19u8, + v1_21_2: 19u8, + v1_21_4: 19u8, + v1_21_5: 19u8, + v1_21_6: 19u8, + v1_21_7: 19u8, + v1_21_9: 19u8, + v1_21_11: 19u8, + v26_1: 20u8, + v26_2: 20u8, }; pub const CAUGHT_FISH: TrackedId = TrackedId { v1_21: 9u8, @@ -1326,11 +1374,11 @@ impl TrackedData { v1_21_4: 21u8, v1_21_5: 21u8, v1_21_6: 21u8, - v1_21_7: 255u8, + v1_21_7: 21u8, v1_21_9: 21u8, v1_21_11: 21u8, - v26_1: 255u8, - v26_2: 255u8, + v26_1: 22u8, + v26_2: 22u8, }; pub const HEAD_POSE: TrackedId = TrackedId { v1_21: 255u8, @@ -1830,11 +1878,11 @@ impl TrackedData { v1_21_4: 20u8, v1_21_5: 20u8, v1_21_6: 20u8, - v1_21_7: 255u8, + v1_21_7: 20u8, v1_21_9: 20u8, v1_21_11: 20u8, - v26_1: 255u8, - v26_2: 255u8, + v26_1: 21u8, + v26_2: 21u8, }; pub const IRON_GOLEM_FLAGS: TrackedId = TrackedId { v1_21: 16u8, @@ -1933,14 +1981,14 @@ impl TrackedData { v26_2: 18u8, }; pub const IS_LYING: TrackedId = TrackedId { - v1_21: 255u8, - v1_21_2: 255u8, - v1_21_4: 255u8, - v1_21_5: 255u8, - v1_21_6: 255u8, - v1_21_7: 255u8, - v1_21_9: 255u8, - v1_21_11: 255u8, + v1_21: 20u8, + v1_21_2: 20u8, + v1_21_4: 20u8, + v1_21_5: 20u8, + v1_21_6: 20u8, + v1_21_7: 20u8, + v1_21_9: 20u8, + v1_21_11: 20u8, v26_1: 21u8, v26_2: 21u8, }; @@ -2701,14 +2749,14 @@ impl TrackedData { v26_2: 8u8, }; pub const RELAX_STATE_ONE: TrackedId = TrackedId { - v1_21: 255u8, - v1_21_2: 255u8, - v1_21_4: 255u8, - v1_21_5: 255u8, - v1_21_6: 255u8, - v1_21_7: 255u8, - v1_21_9: 255u8, - v1_21_11: 255u8, + v1_21: 21u8, + v1_21_2: 21u8, + v1_21_4: 21u8, + v1_21_5: 21u8, + v1_21_6: 21u8, + v1_21_7: 21u8, + v1_21_9: 21u8, + v1_21_11: 21u8, v26_1: 22u8, v26_2: 22u8, }; @@ -3090,21 +3138,21 @@ impl TrackedData { v1_21_4: 255u8, v1_21_5: 23u8, v1_21_6: 23u8, - v1_21_7: 255u8, + v1_21_7: 23u8, v1_21_9: 23u8, v1_21_11: 23u8, - v26_1: 255u8, - v26_2: 255u8, + v26_1: 24u8, + v26_2: 24u8, }; pub const SOUND_VARIANT_ID: TrackedId = TrackedId { v1_21: 255u8, v1_21_2: 255u8, v1_21_4: 255u8, - v1_21_5: 255u8, - v1_21_6: 255u8, - v1_21_7: 255u8, - v1_21_9: 255u8, - v1_21_11: 255u8, + v1_21_5: 23u8, + v1_21_6: 23u8, + v1_21_7: 23u8, + v1_21_9: 23u8, + v1_21_11: 23u8, v26_1: 24u8, v26_2: 24u8, }; @@ -3960,6 +4008,42 @@ impl TrackedData { v26_1: 20u8, v26_2: 20u8, }; + pub const WOLF_COLLAR_COLOR: TrackedId = TrackedId { + v1_21: 20u8, + v1_21_2: 20u8, + v1_21_4: 20u8, + v1_21_5: 20u8, + v1_21_6: 20u8, + v1_21_7: 20u8, + v1_21_9: 20u8, + v1_21_11: 20u8, + v26_1: 21u8, + v26_2: 21u8, + }; + pub const WOLF_COLLAR_COLOR_ID: TrackedId = TrackedId { + v1_21: 20u8, + v1_21_2: 20u8, + v1_21_4: 20u8, + v1_21_5: 20u8, + v1_21_6: 20u8, + v1_21_7: 20u8, + v1_21_9: 20u8, + v1_21_11: 20u8, + v26_1: 21u8, + v26_2: 21u8, + }; + pub const WOLF_SOUND_VARIANT_ID: TrackedId = TrackedId { + v1_21: 255u8, + v1_21_2: 255u8, + v1_21_4: 255u8, + v1_21_5: 23u8, + v1_21_6: 23u8, + v1_21_7: 23u8, + v1_21_9: 23u8, + v1_21_11: 23u8, + v26_1: 24u8, + v26_2: 24u8, + }; pub const WOLF_VARIANT_ID: TrackedId = TrackedId { v1_21: 22u8, v1_21_2: 22u8, diff --git a/crates/pumpkin-data/src/item_stack/mod.rs b/crates/pumpkin-data/src/item_stack/mod.rs index 123c1cc04..724d98393 100644 --- a/crates/pumpkin-data/src/item_stack/mod.rs +++ b/crates/pumpkin-data/src/item_stack/mod.rs @@ -15,7 +15,7 @@ use pumpkin_util::GameMode; use rand; use std::borrow::Cow; use std::cmp::{max, min}; -use std::num::NonZeroI32; +use std::num::NonZero; use std::sync::atomic::{AtomicU32, Ordering}; mod categories; @@ -42,7 +42,7 @@ pub struct ItemStack { // unique ID for Bedrock network; don't serialize // Should always be a positive value for non-empty stacks - pub uid: NonZeroI32, + pub uid: NonZero, } // impl Hash for ItemStack { @@ -71,14 +71,14 @@ impl ItemStackIdGenerator { } } - pub fn next_id(&self) -> NonZeroI32 { + pub fn next_id(&self) -> NonZero { // Wraps on overflow, which is what we want. let value = self.counter.fetch_add(1, Ordering::Relaxed); // Negative values are invalid; cycle through the positives let masked = value & 0x7FFFFFFF; - if let Some(id) = NonZeroI32::new(masked as i32) { + if let Some(id) = NonZero::new(masked as i32) { id } else { // If we fetched 0 or 0x80000000, that's masked out as 0 @@ -133,7 +133,7 @@ impl ItemStack { item, patch: Vec::new(), - uid: match NonZeroI32::new(1) { + uid: match NonZero::new(1) { Some(v) => v, None => panic!("1 is non-zero"), }, @@ -211,7 +211,7 @@ impl ItemStack { item: &Item::AIR, patch: Vec::new(), - uid: NonZeroI32::MIN, // white lie - Bedrock `uid` is never sent if the stack is empty + uid: NonZero::::MIN, // white lie - Bedrock `uid` is never sent if the stack is empty }; #[must_use] diff --git a/crates/pumpkin-protocol/src/bedrock/network_item.rs b/crates/pumpkin-protocol/src/bedrock/network_item.rs index 0a8c09101..e2d238211 100644 --- a/crates/pumpkin-protocol/src/bedrock/network_item.rs +++ b/crates/pumpkin-protocol/src/bedrock/network_item.rs @@ -1,5 +1,5 @@ use std::io::{Error, ErrorKind, Read, Write}; -use std::num::NonZeroI32; +use std::num::NonZero; use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping}; use pumpkin_data::item_stack::ItemStack; @@ -155,7 +155,7 @@ pub struct ItemStackWrapper { pub place_on_blocks: Vec, pub destroy_blocks: Vec, pub shield_blocking_tick: i64, - pub net_id: Option, + pub net_id: Option>, } impl PacketWrite for ItemStackWrapper { @@ -192,7 +192,7 @@ impl PacketRead for ItemStackWrapper { let has_net_id = bool::read(buf)?; let net_id = if has_net_id { let stack_id = VarInt::read(buf)?; - NonZeroI32::new(stack_id.0) + NonZero::new(stack_id.0) } else { None }; @@ -256,7 +256,7 @@ pub struct NetworkItemStackDescriptor { pub aux_value: VarUInt, pub block_runtime_id: VarUInt, pub extra_data: Vec, - pub net_id: Option, + pub net_id: Option>, } impl PacketWrite for NetworkItemStackDescriptor { @@ -290,7 +290,7 @@ impl PacketRead for NetworkItemStackDescriptor { let has_net_id = bool::read(buf)?; let net_id = if has_net_id { let stack_id = VarInt::read(buf)?; - NonZeroI32::new(stack_id.0) + NonZero::new(stack_id.0) } else { None }; diff --git a/crates/pumpkin-protocol/src/codec/var_int.rs b/crates/pumpkin-protocol/src/codec/var_int.rs index 734d090d8..1332c65ea 100644 --- a/crates/pumpkin-protocol/src/codec/var_int.rs +++ b/crates/pumpkin-protocol/src/codec/var_int.rs @@ -1,6 +1,6 @@ use std::{ io::{Error, ErrorKind, Read, Write}, - num::NonZeroUsize, + num::NonZero, ops::Deref, }; @@ -20,9 +20,7 @@ pub type VarIntType = i32; pub struct VarInt(pub VarIntType); impl VarInt { - /// The maximum number of bytes a `VarInt` can occupy. - #[allow(clippy::unwrap_used)] - pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap(); + pub const MAX_SIZE: NonZero = NonZero::new(5).expect("5 is non-zero"); #[must_use] #[inline] diff --git a/crates/pumpkin-protocol/src/codec/var_long.rs b/crates/pumpkin-protocol/src/codec/var_long.rs index 4d97e1116..55392ac85 100644 --- a/crates/pumpkin-protocol/src/codec/var_long.rs +++ b/crates/pumpkin-protocol/src/codec/var_long.rs @@ -1,6 +1,6 @@ use std::{ io::{Error, Read, Write}, - num::NonZeroUsize, + num::NonZero, ops::Deref, }; @@ -19,9 +19,7 @@ pub type VarLongType = i64; pub struct VarLong(pub VarLongType); impl VarLong { - /// The maximum number of bytes a `VarLong` can occupy. - #[allow(clippy::unwrap_used)] - pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap(); + pub const MAX_SIZE: NonZero = NonZero::new(10).expect("10 is non-zero"); #[must_use] #[inline] diff --git a/crates/pumpkin-protocol/src/codec/var_uint.rs b/crates/pumpkin-protocol/src/codec/var_uint.rs index 0475037aa..e08feb0ad 100644 --- a/crates/pumpkin-protocol/src/codec/var_uint.rs +++ b/crates/pumpkin-protocol/src/codec/var_uint.rs @@ -1,6 +1,6 @@ use std::{ io::{Error, ErrorKind, Read, Write}, - num::NonZeroUsize, + num::NonZero, }; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; @@ -19,9 +19,7 @@ pub type VarUIntType = u32; pub struct VarUInt(pub VarUIntType); impl VarUInt { - /// The maximum number of bytes a `VarUInt` can occupy. - #[allow(clippy::unwrap_used)] - pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(5).unwrap(); + pub const MAX_SIZE: NonZero = NonZero::new(5).expect("5 is non-zero"); #[must_use] #[inline] diff --git a/crates/pumpkin-protocol/src/codec/var_ulong.rs b/crates/pumpkin-protocol/src/codec/var_ulong.rs index 6d3139712..4d3655e69 100644 --- a/crates/pumpkin-protocol/src/codec/var_ulong.rs +++ b/crates/pumpkin-protocol/src/codec/var_ulong.rs @@ -1,6 +1,6 @@ use std::{ io::{Error, Read, Write}, - num::NonZeroUsize, + num::NonZero, ops::Deref, }; @@ -19,9 +19,7 @@ pub type VarULongType = u64; pub struct VarULong(pub VarULongType); impl VarULong { - /// The maximum number of bytes a `VarULong` can occupy. - #[allow(clippy::unwrap_used)] - pub const MAX_SIZE: NonZeroUsize = NonZeroUsize::new(10).unwrap(); + pub const MAX_SIZE: NonZero = NonZero::new(10).expect("10 is non-zero"); #[must_use] #[inline] diff --git a/crates/pumpkin-world/src/chunk/io/file_manager.rs b/crates/pumpkin-world/src/chunk/io/file_manager.rs index 671956c2b..e0a313d09 100644 --- a/crates/pumpkin-world/src/chunk/io/file_manager.rs +++ b/crates/pumpkin-world/src/chunk/io/file_manager.rs @@ -225,22 +225,40 @@ where .map(|c| P::file_path(folder, &S::get_chunk_key(c))) .collect(); - let mut watchers = self.watchers.write().await; - for path in paths { - if let std::collections::btree_map::Entry::Occupied(mut e) = watchers.entry(path) { - let count = e.get_mut(); - *count = count.saturating_sub(1); - if *count == 0 { - e.remove(); + let mut paths_to_evict = Vec::new(); + { + let mut watchers = self.watchers.write().await; + for path in paths { + if let std::collections::btree_map::Entry::Occupied(mut e) = + watchers.entry(path) + { + let count = e.get_mut(); + *count = count.saturating_sub(1); + if *count == 0 { + let (path, _) = e.remove_entry(); + paths_to_evict.push(path); + } } } } + + for path in paths_to_evict { + self.maybe_evict(&path).await; + } }) } fn clear_watched_chunks(&self) -> BoxFuture<'_, ()> { Box::pin(async move { - self.watchers.write().await.clear(); + let paths: Vec = { + let mut watchers = self.watchers.write().await; + let keys: Vec<_> = watchers.keys().cloned().collect(); + watchers.clear(); + keys + }; + for path in paths { + self.maybe_evict(&path).await; + } }) } @@ -301,6 +319,9 @@ where }; join!(forward, read); + + // Evict if not watched and references are dropped + self.maybe_evict(&path).await; } }); diff --git a/crates/pumpkin-world/src/chunk_system/channel.rs b/crates/pumpkin-world/src/chunk_system/channel.rs index 8642116cc..190d578fa 100644 --- a/crates/pumpkin-world/src/chunk_system/channel.rs +++ b/crates/pumpkin-world/src/chunk_system/channel.rs @@ -132,10 +132,14 @@ impl LevelChannel { && !level.should_save.load(SeqCst) && !level.shut_down_chunk_system.load(SeqCst) { - lock = self + let (new_lock, timeout_res) = self .notify - .wait(lock) + .wait_timeout(lock, std::time::Duration::from_secs(1)) .unwrap_or_else(std::sync::PoisonError::into_inner); + lock = new_lock; + if timeout_res.timed_out() { + break; + } } let mut ret = (None, None); swap(&mut ret, &mut *lock); diff --git a/crates/pumpkin-world/src/chunk_system/dag.rs b/crates/pumpkin-world/src/chunk_system/dag.rs index df897b466..ef722915b 100644 --- a/crates/pumpkin-world/src/chunk_system/dag.rs +++ b/crates/pumpkin-world/src/chunk_system/dag.rs @@ -74,4 +74,46 @@ impl DAG { *edge = self.edges.insert(Edge::new(to, *edge)); } } + + pub fn drop_edge_chain(&mut self, mut head: EdgeKey) { + while !head.is_null() { + if let Some(edge) = self.edges.remove(head) { + head = edge.next; + } else { + break; + } + } + } + + pub fn prune_edge_chain(&mut self, head: &mut EdgeKey) -> bool { + let mut cur_edge = *head; + let mut prev_edge = EdgeKey::null(); + let mut change_head = None; + let mut has_valid_target = false; + + while !cur_edge.is_null() { + let Some(edge) = self.edges.get(cur_edge) else { + break; + }; + if self.nodes.contains_key(edge.to) { + prev_edge = cur_edge; + cur_edge = edge.next; + has_valid_target = true; + } else { + let next = edge.next; + self.edges.remove(cur_edge); + cur_edge = next; + if prev_edge.is_null() { + change_head = Some(next); + } else if let Some(prev) = self.edges.get_mut(prev_edge) { + prev.next = next; + } + } + } + if let Some(next) = change_head { + *head = next; + } + + has_valid_target + } } diff --git a/crates/pumpkin-world/src/chunk_system/schedule.rs b/crates/pumpkin-world/src/chunk_system/schedule.rs index 6c947bc63..04643d89b 100644 --- a/crates/pumpkin-world/src/chunk_system/schedule.rs +++ b/crates/pumpkin-world/src/chunk_system/schedule.rs @@ -508,10 +508,16 @@ impl GenerationSchedule { } } } - if new_stage == StagedChunkEnum::None - && holder.dependency_stage == StagedChunkEnum::None - { - self.unload_chunks.insert(pos); + if new_stage == StagedChunkEnum::None { + if holder.dependency_stage != StagedChunkEnum::None { + let has_valid_task = self.graph.prune_edge_chain(&mut holder.occupied_by); + if !has_valid_task { + holder.dependency_stage = StagedChunkEnum::None; + } + } + if holder.dependency_stage == StagedChunkEnum::None { + self.unload_chunks.insert(pos); + } } } else { if old_stage == StagedChunkEnum::None { @@ -588,14 +594,120 @@ impl GenerationSchedule { true } - fn garbage_collect_dependencies(&mut self) { - // Garbage collect stranded dependencies - let mut stranded = Vec::new(); - for (pos, holder) in &self.chunk_map { - if holder.target_stage == StagedChunkEnum::None - && holder.dependency_stage != StagedChunkEnum::None + fn recompute_dependency_stages(&mut self) { + let mut required: HashMapType = HashMapType::default(); + let mut worklist: Vec<(ChunkPos, StagedChunkEnum)> = self + .chunk_map + .iter() + .filter(|(_, holder)| holder.target_stage != StagedChunkEnum::None) + .map(|(pos, holder)| (*pos, holder.target_stage)) + .collect(); + + while let Some((pos, req)) = worklist.pop() { + let entry = required.entry(pos).or_insert(StagedChunkEnum::None); + if *entry >= req { + continue; + } + let start = *entry as u8 + 1; + *entry = req; + + // Only expand the stages that were not already accounted for. + for i in start..=(req as u8) { + let stage = StagedChunkEnum::from(i); + let radius = stage.get_direct_radius(); + if radius == 0 { + continue; + } + let dependencies = stage.get_direct_dependencies(); + for dx in -radius..=radius { + for dz in -radius..=radius { + if dx == 0 && dz == 0 { + continue; + } + let neighbor = pos.add_raw(dx, dz); + worklist.push((neighbor, dependencies[dx.abs().max(dz.abs()) as usize])); + } + } + } + } + + let mut nodes_to_drop = Vec::new(); + let mut newly_unused = Vec::new(); + for (pos, holder) in &mut self.chunk_map { + let new_dependency = required.get(pos).copied().unwrap_or(StagedChunkEnum::None); + if new_dependency >= holder.dependency_stage { + continue; + } + holder.dependency_stage = new_dependency; + + let effective_target = holder.target_stage.max(new_dependency); + for i in (effective_target as usize + 1)..StagedChunkEnum::COUNT { + let task = holder.tasks[i]; + if !task.is_null() { + nodes_to_drop.push((*pos, i, task)); + } + } + if effective_target == StagedChunkEnum::None { + newly_unused.push(*pos); + } + } + self.unload_chunks.extend(newly_unused); + + for (pos, index, task) in nodes_to_drop { + if self + .graph + .nodes + .get(task) + .is_some_and(|node| node.in_flight) { - stranded.push(*pos); + continue; + } + self.waiting_for_chunks.remove(&task); + self.drop_node(task); + if let Some(holder) = self.chunk_map.get_mut(&pos) { + holder.tasks[index] = NodeKey::null(); + } + } + + self.purge_dropped_queue_entries(); + } + + /// Drop heap entries whose node has been cancelled. They are skipped when popped, + /// but a saturated queue is never drained, so without this the heap keeps every + /// cancelled task of every chunk the player has flown past and `sort_queue` gets + /// slower on each level change. + fn purge_dropped_queue_entries(&mut self) { + if self.queue.is_empty() { + return; + } + let graph = &self.graph; + let tasks: Vec<_> = self + .queue + .drain() + .filter(|task| graph.nodes.contains_key(task.1)) + .collect(); + self.queue = BinaryHeap::from(tasks); + } + + fn garbage_collect_dependencies(&mut self) { + self.recompute_dependency_stages(); + + // Garbage collect stranded dependencies and empty holders + let mut stranded = Vec::new(); + let mut empty_holders = Vec::new(); + + for (pos, holder) in &self.chunk_map { + if holder.target_stage == StagedChunkEnum::None { + if holder.dependency_stage != StagedChunkEnum::None { + stranded.push(*pos); + } else if holder.current_stage == StagedChunkEnum::None + && holder.chunk.is_none() + && holder.occupied.is_null() + && holder.tasks.iter().all(Key::is_null) + && !holder.public + { + empty_holders.push(*pos); + } } } @@ -605,41 +717,19 @@ impl GenerationSchedule { continue; } - let mut cur_edge = holder.occupied_by; - let mut prev_edge = EdgeKey::null(); - let mut change_head = None; - let mut has_valid_task = false; - - while !cur_edge.is_null() { - let edge = self.graph.edges.get(cur_edge).expect("edge exists"); - if self.graph.nodes.contains_key(edge.to) { - prev_edge = cur_edge; - cur_edge = edge.next; - has_valid_task = true; - } else { - let next = edge.next; - self.graph.edges.remove(cur_edge); - cur_edge = next; - if prev_edge.is_null() { - change_head = Some(next); - } else { - self.graph - .edges - .get_mut(prev_edge) - .expect("edge exists") - .next = next; - } - } - } - if let Some(next) = change_head { - holder.occupied_by = next; - } - + let has_valid_task = self.graph.prune_edge_chain(&mut holder.occupied_by); if !has_valid_task { holder.dependency_stage = StagedChunkEnum::None; self.unload_chunks.insert(pos); } } + + for pos in empty_holders { + if let Some(mut holder) = self.chunk_map.remove(&pos) { + self.graph.drop_edge_chain(holder.occupied_by); + holder.occupied_by = EdgeKey::null(); + } + } } fn process_unload_queue(&mut self) { @@ -671,6 +761,9 @@ impl GenerationSchedule { } } + self.graph.drop_edge_chain(holder.occupied_by); + holder.occupied_by = EdgeKey::null(); + if holder.public { self.public_chunk_map.remove(&pos); holder.public = false; @@ -854,6 +947,15 @@ impl GenerationSchedule { } } } + // If this chunk was only loaded for a dependency or cancelled + // and is no longer needed, clear dependency_stage and queue unload. + if holder.target_stage == StagedChunkEnum::None + && holder.current_stage >= holder.dependency_stage + { + holder.dependency_stage = StagedChunkEnum::None; + self.unload_chunks.insert(pos); + } + holder.chunk = Some(chunk); self.chunk_map.insert(pos, holder); @@ -1087,13 +1189,15 @@ impl GenerationSchedule { } // 1. Get latest world state (player moves, etc) - self.resort_work(self.send_level.get()); + if self.resort_work(self.send_level.get()) { + self.garbage_collect_dependencies(); + } // Process unload queue periodically (every 1 second) to batch writes together // and act as a brief memory cache if a player walks back into the chunk. - if !self.unload_chunks.is_empty() - && self.last_unload.elapsed() >= std::time::Duration::from_secs(1) - { + // This must run even when the queue is empty: `garbage_collect_dependencies` + // is what puts stale dependency holders into the queue in the first place. + if self.last_unload.elapsed() >= std::time::Duration::from_secs(1) { self.garbage_collect_dependencies(); self.process_unload_queue(); self.last_unload = std::time::Instant::now(); @@ -1130,6 +1234,7 @@ impl GenerationSchedule { self.receive_chunk(pos, data); if self.resort_work(self.send_level.get()) { // If world state changed, we MUST re-sort before continuing + self.garbage_collect_dependencies(); self.queue.push(task); self.queue_dirty = true; break 'out2; @@ -1357,11 +1462,15 @@ impl GenerationSchedule { match self.recv_chunk.recv_timeout(Duration::from_millis(5)) { Ok((pos, data)) => { self.receive_chunk(pos, data); - self.resort_work(self.send_level.get()); + if self.resort_work(self.send_level.get()) { + self.garbage_collect_dependencies(); + } } Err(crossfire::compat::RecvTimeoutError::Timeout) => { // Periodically check LevelChannel for new requests - self.resort_work(self.send_level.get()); + if self.resort_work(self.send_level.get()) { + self.garbage_collect_dependencies(); + } } Err(crossfire::compat::RecvTimeoutError::Disconnected) => break, } @@ -1381,7 +1490,9 @@ impl GenerationSchedule { } debug_assert!(self.debug_check()); debug_assert_eq!(self.running_task_count, 0); - self.resort_work(self.send_level.wait_and_get(level)); + if self.resort_work(self.send_level.wait_and_get(level)) { + self.garbage_collect_dependencies(); + } } if self.queue_dirty { self.sort_queue(); @@ -1436,6 +1547,9 @@ impl GenerationSchedule { nodes_to_drop.push(holder.occupied); holder.occupied = NodeKey::null(); } + + self.graph.drop_edge_chain(holder.occupied_by); + holder.occupied_by = EdgeKey::null(); } for node_key in nodes_to_drop { diff --git a/crates/pumpkin-world/src/chunk_system/tests.rs b/crates/pumpkin-world/src/chunk_system/tests.rs index 1c4c76d97..dc636e0d9 100644 --- a/crates/pumpkin-world/src/chunk_system/tests.rs +++ b/crates/pumpkin-world/src/chunk_system/tests.rs @@ -355,3 +355,64 @@ fn cancellation_path_decrements_in_degree() { "The waiting task should have been dropped during cancellation" ); } + +#[test] +fn dag_drop_edge_chain_removes_all_edges() { + let mut graph = DAG::default(); + let node1 = graph + .nodes + .insert(Node::new(ChunkPos::new(0, 0), StagedChunkEnum::Empty)); + let node2 = graph + .nodes + .insert(Node::new(ChunkPos::new(1, 0), StagedChunkEnum::Empty)); + + let edge1 = graph.edges.insert(crate::chunk_system::dag::Edge::new( + node1, + crate::chunk_system::dag::EdgeKey::null(), + )); + let edge2 = graph + .edges + .insert(crate::chunk_system::dag::Edge::new(node2, edge1)); + + assert_eq!(graph.edges.len(), 2); + graph.drop_edge_chain(edge2); + assert_eq!(graph.edges.len(), 0); +} + +#[test] +fn dag_prune_edge_chain_removes_dead_target_edges() { + let mut graph = DAG::default(); + let node_alive = graph + .nodes + .insert(Node::new(ChunkPos::new(0, 0), StagedChunkEnum::Empty)); + let node_dead = graph + .nodes + .insert(Node::new(ChunkPos::new(1, 0), StagedChunkEnum::Empty)); + + let edge_alive = graph.edges.insert(crate::chunk_system::dag::Edge::new( + node_alive, + crate::chunk_system::dag::EdgeKey::null(), + )); + let edge_dead = graph + .edges + .insert(crate::chunk_system::dag::Edge::new(node_dead, edge_alive)); + + // Remove node_dead from graph.nodes to simulate completed/dropped task + graph.nodes.remove(node_dead); + + let mut head = edge_dead; + let has_valid_task = graph.prune_edge_chain(&mut head); + + assert!(has_valid_task); + assert_eq!(head, edge_alive); + assert_eq!(graph.edges.len(), 1); + assert!(graph.edges.contains_key(edge_alive)); + assert!(!graph.edges.contains_key(edge_dead)); + + // Now remove node_alive as well + graph.nodes.remove(node_alive); + let has_valid_task = graph.prune_edge_chain(&mut head); + assert!(!has_valid_task); + assert!(head.is_null()); + assert_eq!(graph.edges.len(), 0); +} diff --git a/crates/pumpkin-world/src/chunk_system/worker_logic.rs b/crates/pumpkin-world/src/chunk_system/worker_logic.rs index 45120956d..0ae22f0b4 100644 --- a/crates/pumpkin-world/src/chunk_system/worker_logic.rs +++ b/crates/pumpkin-world/src/chunk_system/worker_logic.rs @@ -38,7 +38,10 @@ fn needs_relighting(chunk: &crate::chunk::ChunkData, config: LightingEngineConfi return false; } - let engine = chunk.light_engine.lock().expect("Mutex poisoned"); + let engine = chunk + .light_engine + .lock() + .unwrap_or_else(std::sync::PoisonError::into_inner); // Scan for any complex lighting data let has_complex_light = engine.sky_light.iter().any(|lc| match lc { @@ -237,7 +240,14 @@ pub fn run_generation( _settings: &GenerationSettings, ) -> RecvChunk { let portal = level.world_portal.load_full(); - let portal_ref = portal.as_deref().expect("Portal should be initialized"); + let Some(portal_ref) = portal.as_deref() else { + error!("Chunk generation FAILED at {pos:?} ({stage:?}): World portal is not initialized"); + return RecvChunk::GenerationFailure { + pos, + stage, + error: "World portal is not initialized".to_string(), + }; + }; // Run generation with panic catching let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { cache.advance(stage, &level.world_gen, portal_ref, &level.lighting_config); diff --git a/crates/pumpkin-world/src/cylindrical_chunk_iterator.rs b/crates/pumpkin-world/src/cylindrical_chunk_iterator.rs index 558138aad..5389e257a 100644 --- a/crates/pumpkin-world/src/cylindrical_chunk_iterator.rs +++ b/crates/pumpkin-world/src/cylindrical_chunk_iterator.rs @@ -1,16 +1,16 @@ -use std::num::NonZeroU8; +use std::num::NonZero; use pumpkin_util::math::vector2::Vector2; #[derive(Clone, Copy, PartialEq, Eq)] pub struct Cylindrical { pub center: Vector2, - pub view_distance: NonZeroU8, + pub view_distance: NonZero, } impl Cylindrical { #[must_use] - pub const fn new(center: Vector2, view_distance: NonZeroU8) -> Self { + pub const fn new(center: Vector2, view_distance: NonZero) -> Self { Self { center, view_distance, @@ -100,17 +100,17 @@ impl Cylindrical { #[cfg(test)] mod test { - use std::num::NonZeroU8; + use std::num::NonZero; use super::Cylindrical; use pumpkin_util::math::vector2::Vector2; #[test] fn bounds() { - let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap()); + let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZero::new(1).unwrap()); for view_distance in 1..=32 { - cylinder.view_distance = NonZeroU8::new(view_distance).unwrap(); + cylinder.view_distance = NonZero::new(view_distance).unwrap(); for chunk in cylinder.all_chunks_within() { assert!(chunk.x >= cylinder.left() && chunk.x <= cylinder.right()); @@ -130,10 +130,10 @@ mod test { #[test] fn all_chunks_within_capacity_estimation() { - let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZeroU8::new(1).unwrap()); + let mut cylinder = Cylindrical::new(Vector2::new(0, 0), NonZero::new(1).unwrap()); for distance in 1..=64 { - cylinder.view_distance = NonZeroU8::new(distance).unwrap(); + cylinder.view_distance = NonZero::new(distance).unwrap(); let chunks = cylinder.all_chunks_within(); let estimated_capacity = ((distance as usize + 3).pow(2) * 3167) >> 10; diff --git a/crates/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs b/crates/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs index b1aba4140..9ad1ab4dc 100644 --- a/crates/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs +++ b/crates/pumpkin-world/src/generation/feature/features/random_boolean_selector.rs @@ -29,7 +29,10 @@ impl RandomBooleanFeature { } else { &self.feature_false }; - feature.get().generate( + let Some(feature) = feature.get() else { + return false; + }; + feature.generate( chunk, block_registry, min_y, diff --git a/crates/pumpkin-world/src/generation/feature/features/random_selector.rs b/crates/pumpkin-world/src/generation/feature/features/random_selector.rs index 7f0225219..1100fdc17 100644 --- a/crates/pumpkin-world/src/generation/feature/features/random_selector.rs +++ b/crates/pumpkin-world/src/generation/feature/features/random_selector.rs @@ -32,7 +32,20 @@ impl RandomFeature { if random.next_f32() >= feature.chance { continue; } - return feature.feature.get().generate( + if let Some(f) = feature.feature.get() { + return f.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ); + } + } + self.default.get().is_some_and(|default_feature| { + default_feature.generate( chunk, block_registry, min_y, @@ -40,16 +53,7 @@ impl RandomFeature { feature_name, random, pos, - ); - } - self.default.get().generate( - chunk, - block_registry, - min_y, - height, - feature_name, - random, - pos, - ) + ) + }) } } diff --git a/crates/pumpkin-world/src/generation/feature/placed_features.rs b/crates/pumpkin-world/src/generation/feature/placed_features.rs index 84cce2117..b2bdd9a4b 100644 --- a/crates/pumpkin-world/src/generation/feature/placed_features.rs +++ b/crates/pumpkin-world/src/generation/feature/placed_features.rs @@ -28,11 +28,11 @@ pub enum PlacedFeatureWrapper { } impl PlacedFeatureWrapper { - #[allow(clippy::expect_used)] - pub fn get(&self) -> &PlacedFeature { + #[must_use] + pub fn get(&self) -> Option<&PlacedFeature> { match self { - Self::Named(name) => PLACED_FEATURES.get(name).expect("Unknown placed feature"), - Self::Direct(feature) => feature, + Self::Named(name) => PLACED_FEATURES.get(name), + Self::Direct(feature) => Some(feature), } } } diff --git a/crates/pumpkin-world/src/generation/height_provider.rs b/crates/pumpkin-world/src/generation/height_provider.rs index 4f0ff4fda..751bf04e1 100644 --- a/crates/pumpkin-world/src/generation/height_provider.rs +++ b/crates/pumpkin-world/src/generation/height_provider.rs @@ -1,4 +1,4 @@ -use std::num::NonZeroU32; +use std::num::NonZero; use pumpkin_util::{ random::{RandomGenerator, RandomImpl}, @@ -25,7 +25,7 @@ impl HeightProvider { pub struct VeryBiasedToBottomHeightProvider { pub min_inclusive: YOffset, pub max_inclusive: YOffset, - pub inner: Option, + pub inner: Option>, } impl VeryBiasedToBottomHeightProvider { diff --git a/crates/pumpkin-world/src/level.rs b/crates/pumpkin-world/src/level.rs index 07ec206f6..df2457828 100644 --- a/crates/pumpkin-world/src/level.rs +++ b/crates/pumpkin-world/src/level.rs @@ -628,6 +628,10 @@ impl Level { self.chunk_watchers.shrink_to_fit(); } + if self.loaded_chunks.capacity() - self.loaded_chunks.len() >= 4096 { + self.loaded_chunks.shrink_to_fit(); + } + if self.loaded_entity_chunks.capacity() - self.loaded_entity_chunks.len() >= 4096 { self.loaded_entity_chunks.shrink_to_fit(); } diff --git a/crates/pumpkin/src/block/entities/chest_like_block_entity.rs b/crates/pumpkin/src/block/entities/chest_like_block_entity.rs index a46ff6b6f..c208b502c 100644 --- a/crates/pumpkin/src/block/entities/chest_like_block_entity.rs +++ b/crates/pumpkin/src/block/entities/chest_like_block_entity.rs @@ -118,12 +118,12 @@ macro_rules! impl_block_entity_for_chest { } fn take_loot_table(&self) -> Option<(String, i64)> { - let mut guard = self.loot_table.lock().expect("Loot table mutex should not be poisoned"); + let mut guard = self.loot_table.lock().unwrap_or_else(std::sync::PoisonError::into_inner); guard.take().map(|key| (key, self.loot_table_seed)) } fn has_loot_table(&self) -> bool { - self.loot_table.lock().expect("Loot table mutex should not be poisoned").is_some() + self.loot_table.lock().unwrap_or_else(std::sync::PoisonError::into_inner).is_some() } } }; diff --git a/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs b/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs index 25c438333..99627dee3 100644 --- a/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs +++ b/crates/pumpkin/src/block/entities/furnace_like_block_entity.rs @@ -147,7 +147,7 @@ macro_rules! impl_cooking_block_entity_base { .result .id .strip_prefix("minecraft:") - .expect("Recipe ID should have minecraft: prefix"), + .unwrap_or(&recipe.result.id), ) && !is_top_items_empty && recipe_output_item.id == side_item_stack.item.id && side_item_stack.item_count < max_count @@ -172,7 +172,7 @@ macro_rules! impl_cooking_block_entity_base { .result .id .strip_prefix("minecraft:") - .expect("Recipe ID should have minecraft: prefix"), + .unwrap_or(&recipe.result.id), ) else { return false; }; diff --git a/crates/pumpkin/src/command/args/entities.rs b/crates/pumpkin/src/command/args/entities.rs index d694ae95c..3d611bd48 100644 --- a/crates/pumpkin/src/command/args/entities.rs +++ b/crates/pumpkin/src/command/args/entities.rs @@ -186,7 +186,7 @@ impl TargetSelector { #[must_use] pub fn includes_entities(&self) -> bool { - let player_type = EntityType::from_name("player").expect("entity type player must exist"); + let player_type = &EntityType::PLAYER; let mut includes_entities = self.base_includes_entities(); for condition in &self.conditions { diff --git a/crates/pumpkin/src/command/args/time.rs b/crates/pumpkin/src/command/args/time.rs index 8bf3d625f..763991e83 100644 --- a/crates/pumpkin/src/command/args/time.rs +++ b/crates/pumpkin/src/command/args/time.rs @@ -3,11 +3,14 @@ use pumpkin_protocol::java::client::play::{ArgumentType, SuggestionProviders}; use crate::command::{ CommandSender, args::{ - Arg, ArgumentConsumer, ConsumeResult, DefaultNameArgConsumer, FindArg, - GetClientSideArgParser, + Arg, ArgumentConsumer, ConsumeResult, ConsumeResultWithSyntax, DefaultNameArgConsumer, + FindArg, GetClientSideArgParser, }, + argument_types::{argument_type::ArgumentType as _, time::TimeArgumentType}, dispatcher::CommandError, - tree::RawArgs, + errors::command_syntax_error::{CommandSyntaxError, CommandSyntaxErrorContext}, + string_reader::StringReader, + tree::{RawArg, RawArgs}, }; use crate::server::Server; @@ -34,6 +37,23 @@ impl TimeArgumentConsumer { } } +fn map_local_syntax_error(error: CommandSyntaxError, raw_arg: RawArg<'_>) -> CommandSyntaxError { + let local_cursor = error.context.map_or(0, |context| context.cursor); + let mut clamped_local_cursor = local_cursor.min(raw_arg.value.len()); + while clamped_local_cursor > 0 && !raw_arg.value.is_char_boundary(clamped_local_cursor) { + clamped_local_cursor -= 1; + } + + CommandSyntaxError { + error_type: error.error_type, + message: error.message, + context: Some(CommandSyntaxErrorContext { + input: raw_arg.input.to_string(), + cursor: raw_arg.start + clamped_local_cursor, + }), + } +} + impl GetClientSideArgParser for TimeArgumentConsumer { fn get_client_side_parser(&self) -> ArgumentType { ArgumentType::Time { min: self.min } @@ -54,30 +74,33 @@ impl ArgumentConsumer for TimeArgumentConsumer { let s_opt: Option<&'a str> = args.pop().map(|arg| arg.value); let result: Option> = s_opt.and_then(|s| { - let (num_str, unit) = s - .find(|c: char| c.is_alphabetic() && c != '-') - .map_or((s, "t"), |pos| (&s[..pos], &s[pos..])); - - let number = num_str.parse::().ok()?; - - let factor = match unit { - "d" => 24000.0, - "s" => 20.0, - "t" | "" => 1.0, - _ => return None, - }; - - let ticks = (number * factor).round() as i32; - - if ticks < self.min { - return None; - } - - Some(Arg::Time(ticks)) + let mut reader = StringReader::new(s); + let parser = TimeArgumentType::new(self.min); + parser.parse(&mut reader).ok().map(Arg::Time) }); Box::pin(async move { result }) } + + fn consume_with_syntax<'a>( + &'a self, + _sender: &'a CommandSender, + _server: &'a Server, + args: &mut RawArgs<'a>, + ) -> ConsumeResultWithSyntax<'a> { + let Some(raw_arg) = args.pop() else { + return Box::pin(async { Ok(None) }); + }; + + let mut reader = StringReader::new(raw_arg.value); + let parser = TimeArgumentType::new(self.min); + let result = parser + .parse(&mut reader) + .map(|ticks| Some(Arg::Time(ticks))) + .map_err(|error| map_local_syntax_error(error, raw_arg)); + + Box::pin(async move { result }) + } } impl DefaultNameArgConsumer for TimeArgumentConsumer { diff --git a/crates/pumpkin/src/command/argument_types/attribute.rs b/crates/pumpkin/src/command/argument_types/attribute.rs index 62714e1c0..bd49dcec6 100644 --- a/crates/pumpkin/src/command/argument_types/attribute.rs +++ b/crates/pumpkin/src/command/argument_types/attribute.rs @@ -42,7 +42,7 @@ impl ArgumentType for AttributeArgumentType { fn client_side_parser(&'_ self) -> JavaClientArgumentType { JavaClientArgumentType::Resource { - identifier: Identifier::vanilla("attribute").expect("valid identifier"), + identifier: Identifier::vanilla_static("attribute"), } } diff --git a/crates/pumpkin/src/command/client_suggestions.rs b/crates/pumpkin/src/command/client_suggestions.rs index 2ef6099dc..8c409fb6f 100644 --- a/crates/pumpkin/src/command/client_suggestions.rs +++ b/crates/pumpkin/src/command/client_suggestions.rs @@ -82,14 +82,13 @@ pub async fn send_c_commands_packet( .values() .copied() .map(|id| resolve_node_id(id, node_id_offset, root_node_index)) - .map(|i| i.try_into().expect("i32 limit reached for ids")) + .map(|i| VarInt(i as i32)) .collect(); let redirect_target = node .redirect() .and_then(|redirection| dispatcher.tree.resolve(redirection)) - .map(|id| resolve_node_id(id, node_id_offset, root_node_index)) - .map(|i| i.try_into().expect("i32 limit reached for ids")); + .map(|id| resolve_node_id(id, node_id_offset, root_node_index) as i32); let satisfies_requirements = true; @@ -117,7 +116,7 @@ pub async fn send_c_commands_packet( !disabled }) .map(|id| resolve_node_id(id, node_id_offset, root_node_index)) - .map(|i| i.try_into().expect("i32 limit reached for ids")) + .map(|i| VarInt(i as i32)) .collect(); } AttachedNode::Literal(literal_attached_node) => { @@ -205,11 +204,7 @@ impl<'a> ProtoNodeBuilder<'a> { let children: Box<[VarInt]> = self .child_nodes .into_iter() - .map(|node| { - node.build(buffer) - .try_into() - .expect("Buffer index exceeded i32 bounds") - }) + .map(|node| VarInt(node.build(buffer) as i32)) .collect(); let i = buffer.len(); @@ -589,15 +584,14 @@ fn collect_overloads_from_attached( } fn ensure_enum_value(enum_values: &mut Vec, value: &str) -> u32 { - enum_values + let index = enum_values .iter() .position(|v| v == value) .unwrap_or_else(|| { enum_values.push(value.to_string()); enum_values.len() - 1 - }) - .try_into() - .unwrap() + }); + index as u32 } fn ensure_command_enum( diff --git a/crates/pumpkin/src/command/commands/damage.rs b/crates/pumpkin/src/command/commands/damage.rs index 635b66617..015563054 100644 --- a/crates/pumpkin/src/command/commands/damage.rs +++ b/crates/pumpkin/src/command/commands/damage.rs @@ -67,12 +67,7 @@ impl CommandExecutor for LocationExecutor { Box::pin(async move { let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; - let Ok(Ok(amount)) = BoundedNumArgumentConsumer::::find_arg(args, ARG_AMOUNT) - else { - return Err(CommandError::CommandFailed(TextComponent::text( - "Invalid damage amount", - ))); - }; + let amount = BoundedNumArgumentConsumer::::find_arg(args, ARG_AMOUNT)??; let damage_type = args.get(ARG_DAMAGE_TYPE) @@ -102,12 +97,7 @@ impl CommandExecutor for EntityExecutor { Box::pin(async move { let target = EntityArgumentConsumer::find_arg(args, ARG_TARGET)?; - let Ok(Ok(amount)) = BoundedNumArgumentConsumer::::find_arg(args, ARG_AMOUNT) - else { - return Err(CommandError::CommandFailed(TextComponent::text( - "Invalid damage amount", - ))); - }; + let amount = BoundedNumArgumentConsumer::::find_arg(args, ARG_AMOUNT)??; let damage_type = args.get(ARG_DAMAGE_TYPE) diff --git a/crates/pumpkin/src/command/commands/effect.rs b/crates/pumpkin/src/command/commands/effect.rs index d94fd0197..632879a23 100644 --- a/crates/pumpkin/src/command/commands/effect.rs +++ b/crates/pumpkin/src/command/commands/effect.rs @@ -88,16 +88,13 @@ impl CommandExecutor for GiveExecutor { let mut successes = 0; for target in targets { - if target.living_entity.has_effect(effect).await - && target - .living_entity - .get_effect(effect) - .await - .expect("Effect should exist because has_effect returned true") - .amplifier - >= amplifier - { - } else { + let should_skip = target + .living_entity + .get_effect(effect) + .await + .is_some_and(|existing| existing.amplifier >= amplifier); + + if !should_skip { target .add_effect(Effect { effect_type: effect, diff --git a/crates/pumpkin/src/command/commands/give.rs b/crates/pumpkin/src/command/commands/give.rs index 4be88ed9c..e0e0a2e05 100644 --- a/crates/pumpkin/src/command/commands/give.rs +++ b/crates/pumpkin/src/command/commands/give.rs @@ -1,5 +1,3 @@ -use pumpkin_data::data_component::DataComponent::MaxStackSize; -use pumpkin_data::data_component_impl::{MaxStackSizeImpl, get}; use pumpkin_util::text::TextComponent; use pumpkin_util::text::hover::HoverEvent; @@ -47,14 +45,7 @@ impl CommandExecutor for Executor { }; for target in targets { - let max_stack = i32::from( - item.components - .iter() - .find_map(|(id, component)| { - (id == &MaxStackSize).then(|| get::(*component).size) - }) - .expect("Item should have MaxStackSize component"), - ); + let max_stack = i32::from(parsed_stack.get_max_stack_size()); let mut remaining = item_count; while remaining > 0 { diff --git a/crates/pumpkin/src/command/commands/mod.rs b/crates/pumpkin/src/command/commands/mod.rs index 1cab8fc78..edfeaaaf4 100644 --- a/crates/pumpkin/src/command/commands/mod.rs +++ b/crates/pumpkin/src/command/commands/mod.rs @@ -333,7 +333,7 @@ fn register_permissions(registry: &mut PermissionRegistry) { "Allows a player to use selector variables", PermissionDefault::Allow, )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); } fn register_level_0_permissions(registry: &mut PermissionRegistry) { @@ -344,21 +344,21 @@ fn register_level_0_permissions(registry: &mut PermissionRegistry) { "Shows information about the Pumpkin server", PermissionDefault::Allow, )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.me", "Broadcasts a narrative message about the player", PermissionDefault::Allow, )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.msg", "Sends a private message to another player", PermissionDefault::Allow, )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); } #[expect(clippy::too_many_lines)] @@ -370,210 +370,210 @@ fn register_level_2_permissions(registry: &mut PermissionRegistry) { "Manages the world border", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.effect", "Adds or removes status effects", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.teleport", "Teleports entities to other locations", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.time", "Changes or queries the world's game time", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.waypoint", "List or modify waypoints", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.give", "Gives an item to a player", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.item", "Replace items in inventories", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.clear", "Clears items from player inventory", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.setblock", "Changes a block to another block", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.fill", "Fills a region with a specific block", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.playsound", "Plays a sound to players", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.tellraw", "Displays a JSON message to players", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.title", "Controls screen titles displayed to players", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.summon", "Summons an entity", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.experience", "Adds, removes or queries player experience", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.weather", "Sets the weather in the server", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.particle", "Creates particles in the world", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.rotate", "Changes the rotation of an entity", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.damage", "Damages entities", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.bossbar", "Creates and manages boss bars", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.say", "Broadcasts a message to multiple players", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.gamemode", "Sets a player's game mode", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.gamerule", "Sets a player's game mode", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.stopsound", "Stops sounds from playing", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.defaultgamemode", "Sets the default game mode for new players", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.data", "Query and modify data of entities and blocks", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.enchant", "Adds an enchantment to a player's selected item, subject to the same restrictions as an anvil. Also works on any mob or entity holding a weapon/tool/armor in its main hand.", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.spawnpoint", "Sets the spawn point for a player", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.spectate", "Allows a player to spectate another entity", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "pumpkin:command.tps", "Displays the server TPS and MSPT", PermissionDefault::Op(PermissionLvl::Two), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); } fn register_level_3_permissions(registry: &mut PermissionRegistry) { @@ -584,77 +584,77 @@ fn register_level_3_permissions(registry: &mut PermissionRegistry) { "Sets the world spawn point", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.deop", "Revokes operator status from a player", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.kick", "Removes players from the server", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "pumpkin:command.plugin", "Manages server plugins", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "pumpkin:command.plugins", "Lists all plugins loaded on the server", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.ban", "Adds players to banlist", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.banip", "Adds IP addresses to banlist", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.pardon", "Removes entries from the player banlist", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.pardonip", "Removes entries from the IP banlist", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.whitelist", "Manages server whitelist", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); registry .register_permission(Permission::new( "minecraft:command.transfer", "Transfers the player to another server", PermissionDefault::Op(PermissionLvl::Three), )) - .expect("Permission already registered"); + .unwrap_or_else(|e| tracing::warn!("{e}")); } #[cfg(test)] diff --git a/crates/pumpkin/src/command/commands/time.rs b/crates/pumpkin/src/command/commands/time.rs index 34da8800d..61ab30a2d 100644 --- a/crates/pumpkin/src/command/commands/time.rs +++ b/crates/pumpkin/src/command/commands/time.rs @@ -1,14 +1,14 @@ use pumpkin_data::translation; use pumpkin_util::text::TextComponent; -use crate::command::CommandResult; use crate::command::args::{ FindArg, bounded_num::BoundedNumArgumentConsumer, resource_location::ResourceLocationArgumentConsumer, time::TimeArgumentConsumer, }; -use crate::command::dispatcher::CommandError; use crate::command::tree::builder::{argument, literal}; -use crate::command::{CommandExecutor, CommandSender, ConsumedArgs, tree::CommandTree}; +use crate::command::{ + CommandError, CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree, +}; const NAMES: [&str; 1] = ["time"]; const DESCRIPTION: &str = "Query or modify the world time and clocks."; @@ -72,9 +72,7 @@ impl CommandExecutor for QueryExecutor { .unwrap_or(DEFAULT_CLOCK); let mode = self.0; let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let level_time = world.level_time.lock().await; match mode { @@ -142,21 +140,15 @@ impl CommandExecutor for ActionExecutor { .unwrap_or(DEFAULT_CLOCK); let action = self.0; let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut level_time = world.level_time.lock().await; match action { Action::Set(preset) => { let time_count = if let Some(p) = preset { p.to_ticks() - } else if let Ok(ticks) = TimeArgumentConsumer::find_arg(args, ARG_TIME) { - ticks } else { - return Err(CommandError::CommandFailed(TextComponent::text( - "Invalid time specified.", - ))); + TimeArgumentConsumer::find_arg(args, ARG_TIME)? }; level_time.set_time(time_count.into()); level_time.send_time(world).await; diff --git a/crates/pumpkin/src/command/commands/waypoint.rs b/crates/pumpkin/src/command/commands/waypoint.rs index a35786fa8..3f85da768 100644 --- a/crates/pumpkin/src/command/commands/waypoint.rs +++ b/crates/pumpkin/src/command/commands/waypoint.rs @@ -9,7 +9,7 @@ use crate::command::args::{ }; use crate::command::tree::builder::{argument, literal}; use crate::command::{ - CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree, + CommandError, CommandExecutor, CommandResult, CommandSender, ConsumedArgs, tree::CommandTree, }; const NAMES: [&str; 1] = ["waypoint"]; @@ -29,9 +29,7 @@ impl CommandExecutor for ListExecutor { ) -> CommandResult<'a> { Box::pin(async move { let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let dimension = world.dimension.minecraft_name.to_string(); sender diff --git a/crates/pumpkin/src/command/commands/worldborder.rs b/crates/pumpkin/src/command/commands/worldborder.rs index a800b7379..ea1cbba8a 100644 --- a/crates/pumpkin/src/command/commands/worldborder.rs +++ b/crates/pumpkin/src/command/commands/worldborder.rs @@ -4,8 +4,8 @@ use pumpkin_util::{math::vector2::Vector2, text::TextComponent}; use crate::command::{ CommandError, CommandExecutor, CommandResult, CommandSender, args::{ - ConsumedArgs, DefaultNameArgConsumer, FindArgDefaultName, - bounded_num::BoundedNumArgumentConsumer, position_2d::Position2DArgumentConsumer, + ConsumedArgs, FindArgDefaultName, bounded_num::BoundedNumArgumentConsumer, + position_2d::Position2DArgumentConsumer, }, tree::{ CommandTree, @@ -51,9 +51,7 @@ impl CommandExecutor for GetExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let border = world.worldborder.lock().await; let diameter = border.new_diameter.round() as i32; @@ -82,17 +80,10 @@ impl CommandExecutor for SetExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let distance = distance_consumer().find_arg_default_name(args)??; if (distance - border.new_diameter).abs() < f64::EPSILON { return Err(CommandError::CommandFailed( @@ -132,23 +123,11 @@ impl CommandExecutor for SetTimeExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(distance) = distance_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; - let Ok(time) = time_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let distance = distance_consumer().find_arg_default_name(args)??; + let time = time_consumer().find_arg_default_name(args)??; let old_dist = format!("{:.1}", border.new_diameter); match distance.total_cmp(&border.new_diameter) { @@ -206,17 +185,10 @@ impl CommandExecutor for AddExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(distance_add) = distance_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let distance_add = distance_consumer().find_arg_default_name(args)??; if distance_add == 0.0 { return Err(CommandError::CommandFailed( @@ -257,33 +229,23 @@ impl CommandExecutor for AddTimeExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(distance_add) = distance_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; - let Ok(time) = time_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let distance_add = distance_consumer().find_arg_default_name(args)??; + let time = time_consumer().find_arg_default_name(args)??; let distance = distance_add + border.new_diameter; let old_dist = format!("{:.1}", border.new_diameter); match distance.total_cmp(&border.new_diameter) { std::cmp::Ordering::Equal => { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); + return Err(CommandError::CommandFailed( + pumpkin_macros::translate_cross!( + translation::java::COMMANDS_WORLDBORDER_SET_FAILED_NOCHANGE, + translation::bedrock::COMMANDS_WORLDBORDER_SET_SUCCESS + ), + )); } std::cmp::Ordering::Less => { let dist = format!("{distance:.1}"); @@ -330,9 +292,7 @@ impl CommandExecutor for CenterExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; let Vector2 { x, y } = Position2DArgumentConsumer.find_arg_default_name(args)?; @@ -363,18 +323,10 @@ impl CommandExecutor for DamageAmountExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(damage_per_block) = damage_per_block_consumer().find_arg_default_name(args)? - else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let damage_per_block = damage_per_block_consumer().find_arg_default_name(args)??; if (damage_per_block - border.damage_per_block).abs() < f32::EPSILON { return Err(CommandError::CommandFailed( @@ -413,17 +365,10 @@ impl CommandExecutor for DamageBufferExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(buffer) = damage_buffer_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let buffer = damage_buffer_consumer().find_arg_default_name(args)??; if (buffer - border.buffer).abs() < f32::EPSILON { return Err(CommandError::CommandFailed( @@ -462,17 +407,10 @@ impl CommandExecutor for WarningDistanceExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(distance) = warning_distance_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let distance = warning_distance_consumer().find_arg_default_name(args)??; if distance == border.warning_blocks { return Err(CommandError::CommandFailed( @@ -509,17 +447,10 @@ impl CommandExecutor for WarningTimeExecutor { Box::pin(async move { // TODO: Maybe ask player for world, or get the current world let worlds = server.worlds.load(); - let world = worlds - .first() - .expect("There should always be at least one world"); + let world = worlds.first().ok_or(CommandError::InvalidRequirement)?; let mut border = world.worldborder.lock().await; - let Ok(time) = time_consumer().find_arg_default_name(args)? else { - return Err(CommandError::CommandFailed(TextComponent::text(format!( - "{} is out of bounds.", - distance_consumer().default_name() - )))); - }; + let time = time_consumer().find_arg_default_name(args)??; if time == border.warning_time { return Err(CommandError::CommandFailed( diff --git a/crates/pumpkin/src/command/dispatcher.rs b/crates/pumpkin/src/command/dispatcher.rs index c69e458a6..b7d292f03 100644 --- a/crates/pumpkin/src/command/dispatcher.rs +++ b/crates/pumpkin/src/command/dispatcher.rs @@ -666,7 +666,10 @@ impl CommandDispatcher { let mut names = tree.names.iter(); let permission = permission.into(); - let primary_name = names.next().expect("at least one name must be provided"); + let Some(primary_name) = names.next() else { + tracing::warn!("Command registration skipped: command tree has no names"); + return; + }; for name in names { self.commands diff --git a/crates/pumpkin/src/command/mod.rs b/crates/pumpkin/src/command/mod.rs index 924caf169..a276d5264 100644 --- a/crates/pumpkin/src/command/mod.rs +++ b/crates/pumpkin/src/command/mod.rs @@ -117,7 +117,7 @@ impl CommandSender { let format = time::macros::format_description!("[hour]:[minute]:[second]"); let timestamp = now .format(&format) - .expect("Failed to format timestamp for command block output"); + .unwrap_or_else(|_| "00:00:00".to_string()); *last_output = format!("[{}] {}", timestamp, text.get_text()); } diff --git a/crates/pumpkin/src/entity/passive/cat.rs b/crates/pumpkin/src/entity/passive/cat.rs index 810849900..980512600 100644 --- a/crates/pumpkin/src/entity/passive/cat.rs +++ b/crates/pumpkin/src/entity/passive/cat.rs @@ -196,7 +196,7 @@ impl CatEntity { let entity = self.get_entity(); entity.send_meta_data( &[Metadata::new( - TrackedData::COLLAR_COLOR, + TrackedData::CAT_COLLAR_COLOR, MetaDataType::INT, VarInt(color as i32), )], @@ -443,7 +443,7 @@ impl Mob for CatEntity { ); entity.send_meta_data( &[Metadata::new( - TrackedData::COLLAR_COLOR, + TrackedData::CAT_COLLAR_COLOR, MetaDataType::INT, VarInt(self.collar_color.load(Ordering::Relaxed) as i32), )], diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 3d55b2a82..518cf0c43 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -4,7 +4,7 @@ pub mod statistics; use core::f32; use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque}; use std::f64::consts::TAU; -use std::num::NonZeroU8; +use std::num::NonZero; use std::str::FromStr; use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering}; use std::sync::{Arc, Weak}; @@ -374,6 +374,8 @@ pub struct ChunkManager { entity_chunk_queue: VecDeque<(Vector2, Weak)>, batches_sent_since_ack: u8, last_chunk_batch_sent_at: Instant, + /// The (view, simulation) chunk loading ticket levels currently held at `center`. + held_tickets: Option<(i8, i8)>, /// The current world for chunk loading. Updated on dimension change. world: Arc, } @@ -398,6 +400,7 @@ impl ChunkManager { entity_chunk_queue: VecDeque::new(), batches_sent_since_ack: 0, last_chunk_batch_sent_at: Instant::now(), + held_tickets: None, world, } } @@ -413,6 +416,25 @@ impl ChunkManager { self.chunk_sent.len() } + pub fn reset_sent_chunks(&mut self) { + self.chunk_sent.clear(); + } + + pub const fn set_view_distance(&mut self, view_distance: u8) { + self.view_distance = view_distance; + } + + pub fn clean(&mut self) { + let (_rx, tx) = crossbeam::channel::unbounded(); + // drop old channel + self.chunk_listener = tx; + + // Drop any held chunk references to allow chunks to be unloaded. + self.chunk_sent.clear(); + self.chunk_queue.clear(); + self.entity_chunk_queue.clear(); + } + fn should_enqueue_chunk(&mut self, position: Vector2, chunk: &SyncChunk) -> bool { self.chunk_sent .insert(position, Arc::downgrade(chunk)) @@ -459,7 +481,6 @@ impl ChunkManager { ) { view_distance += 1; // Margin for loading let old_center = self.center; - let old_view_distance = self.view_distance; { let mut lock = level @@ -475,11 +496,13 @@ impl ChunkManager { let sim_level = ChunkLoading::get_level_from_simulation_distance(sim_dist); lock.add_ticket(center, sim_level); - if (old_center != center || old_view_distance != view_distance) && old_view_distance > 0 - { - let old_level = ChunkLoading::get_level_from_view_distance(old_view_distance); - lock.remove_ticket(old_center, old_level); - lock.remove_ticket(old_center, sim_level); + // Drop exactly the pair we last added rather than recomputing it. The + // simulation level is derived from live config and the view level from the + // previous view distance, so recomputing them can release a ticket we never + // took and strand the one we did. + if let Some((held_view, held_sim)) = self.held_tickets.replace((new_level, sim_level)) { + lock.remove_ticket(old_center, held_view); + lock.remove_ticket(old_center, held_sim); } lock.send_change(); }; @@ -528,11 +551,13 @@ impl ChunkManager { .chunk_loading .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); - lock.remove_ticket( - self.center, - ChunkLoading::get_level_from_view_distance(self.view_distance), - ); + if let Some((view_level, sim_level)) = self.held_tickets.take() { + lock.remove_ticket(self.center, view_level); + lock.remove_ticket(self.center, sim_level); + } lock.send_change(); + level.should_unload.store(true, Ordering::Relaxed); + level.level_channel.notify(); let (_rx, tx) = crossbeam::channel::unbounded(); // drop old channel self.chunk_listener = tx; @@ -543,6 +568,7 @@ impl ChunkManager { self.entity_chunk_queue.clear(); self.batches_sent_since_ack = 0; self.last_chunk_batch_sent_at = Instant::now(); + self.view_distance = 0; } pub fn change_world(&mut self, old_level: &Arc, new_world: Arc) { @@ -550,15 +576,19 @@ impl ChunkManager { .chunk_loading .lock() .unwrap_or_else(std::sync::PoisonError::into_inner); - lock.remove_ticket( - self.center, - ChunkLoading::get_level_from_view_distance(self.view_distance), - ); + if let Some((view_level, sim_level)) = self.held_tickets.take() { + lock.remove_ticket(self.center, view_level); + lock.remove_ticket(self.center, sim_level); + } lock.send_change(); + old_level.should_unload.store(true, Ordering::Relaxed); + old_level.level_channel.notify(); drop(lock); self.chunk_listener = new_world.level.chunk_listener.add_global_chunk_listener(); self.chunk_sent.clear(); self.chunk_queue.clear(); + self.entity_chunk_queue.clear(); + self.view_distance = 0; self.world = new_world; // Reset batch state so chunks can be sent immediately in the new dimension self.batches_sent_since_ack = 0; @@ -953,7 +983,7 @@ impl Player { watched_section: AtomicCell::new(Cylindrical::new( Vector2::new(0, 0), // Since 1 is not possible in vanilla it is used as uninit - NonZeroU8::new(1).unwrap_or(NonZeroU8::MIN), + NonZero::new(1).unwrap_or(NonZero::::MIN), )), last_action_time: AtomicCell::new(std::time::Instant::now()), ping: AtomicU32::new(0), @@ -3165,7 +3195,7 @@ impl Player { self.watched_section.store(Cylindrical::new( Vector2::new(0, 0), - NonZeroU8::new(1).unwrap_or(NonZeroU8::MIN), + NonZero::new(1).unwrap_or(NonZero::::MIN), )); } diff --git a/crates/pumpkin/src/net/bedrock/play/mod.rs b/crates/pumpkin/src/net/bedrock/play/mod.rs index 4ae0e7f02..a527d73f3 100644 --- a/crates/pumpkin/src/net/bedrock/play/mod.rs +++ b/crates/pumpkin/src/net/bedrock/play/mod.rs @@ -1,5 +1,5 @@ use std::{ - num::{NonZero, NonZeroI32}, + num::NonZero, sync::{Arc, atomic::Ordering}, }; diff --git a/crates/pumpkin/src/net/bedrock/play/request_chunk_radius.rs b/crates/pumpkin/src/net/bedrock/play/request_chunk_radius.rs index 2c72374b2..584941c5a 100644 --- a/crates/pumpkin/src/net/bedrock/play/request_chunk_radius.rs +++ b/crates/pumpkin/src/net/bedrock/play/request_chunk_radius.rs @@ -22,7 +22,7 @@ impl BedrockClient { let view_distance = chunk_radius.clamp( 2, - NonZeroI32::from(server.advanced_config.networking.bedrock.view_distance).get(), + NonZero::::from(server.advanced_config.networking.bedrock.view_distance).get(), ); self.enqueue_packet(&CChunkRadiusUpdate { diff --git a/crates/pumpkin/src/net/java/config/client_information.rs b/crates/pumpkin/src/net/java/config/client_information.rs index 9bbe2713d..46875bdd7 100644 --- a/crates/pumpkin/src/net/java/config/client_information.rs +++ b/crates/pumpkin/src/net/java/config/client_information.rs @@ -22,8 +22,8 @@ impl JavaClient { self.config.store(Arc::new(PlayerConfig { locale: client_information.locale.to_string(), // client_information.view_distance was checked above to be > 0 so compiler should optimize this out. - view_distance: NonZeroU8::new(client_information.view_distance as u8) - .unwrap_or(NonZeroU8::MIN), + view_distance: NonZero::new(client_information.view_distance as u8) + .unwrap_or(NonZero::::MIN), chat_mode, chat_colors: client_information.chat_colors, skin_parts: client_information.skin_parts, diff --git a/crates/pumpkin/src/net/java/config/mod.rs b/crates/pumpkin/src/net/java/config/mod.rs index 0fe2f965e..aab4ad000 100644 --- a/crates/pumpkin/src/net/java/config/mod.rs +++ b/crates/pumpkin/src/net/java/config/mod.rs @@ -1,4 +1,4 @@ -use std::{num::NonZeroU8, sync::Arc, sync::atomic::Ordering}; +use std::{num::NonZero, sync::Arc, sync::atomic::Ordering}; use crate::{ entity::player::ChatMode, diff --git a/crates/pumpkin/src/net/java/pending.rs b/crates/pumpkin/src/net/java/pending.rs index ff951fd83..fc82483f7 100644 --- a/crates/pumpkin/src/net/java/pending.rs +++ b/crates/pumpkin/src/net/java/pending.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, num::NonZeroU8, sync::Arc}; +use std::{net::SocketAddr, num::NonZero, sync::Arc}; use bytes::Bytes; use crossbeam::atomic::AtomicCell; @@ -423,8 +423,8 @@ impl PendingConnection { ) { self.config = Some(PlayerConfig { locale: client_information.locale.to_string(), - view_distance: NonZeroU8::new(client_information.view_distance as u8) - .unwrap_or(NonZeroU8::MIN), + view_distance: NonZero::new(client_information.view_distance as u8) + .unwrap_or(NonZero::::MIN), chat_mode, chat_colors: client_information.chat_colors, skin_parts: client_information.skin_parts, diff --git a/crates/pumpkin/src/net/java/play/client_information.rs b/crates/pumpkin/src/net/java/play/client_information.rs index f61079741..adb3e4af2 100644 --- a/crates/pumpkin/src/net/java/play/client_information.rs +++ b/crates/pumpkin/src/net/java/play/client_information.rs @@ -45,7 +45,7 @@ impl JavaClient { // 3. Construct the new config // If view_distance is 0, we exit early (safe guard) - let Some(new_view_distance) = NonZeroU8::new(new_view_distance_raw) else { + let Some(new_view_distance) = NonZero::new(new_view_distance_raw) else { return; }; diff --git a/crates/pumpkin/src/net/java/play/mod.rs b/crates/pumpkin/src/net/java/play/mod.rs index 4a33939e0..614f34c81 100644 --- a/crates/pumpkin/src/net/java/play/mod.rs +++ b/crates/pumpkin/src/net/java/play/mod.rs @@ -3,7 +3,7 @@ use pumpkin_util::{Hand, PermissionLvl}; use rsa::pkcs1v15::{Signature as RsaPkcs1v15Signature, VerifyingKey}; use rsa::signature::Verifier; use sha1::Sha1; -use std::num::NonZeroU8; +use std::num::NonZero; use std::sync::Arc; use std::sync::atomic::{AtomicU32, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; diff --git a/crates/pumpkin/src/net/mod.rs b/crates/pumpkin/src/net/mod.rs index 0f323e86b..f0e227383 100644 --- a/crates/pumpkin/src/net/mod.rs +++ b/crates/pumpkin/src/net/mod.rs @@ -8,7 +8,7 @@ use bytes::Bytes; use pumpkin_world::level::SyncChunk; use std::{ net::SocketAddr, - num::NonZeroU8, + num::NonZero, sync::{Arc, atomic::Ordering}, }; @@ -79,7 +79,7 @@ pub struct PlayerConfig { /// The player's preferred language. pub locale: String, // 16 /// The maximum distance at which chunks are rendered. - pub view_distance: NonZeroU8, + pub view_distance: NonZero, /// The player's chat mode settings pub chat_mode: ChatMode, /// Whether chat colors are enabled. @@ -98,7 +98,7 @@ impl Default for PlayerConfig { fn default() -> Self { Self { locale: "en_us".to_string(), - view_distance: NonZeroU8::new(8).unwrap_or(NonZeroU8::MIN), + view_distance: NonZero::new(8).unwrap_or(NonZero::::MIN), chat_mode: ChatMode::Enabled, chat_colors: true, skin_parts: 0x7F, diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs index 5de22c6cb..eecf033cf 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/boss_bar.rs @@ -133,7 +133,11 @@ impl boss_bar::HostBossBar for PluginHostState { bossbar.color = from_wit_color(color); bossbar.division = from_wit_division(division); - let server = self.server.as_ref().expect("server not available").clone(); + let server = self + .server + .as_ref() + .ok_or_else(|| wasmtime::Error::msg("server not available"))? + .clone(); let plugin_bossbar = Arc::new(Mutex::new(PluginBossBar::new( bossbar, Arc::downgrade(&server), diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs index 2e9010722..cf114456d 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/entity.rs @@ -666,7 +666,10 @@ impl HostEntity for PluginHostState { let entity = entity_from_resource(self, &entity)?; let attribute = from_wit_attribute(attr); if let Some(living) = entity.get_living_entity() { - let map = living.attributes.read().unwrap(); + let map = living + .attributes + .read() + .unwrap_or_else(std::sync::PoisonError::into_inner); if let Some(inst) = map.get(&attribute.id) { return Ok(inst .modifiers @@ -691,7 +694,10 @@ impl HostEntity for PluginHostState { let attribute = from_wit_attribute(attr); if let Some(living) = entity.get_living_entity() { { - let mut map = living.attributes.write().unwrap(); + let mut map = living + .attributes + .write() + .unwrap_or_else(std::sync::PoisonError::into_inner); map.remove(&attribute.id); }; crate::entity::attributes::send_attribute_updates_for_living( diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs index ead7f7d7a..f7924dc7f 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/mod.rs @@ -95,7 +95,7 @@ pub(super) fn to_wasm_block_name(block: &'static Block) -> String { pub(super) fn from_wasm_block_name(block_name: &str) -> &'static Block { Block::from_registry_key(block_name.strip_prefix("minecraft:").unwrap_or(block_name)) - .expect("invalid block name") + .unwrap_or(&Block::AIR) } pub(super) fn to_wasm_entity_type(entity_type: &'static EntityType) -> String { @@ -103,7 +103,12 @@ pub(super) fn to_wasm_entity_type(entity_type: &'static EntityType) -> String { } pub(super) fn from_wasm_entity_type(entity_type: &str) -> &'static EntityType { - EntityType::from_name(entity_type).expect("invalid entity type") + EntityType::from_name( + entity_type + .strip_prefix("minecraft:") + .unwrap_or(entity_type), + ) + .unwrap_or(&EntityType::PLAYER) } pub(super) const fn to_wasm_hand(hand: Hand) -> pumpkin::plugin::common::Hand { @@ -228,10 +233,9 @@ impl EventHandler for WasmPluginEventHandler { let event = event.to_wasm_event(store.data_mut()); match self.plugin.plugin_instance { PluginInstance::V0_1(ref plugin) => { - let server = store - .data_mut() - .add_server(server.clone()) - .expect("valid server"); + let Ok(server) = store.data_mut().add_server(server.clone()) else { + return; + }; let _ = plugin .call_handle_event(&mut *store, self.handler_id, server, &event) .await; @@ -250,10 +254,9 @@ impl EventHandler for WasmPluginEventHandler { let wasm_event = event.to_wasm_event(store.data_mut()); match self.plugin.plugin_instance { PluginInstance::V0_1(ref plugin) => { - let server = store - .data_mut() - .add_server(server.clone()) - .expect("valid server"); + let Ok(server) = store.data_mut().add_server(server.clone()) else { + return; + }; if let Ok(returned_event) = plugin .call_handle_event(&mut *store, self.handler_id, server, &wasm_event) .await diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs index fa111f757..9b5b92ab0 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/events/player.rs @@ -1199,10 +1199,12 @@ impl ToFromWasmEvent for AsyncPlayerPreLoginEvent { Event::AsyncPlayerPreLoginEvent(data) => Self { player_name: data.player_name, player_uuid: data.player_uuid.parse().unwrap_or_default(), - ip_address: data - .ip_address - .parse() - .unwrap_or_else(|_| "127.0.0.1:0".parse().unwrap()), + ip_address: data.ip_address.parse().unwrap_or_else(|_| { + std::net::SocketAddr::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), + 0, + ) + }), kick_message: consume_text_component(state, &data.kick_message), cancelled: data.cancelled, }, @@ -1912,10 +1914,12 @@ impl ToFromWasmEvent for PlayerPreLoginEvent { Event::PlayerPreLoginEvent(data) => Self { player_name: data.player_name, player_uuid: data.player_uuid.parse().unwrap_or_default(), - ip_address: data - .ip_address - .parse() - .unwrap_or_else(|_| "127.0.0.1:0".parse().unwrap()), + ip_address: data.ip_address.parse().unwrap_or_else(|_| { + std::net::SocketAddr::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST), + 0, + ) + }), kick_message: consume_text_component(state, &data.kick_message), cancelled: data.cancelled, }, diff --git a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs index 55d89d91b..68f36608c 100644 --- a/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs +++ b/crates/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/ipc.rs @@ -12,12 +12,15 @@ impl pumpkin::plugin::ipc::Host for PluginHostState { recipient: PluginId, message: IpcMessage, ) -> wasmtime::Result, ()>> { - Ok(self - .server - .as_ref() - .unwrap() + let Some(server) = &self.server else { + return Err(wasmtime::Error::msg("Server not available")); + }; + let Some(name) = &self.name else { + return Err(wasmtime::Error::msg("Plugin name not available")); + }; + Ok(server .plugin_manager - .send_message(self.name.as_ref().unwrap(), &recipient, &message) + .send_message(name, &recipient, &message) .await) } } diff --git a/crates/pumpkin/src/server/mod.rs b/crates/pumpkin/src/server/mod.rs index 1a30bab3e..2d12d8e0a 100644 --- a/crates/pumpkin/src/server/mod.rs +++ b/crates/pumpkin/src/server/mod.rs @@ -1047,9 +1047,7 @@ impl Server { .map_or_else(Vec::new, |player| vec![player]), }; - let Some(player_type) = EntityType::from_name("player") else { - return Vec::new(); - }; + let player_type = &EntityType::PLAYER; let type_included = target_selector .conditions .iter() diff --git a/crates/pumpkin/src/world/chunker.rs b/crates/pumpkin/src/world/chunker.rs index 09b1e1a07..11a336ffb 100644 --- a/crates/pumpkin/src/world/chunker.rs +++ b/crates/pumpkin/src/world/chunker.rs @@ -1,5 +1,5 @@ use pumpkin_util::math::vector2::Vector2; -use std::{num::NonZeroU8, sync::Arc}; +use std::{num::NonZero, sync::Arc}; use pumpkin_protocol::{ bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate, @@ -12,8 +12,8 @@ use crate::{ net::ClientPlatform, }; -pub fn get_view_distance(player: &Player) -> NonZeroU8 { - let fallback = NonZeroU8::new(2).unwrap_or(NonZeroU8::MIN); +pub fn get_view_distance(player: &Player) -> NonZero { + let fallback = NonZero::new(2).unwrap_or(NonZero::::MIN); let Some(server) = player.world().server.upgrade() else { return fallback; }; diff --git a/tools/pumpkin-codegen/Cargo.toml b/tools/pumpkin-codegen/Cargo.toml index a6f36a6cc..521125844 100644 --- a/tools/pumpkin-codegen/Cargo.toml +++ b/tools/pumpkin-codegen/Cargo.toml @@ -16,7 +16,7 @@ serde_repr = { version = "0.1" } serde_json = { version = "1.0", features = ["preserve_order"] } serde_json5 = "0.2" -wit-encoder = "0.255.0" +wit-encoder = "0.256.0" semver = "1.0" rayon = "1.12.0" diff --git a/tools/pumpkin-codegen/src/carver.rs b/tools/pumpkin-codegen/src/carver.rs index abcbc0a99..9cc7ecaad 100644 --- a/tools/pumpkin-codegen/src/carver.rs +++ b/tools/pumpkin-codegen/src/carver.rs @@ -128,7 +128,7 @@ pub fn build() -> TokenStream { pub struct VeryBiasedToBottomHeightProvider { pub min_inclusive: YOffset, pub max_inclusive: YOffset, - pub inner: Option, + pub inner: Option>, } pub struct CaveCarverConfig { diff --git a/tools/pumpkin-codegen/src/chunk_gen_settings.rs b/tools/pumpkin-codegen/src/chunk_gen_settings.rs index b835362a3..0db10dde6 100644 --- a/tools/pumpkin-codegen/src/chunk_gen_settings.rs +++ b/tools/pumpkin-codegen/src/chunk_gen_settings.rs @@ -486,7 +486,7 @@ pub fn build() -> TokenStream { use crate::chunk::DoublePerlinNoiseParameters; use crate::BlockState; - use std::{cell::RefCell, num::NonZeroUsize}; + use std::cell::RefCell; use pumpkin_util::random::RandomDeriver; use pumpkin_util::y_offset::YOffset; use crate::biome::Biome; diff --git a/tools/pumpkin-codegen/src/placed_feature.rs b/tools/pumpkin-codegen/src/placed_feature.rs index 32a69761a..f268b866c 100644 --- a/tools/pumpkin-codegen/src/placed_feature.rs +++ b/tools/pumpkin-codegen/src/placed_feature.rs @@ -506,7 +506,7 @@ pub fn value_to_height_provider(v: &Value) -> TokenStream { quote! { None } } else { let n = v["inner"].as_u64().unwrap_or(1) as u32; - quote! { std::num::NonZeroU32::new(#n) } + quote! { std::num::NonZero::new(#n) } }; quote! { HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider { diff --git a/tools/pumpkin-codegen/src/tracked_data.rs b/tools/pumpkin-codegen/src/tracked_data.rs index 55e1db81d..49e5f5d13 100644 --- a/tools/pumpkin-codegen/src/tracked_data.rs +++ b/tools/pumpkin-codegen/src/tracked_data.rs @@ -44,6 +44,67 @@ pub(crate) fn build() -> TokenStream { parsed.insert("WOLF_VARIANT_ID".to_owned(), id); } + let cat_variant_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(20), + _ => parsed.get("CAT_VARIANT").copied().or(Some(19)), + }; + if let Some(id) = cat_variant_id { + parsed.insert("CAT_VARIANT".to_owned(), id); + parsed.insert("CAT_VARIANT_ID".to_owned(), id); + } + + let cat_collar_color_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(23), + _ => Some(22), + }; + if let Some(id) = cat_collar_color_id { + parsed.insert("CAT_COLLAR_COLOR".to_owned(), id); + parsed.insert("CAT_COLLAR_COLOR_ID".to_owned(), id); + } + + let wolf_collar_color_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(21), + _ => Some(20), + }; + if let Some(id) = wolf_collar_color_id { + parsed.insert("WOLF_COLLAR_COLOR".to_owned(), id); + parsed.insert("WOLF_COLLAR_COLOR_ID".to_owned(), id); + } + + let sound_variant_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(24), + JavaMinecraftVersion::V_1_21_5 + | JavaMinecraftVersion::V_1_21_6 + | JavaMinecraftVersion::V_1_21_7 + | JavaMinecraftVersion::V_1_21_9 + | JavaMinecraftVersion::V_1_21_11 => Some(23), + _ => None, + }; + if let Some(id) = sound_variant_id { + parsed.insert("SOUND_VARIANT".to_owned(), id); + parsed.insert("SOUND_VARIANT_ID".to_owned(), id); + parsed.insert("CAT_SOUND_VARIANT_ID".to_owned(), id); + parsed.insert("WOLF_SOUND_VARIANT_ID".to_owned(), id); + } + + let is_lying_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(21), + _ => Some(20), + }; + if let Some(id) = is_lying_id { + parsed.insert("IS_LYING".to_owned(), id); + parsed.insert("IN_SLEEPING_POSE".to_owned(), id); + } + + let relax_state_one_id = match ver { + JavaMinecraftVersion::V_26_1 | JavaMinecraftVersion::V_26_2 => Some(22), + _ => Some(21), + }; + if let Some(id) = relax_state_one_id { + parsed.insert("RELAX_STATE_ONE".to_owned(), id); + parsed.insert("HEAD_DOWN".to_owned(), id); + } + versions.insert(ver, parsed); } @@ -122,10 +183,15 @@ fn generate_consts(versions: &BTreeMap &["NAME_VISIBLE"], + "IS_LYING" => &["IN_SLEEPING_POSE"], + "IN_SLEEPING_POSE" => &["IS_LYING"], + "RELAX_STATE_ONE" => &["HEAD_DOWN"], + "HEAD_DOWN" => &["RELAX_STATE_ONE"], + "SOUND_VARIANT" => &["SOUND_VARIANT_ID", "DATA_SOUND_VARIANT_ID"], + "SOUND_VARIANT_ID" => &["SOUND_VARIANT", "DATA_SOUND_VARIANT_ID"], + _ => &[], }; let mut fields = TokenStream::new(); @@ -184,6 +250,51 @@ mod tests { assert!(wolf.contains("v26_2 : 23u8")); } + #[test] + fn cat_trackers_keep_their_entity_specific_tracker_ids() { + let generated = build().to_string(); + + assert!(generated.contains("CAT_VARIANT")); + let cat_variant = generated + .split("CAT_VARIANT") + .nth(1) + .expect("cat variant constant"); + assert!(cat_variant.contains("v1_21_11 : 19u8")); + assert!(cat_variant.contains("v26_2 : 20u8")); + + assert!(generated.contains("IS_LYING")); + let is_lying = generated + .split("IS_LYING") + .nth(1) + .expect("is lying constant"); + assert!(is_lying.contains("v1_21_11 : 20u8")); + assert!(is_lying.contains("v26_2 : 21u8")); + + assert!(generated.contains("RELAX_STATE_ONE")); + let relax = generated + .split("RELAX_STATE_ONE") + .nth(1) + .expect("relax state one constant"); + assert!(relax.contains("v1_21_11 : 21u8")); + assert!(relax.contains("v26_2 : 22u8")); + + assert!(generated.contains("CAT_COLLAR_COLOR")); + let collar = generated + .split("CAT_COLLAR_COLOR") + .nth(1) + .expect("cat collar color constant"); + assert!(collar.contains("v1_21_11 : 22u8")); + assert!(collar.contains("v26_2 : 23u8")); + + assert!(generated.contains("SOUND_VARIANT")); + let sound = generated + .split("SOUND_VARIANT") + .nth(1) + .expect("sound variant constant"); + assert!(sound.contains("v1_21_11 : 23u8")); + assert!(sound.contains("v26_2 : 24u8")); + } + #[test] fn checked_in_tracker_table_matches_codegen() { let checked_in = diff --git a/tools/pumpkin-fuzzer/Cargo.toml b/tools/pumpkin-fuzzer/Cargo.toml index 9bc24594e..b4059fe23 100644 --- a/tools/pumpkin-fuzzer/Cargo.toml +++ b/tools/pumpkin-fuzzer/Cargo.toml @@ -9,7 +9,7 @@ license.workspace = true tokio = { workspace = true, features = ["full"] } clap = { version = "4", features = ["derive"] } colored.workspace = true -rand = "0.9" +rand.workspace = true [lints.clippy] expect_used = "allow" diff --git a/tools/pumpkin-fuzzer/src/main.rs b/tools/pumpkin-fuzzer/src/main.rs index 7cd3c9c9b..5406cf7b0 100644 --- a/tools/pumpkin-fuzzer/src/main.rs +++ b/tools/pumpkin-fuzzer/src/main.rs @@ -1,6 +1,6 @@ use clap::{Parser, ValueEnum}; use colored::Colorize; -use rand::Rng; +use rand::RngExt; use std::{ net::SocketAddr, sync::{