1
2
Fork 0
mirror of https://github.com/mat-1/azalea.git synced 2025-08-02 06:16:04 +00:00

update dependencies

This commit is contained in:
mat 2023-03-08 18:57:10 +00:00
commit f93910cf50
67 changed files with 1266 additions and 2457 deletions

297
Cargo.lock generated
View file

@ -79,7 +79,7 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [
"concurrent-queue 2.1.0",
"concurrent-queue",
"event-listener",
"futures-core",
]
@ -105,7 +105,7 @@ checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
dependencies = [
"async-lock",
"async-task",
"concurrent-queue 2.1.0",
"concurrent-queue",
"fastrand",
"futures-lite",
"slab",
@ -113,12 +113,11 @@ dependencies = [
[[package]]
name = "async-lock"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685"
checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7"
dependencies = [
"event-listener",
"futures-lite",
]
[[package]]
@ -140,9 +139,9 @@ checksum = "7a40729d2133846d9ed0ea60a8b9541bccddab49cd30f0715a1da672fe9a2524"
[[package]]
name = "async-trait"
version = "0.1.64"
version = "0.1.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc"
dependencies = [
"proc-macro2",
"quote",
@ -176,11 +175,12 @@ dependencies = [
"azalea-chat",
"azalea-client",
"azalea-core",
"azalea-ecs",
"azalea-physics",
"azalea-protocol",
"azalea-registry",
"azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_tasks",
"derive_more",
"futures",
@ -284,12 +284,13 @@ dependencies = [
"azalea-chat",
"azalea-core",
"azalea-crypto",
"azalea-ecs",
"azalea-inventory",
"azalea-physics",
"azalea-protocol",
"azalea-registry",
"azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_log",
"bevy_tasks",
"bevy_time",
@ -333,26 +334,6 @@ dependencies = [
"uuid",
]
[[package]]
name = "azalea-ecs"
version = "0.6.0"
dependencies = [
"azalea-ecs-macros",
"bevy_app",
"bevy_ecs",
"tokio",
]
[[package]]
name = "azalea-ecs-macros"
version = "0.6.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
"toml 0.7.2",
]
[[package]]
name = "azalea-inventory"
version = "0.1.0"
@ -400,9 +381,11 @@ version = "0.6.0"
dependencies = [
"azalea-block",
"azalea-core",
"azalea-ecs",
"azalea-registry",
"azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_time",
"once_cell",
"parking_lot",
"uuid",
@ -480,9 +463,10 @@ dependencies = [
"azalea-buf",
"azalea-chat",
"azalea-core",
"azalea-ecs",
"azalea-nbt",
"azalea-registry",
"bevy_app",
"bevy_ecs",
"derive_more",
"enum-as-inner",
"log",
@ -516,9 +500,9 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bevy_app"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42"
checksum = "960c6e444dc6a25dd51a2196f04872ae9e2e876802b66c391104849ec9225e38"
dependencies = [
"bevy_derive",
"bevy_ecs",
@ -531,9 +515,9 @@ dependencies = [
[[package]]
name = "bevy_derive"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7baf73c58d41c353c6fd08e6764a2e7420c9f19e8227b391c50981db6d0282a6"
checksum = "cdf11701c01bf4dc7a3fac9f4547f3643d3db4cc1682af40c8c86e2f8734b617"
dependencies = [
"bevy_macro_utils",
"quote",
@ -542,9 +526,9 @@ dependencies = [
[[package]]
name = "bevy_ecs"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c071d7c6bc9801253485e05d0c257284150de755391902746837ba21c0cf74"
checksum = "fdc5b19451128091e8507c9247888359ca0bfa895e7f6ca749ccc55c5463bef6"
dependencies = [
"async-channel",
"bevy_ecs_macros",
@ -555,16 +539,16 @@ dependencies = [
"downcast-rs",
"event-listener",
"fixedbitset",
"fxhash",
"rustc-hash",
"serde",
"thread_local",
]
[[package]]
name = "bevy_ecs_macros"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c15bd45438eeb681ad74f2d205bb07a5699f98f9524462a30ec764afab2742ce"
checksum = "b1e79757319533bde006a4f30c268223ec6426371297182925932075ccfdae30"
dependencies = [
"bevy_macro_utils",
"proc-macro2",
@ -574,9 +558,9 @@ dependencies = [
[[package]]
name = "bevy_log"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c480bac54cf4ae76edc3ae9ae3fa7c5e1b385e7f2111ef5ec3fd00cf3a7998b"
checksum = "47dcb09ec71145c80d88a84181cc1449d30f23c571bdd58c59c10eece82dfaa5"
dependencies = [
"android_log-sys",
"bevy_app",
@ -590,20 +574,20 @@ dependencies = [
[[package]]
name = "bevy_macro_utils"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "022bb69196deeea691b6997414af85bbd7f2b34a8914c4aa7a7ff4dfa44f7677"
checksum = "f24ca3363292f1435641fbafd5c24ce362137dd7d69bee56dcaaa2bc1d512ffe"
dependencies = [
"quote",
"syn",
"toml 0.5.11",
"toml_edit",
]
[[package]]
name = "bevy_math"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434c77ab766c806ed9062ef8a7285b3b02b47df51f188d4496199c3ac062eaf"
checksum = "5e45e46c2ac0a92db3ae622f2ed690928fe2612e7c9470a46d0ed4c2c77e2e95"
dependencies = [
"glam",
"serde",
@ -611,15 +595,15 @@ dependencies = [
[[package]]
name = "bevy_ptr"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec44f7655039546bc5d34d98de877083473f3e9b2b81d560c528d6d74d3eff4"
checksum = "a96c24da064370917b92c2a84527e6a73b620c50ac5ef8b1af8c04ccf5256a7c"
[[package]]
name = "bevy_reflect"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6deae303a7f69dc243b2fa35b5e193cc920229f448942080c8eb2dbd9de6d37a"
checksum = "ab880e0eed9df5c99ce1a2f89edc11cdef1bc78413719b29e9ad7e3bc27f4c20"
dependencies = [
"bevy_math",
"bevy_ptr",
@ -637,9 +621,9 @@ dependencies = [
[[package]]
name = "bevy_reflect_derive"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bf4cb9cd5acb4193f890f36cb63679f1502e2de025e66a63b194b8b133d018"
checksum = "3b361b8671bdffe93978270dd770b03b48560c3127fdf9003f98111fb806bb11"
dependencies = [
"bevy_macro_utils",
"bit-set",
@ -651,14 +635,14 @@ dependencies = [
[[package]]
name = "bevy_tasks"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680b16b53df9c9f24681dd95f4d772d83760bd19adf8bca00f358a3aad997853"
checksum = "3e368e4177fe70d695d5cb67fb7480fa262de79948d9b883a21788b9abf5a85a"
dependencies = [
"async-channel",
"async-executor",
"async-task",
"concurrent-queue 1.2.4",
"concurrent-queue",
"futures-lite",
"once_cell",
"wasm-bindgen-futures",
@ -666,31 +650,46 @@ dependencies = [
[[package]]
name = "bevy_time"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5c38a6d3ea929c7f81e6adf5a6c62cf7e8c40f5106c2174d6057e9d8ea624d"
checksum = "d2f2863cfc08fa38909e047a1bbc2dd71d0836057ed0840c69ace9dff3e0c298"
dependencies = [
"bevy_app",
"bevy_ecs",
"bevy_reflect",
"bevy_utils",
"crossbeam-channel",
"thiserror",
]
[[package]]
name = "bevy_utils"
version = "0.9.1"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16750aae52cd35bd7b60eb61cee883420b250e11b4a290b8d44b2b2941795739"
checksum = "04d90ce493910ad9af3b4220ea6864c7d1472761086a98230ecac59c8d547e95"
dependencies = [
"ahash 0.7.6",
"bevy_utils_proc_macros",
"getrandom",
"hashbrown",
"instant",
"petgraph",
"thiserror",
"tracing",
"uuid",
]
[[package]]
name = "bevy_utils_proc_macros"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62a42e465c446800c57a5bf65b64f4fa1c1f3a74efc2a64a2a001e4a4f548a2e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bit-set"
version = "0.5.3"
@ -729,9 +728,9 @@ checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytemuck"
version = "1.13.0"
version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
[[package]]
name = "byteorder"
@ -745,12 +744,6 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]]
name = "cast"
version = "0.3.0"
@ -809,15 +802,6 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "concurrent-queue"
version = "1.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af4780a44ab5696ea9e28294517f1fffb421a83a25af521333c838635509db9c"
dependencies = [
"cache-padded",
]
[[package]]
name = "concurrent-queue"
version = "2.1.0"
@ -899,9 +883,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
dependencies = [
"cfg-if",
"crossbeam-utils",
@ -909,9 +893,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if",
"crossbeam-epoch",
@ -920,9 +904,9 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg",
"cfg-if",
@ -933,9 +917,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if",
]
@ -952,9 +936,9 @@ dependencies = [
[[package]]
name = "csv"
version = "1.2.0"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af91f40b7355f82b0a891f50e70399475945bb0b0da4f1700ce60761c9d3e359"
checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
dependencies = [
"csv-core",
"itoa",
@ -1048,9 +1032,9 @@ dependencies = [
[[package]]
name = "erased-serde"
version = "0.3.24"
version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4ca605381c017ec7a5fef5e548f1cfaa419ed0f6df6367339300db74c92aa7d"
checksum = "4f2b0c2380453a92ea8b6c8e5f64ecaafccddde8ceab55ff7a8ac1029f894569"
dependencies = [
"serde",
]
@ -1205,15 +1189,6 @@ dependencies = [
"slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.14.6"
@ -1245,9 +1220,9 @@ checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "glam"
version = "0.22.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774"
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
dependencies = [
"bytemuck",
"serde",
@ -1255,9 +1230,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.15"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
dependencies = [
"bytes",
"fnv",
@ -1458,9 +1433,9 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "js-sys"
@ -1540,9 +1515,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
dependencies = [
"autocfg",
]
@ -1571,7 +1546,7 @@ dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
"windows-sys",
]
[[package]]
@ -1743,7 +1718,7 @@ dependencies = [
"redox_syscall",
"smallvec",
"thread-id",
"windows-sys 0.45.0",
"windows-sys",
]
[[package]]
@ -1868,9 +1843,9 @@ dependencies = [
[[package]]
name = "rayon"
version = "1.6.1"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
"either",
"rayon-core",
@ -1878,9 +1853,9 @@ dependencies = [
[[package]]
name = "rayon-core"
version = "1.10.2"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@ -1994,6 +1969,12 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.4.0"
@ -2026,9 +2007,9 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.12"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "same-file"
@ -2063,9 +2044,9 @@ checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
dependencies = [
"serde_derive",
]
@ -2082,9 +2063,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
dependencies = [
"proc-macro2",
"quote",
@ -2093,24 +2074,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.93"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0efd8caf556a6cebd3b285caf480045fcc1ac04f6bd786b09a6f11af30c4fcf4"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -2184,9 +2156,9 @@ dependencies = [
[[package]]
name = "socket2"
version = "0.4.7"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
@ -2229,18 +2201,18 @@ dependencies = [
[[package]]
name = "thiserror"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0"
checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.38"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
@ -2295,9 +2267,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.25.0"
version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@ -2310,7 +2282,7 @@ dependencies = [
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.42.0",
"windows-sys",
]
[[package]]
@ -2349,35 +2321,11 @@ dependencies = [
"tracing",
]
[[package]]
name = "toml"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7afcae9e3f0fe2c370fd4657108972cbb2fa9db1b9f84849cefd80741b01cb6"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
@ -2386,8 +2334,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
@ -2540,9 +2486,9 @@ checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-ident"
version = "1.0.6"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
@ -2757,21 +2703,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows-sys"
version = "0.45.0"
@ -2840,9 +2771,9 @@ checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.3"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]

View file

@ -16,7 +16,6 @@ members = [
"azalea-physics",
"azalea-registry",
"azalea-inventory",
"azalea-ecs",
]
[profile.release]

View file

@ -8,11 +8,11 @@ There's three block types, used for different things. You can (mostly) convert b
```
# use azalea_block::BlockState;
let block_state: BlockState = azalea_block::CobblestoneWallBlock {
east: azalea_block::EastWall::Low,
north: azalea_block::NorthWall::Low,
south: azalea_block::SouthWall::Low,
west: azalea_block::WestWall::Low,
let block_state: BlockState = azalea_block::blocks::CobblestoneWall {
east: azalea_block::properties::EastWall::Low,
north: azalea_block::properties::NorthWall::Low,
south: azalea_block::properties::SouthWall::Low,
west: azalea_block::properties::WestWall::Low,
up: false,
waterlogged: false,
}
@ -36,7 +36,7 @@ let block = Box::<dyn Block>::from(block_state);
```
# use azalea_block::{Block, BlockState};
# let block_state: BlockState = azalea_registry::Block::Jukebox.into();
if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::JukeboxBlock>() {
if let Some(jukebox) = Box::<dyn Block>::from(block_state).downcast_ref::<azalea_block::blocks::Jukebox>() {
// ...
}
```

View file

@ -38,7 +38,7 @@ struct PropertyDefinitions {
properties: Vec<PropertyDefinition>,
}
/// `snowy: false` or `axis: Axis::Y`
/// `snowy: false` or `axis: properties::Axis::Y`
#[derive(Debug)]
struct PropertyWithNameAndDefault {
name: Ident,
@ -59,7 +59,7 @@ struct BlockDefinition {
}
impl Parse for PropertyWithNameAndDefault {
fn parse(input: ParseStream) -> Result<Self> {
// `snowy: false` or `axis: Axis::Y`
// `snowy: false` or `axis: properties::Axis::Y`
let property_name = input.parse()?;
input.parse::<Token![:]>()?;
@ -74,7 +74,7 @@ impl Parse for PropertyWithNameAndDefault {
is_enum = true;
property_type = first_ident;
let variant = input.parse::<Ident>()?;
property_default.extend(quote! { ::#variant });
property_default = quote! { properties::#property_default::#variant };
} else if first_ident_string == "true" || first_ident_string == "false" {
property_type = Ident::new("bool", first_ident.span());
} else {
@ -310,6 +310,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let mut from_state_to_block_match = quote! {};
let mut from_registry_block_to_block_match = quote! {};
let mut from_registry_block_to_blockstate_match = quote! {};
let mut from_registry_block_to_blockstates_match = quote! {};
for block in &input.block_definitions.blocks {
let block_property_names = &block
@ -386,13 +387,16 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
for PropertyWithNameAndDefault {
property_type: struct_name,
name,
is_enum,
..
} in &properties_with_name
{
// let property_name_snake =
// Ident::new(&property.to_string(), proc_macro2::Span::call_site());
block_struct_fields.extend(quote! {
pub #name: #struct_name,
block_struct_fields.extend(if *is_enum {
quote! { pub #name: properties::#struct_name, }
} else {
quote! { pub #name: #struct_name, }
});
}
@ -400,10 +404,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
&to_pascal_case(&block.name.to_string()),
proc_macro2::Span::call_site(),
);
let block_struct_name = Ident::new(
&format!("{block_name_pascal_case}Block"),
proc_macro2::Span::call_site(),
);
let block_struct_name = Ident::new(&block_name_pascal_case.to_string(), proc_macro2::Span::call_site());
let mut from_block_to_state_match_inner = quote! {};
@ -445,7 +446,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
}
let property_type = if property.is_enum {
quote! {#property_struct_name_ident::#variant}
quote! {properties::#property_struct_name_ident::#variant}
} else {
quote! {#variant}
};
@ -476,9 +477,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// 7035..=7058 => {
// let b = b - 7035;
// &AcaciaButtonBlock {
// powered: Powered::from((b / 1) % 2),
// facing: Facing::from((b / 2) % 4),
// face: Face::from((b / 8) % 3),
// powered: properties::Powered::from((b / 1) % 2),
// facing: properties::Facing::from((b / 2) % 4),
// face: properties::Face::from((b / 8) % 3),
// }
// }
let mut from_state_to_block_inner = quote! {};
@ -498,7 +499,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
// this is not a mistake, it starts with true for some reason
quote! {(b / #division) % #property_variants_count == 0}
} else {
quote! {#property_struct_name_ident::from((b / #division) % #property_variants_count)}
quote! {properties::#property_struct_name_ident::from((b / #division) % #property_variants_count)}
}
};
from_state_to_block_inner.extend(quote! {
@ -523,6 +524,9 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
from_registry_block_to_blockstate_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => BlockState { id: #default_state_id },
});
from_registry_block_to_blockstates_match.extend(quote! {
azalea_registry::Block::#block_name_pascal_case => BlockStates::from(#first_state_id..=#last_state_id),
});
let mut block_default_fields = quote! {};
for PropertyWithNameAndDefault {
@ -560,14 +564,14 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
fn id(&self) -> &'static str {
#block_id
}
fn as_blockstate(&self) -> BlockState {
fn as_block_state(&self) -> BlockState {
#from_block_to_state_match
}
}
impl From<#block_struct_name> for BlockState {
fn from(b: #block_struct_name) -> Self {
b.as_blockstate()
b.as_block_state()
}
}
@ -585,21 +589,7 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
let last_state_id = state_id - 1;
let mut generated = quote! {
#property_enums
/// A representation of a state a block can be in. (for example, a stone
/// block only has one state but each possible stair rotation is a
/// different state).
#[derive(Copy, Clone, PartialEq, Eq, Default)]
pub struct BlockState {
/// The protocol ID for the block state. IDs may change every
/// version, so you shouldn't hard-code them or store them in databases.
pub id: u32
}
impl BlockState {
pub const AIR: BlockState = BlockState { id: 0 };
/// Returns the highest possible state ID.
#[inline]
pub fn max_state() -> u32 {
@ -607,38 +597,50 @@ pub fn make_block_states(input: TokenStream) -> TokenStream {
}
}
impl std::fmt::Debug for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "BlockState(id: {}, {:?})", self.id, Box::<dyn Block>::from(*self))
}
pub mod properties {
use super::*;
#property_enums
}
};
generated.extend(quote! {
#block_structs
pub mod blocks {
use super::*;
impl From<BlockState> for Box<dyn Block> {
fn from(block_state: BlockState) -> Self {
let b = block_state.id;
match b {
#from_state_to_block_match
_ => panic!("Invalid block state: {}", b),
#block_structs
impl From<BlockState> for Box<dyn Block> {
fn from(block_state: BlockState) -> Self {
let b = block_state.id;
match b {
#from_state_to_block_match
_ => panic!("Invalid block state: {}", b),
}
}
}
}
impl From<azalea_registry::Block> for Box<dyn Block> {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_block_match
_ => unreachable!("There should always be a block struct for every azalea_registry::Block variant")
impl From<azalea_registry::Block> for Box<dyn Block> {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_block_match
_ => unreachable!("There should always be a block struct for every azalea_registry::Block variant")
}
}
}
}
impl From<azalea_registry::Block> for BlockState {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_blockstate_match
_ => unreachable!("There should always be a block state for every azalea_registry::Block variant")
impl From<azalea_registry::Block> for BlockState {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_blockstate_match
_ => unreachable!("There should always be a block state for every azalea_registry::Block variant")
}
}
}
impl From<azalea_registry::Block> for BlockStates {
fn from(block: azalea_registry::Block) -> Self {
match block {
#from_registry_block_to_blockstates_match
_ => unreachable!("There should always be a block state for every azalea_registry::Block variant")
}
}
}
}

View file

@ -1,20 +1,7 @@
use std::any::Any;
use crate::BlockBehavior;
use crate::{Block, BlockBehavior, BlockState, BlockStates};
use azalea_block_macros::make_block_states;
use std::fmt::Debug;
pub trait Block: Debug + Any {
fn behavior(&self) -> BlockBehavior;
fn id(&self) -> &'static str;
fn as_blockstate(&self) -> BlockState;
}
impl dyn Block {
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref::<T>()
}
}
make_block_states! {
Properties => {
"snowy" => bool,

View file

@ -2,14 +2,49 @@
#![feature(trait_upcasting)]
mod behavior;
mod blocks;
mod generated;
mod range;
pub use generated::{blocks, properties};
use azalea_buf::{BufReadError, McBufReadable, McBufVarReadable, McBufVarWritable, McBufWritable};
pub use behavior::BlockBehavior;
pub use blocks::*;
use std::io::{Cursor, Write};
use core::fmt::Debug;
pub use range::BlockStates;
use std::{
any::Any,
io::{Cursor, Write},
};
pub trait Block: Debug + Any {
fn behavior(&self) -> BlockBehavior;
/// Get the Minecraft ID for this block. For example `stone` or
/// `grass_block`.
fn id(&self) -> &'static str;
/// Convert the block to a block state. This is lossless, as the block
/// contains all the state data.
fn as_block_state(&self) -> BlockState;
}
impl dyn Block {
pub fn downcast_ref<T: Block>(&self) -> Option<&T> {
(self as &dyn Any).downcast_ref::<T>()
}
}
/// A representation of a state a block can be in.
///
/// For example, a stone block only has one state but each possible stair
/// rotation is a different state.
#[derive(Copy, Clone, PartialEq, Eq, Default, Hash)]
pub struct BlockState {
/// The protocol ID for the block state. IDs may change every
/// version, so you shouldn't hard-code them or store them in databases.
pub id: u32,
}
impl BlockState {
pub const AIR: BlockState = BlockState { id: 0 };
/// Transmutes a u32 to a block state.
///
/// # Safety
@ -52,6 +87,17 @@ impl McBufWritable for BlockState {
}
}
impl std::fmt::Debug for BlockState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"BlockState(id: {}, {:?})",
self.id,
Box::<dyn Block>::from(*self)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -80,18 +126,14 @@ mod tests {
"{:?}",
BlockState::from(azalea_registry::Block::FloweringAzalea)
);
assert!(
formatted.ends_with(", FloweringAzaleaBlock)"),
"{}",
formatted
);
assert!(formatted.ends_with(", FloweringAzalea)"), "{}", formatted);
let formatted = format!(
"{:?}",
BlockState::from(azalea_registry::Block::BigDripleafStem)
);
assert!(
formatted.ends_with(", BigDripleafStemBlock { facing: North, waterlogged: false })"),
formatted.ends_with(", BigDripleafStem { facing: North, waterlogged: false })"),
"{}",
formatted
);

33
azalea-block/src/range.rs Normal file
View file

@ -0,0 +1,33 @@
use std::{collections::HashSet, ops::RangeInclusive};
use crate::BlockState;
#[derive(Debug, Clone)]
pub struct BlockStates {
pub set: HashSet<BlockState>,
}
impl From<RangeInclusive<u32>> for BlockStates {
fn from(range: RangeInclusive<u32>) -> Self {
let mut set = HashSet::with_capacity((range.end() - range.start() + 1) as usize);
for id in range {
set.insert(BlockState { id });
}
Self { set }
}
}
impl IntoIterator for BlockStates {
type Item = BlockState;
type IntoIter = std::collections::hash_set::IntoIter<BlockState>;
fn into_iter(self) -> Self::IntoIter {
self.set.into_iter()
}
}
impl BlockStates {
pub fn contains(&self, state: &BlockState) -> bool {
self.set.contains(state)
}
}

View file

@ -16,15 +16,16 @@ azalea-block = { path = "../azalea-block", version = "0.6.0" }
azalea-chat = { path = "../azalea-chat", version = "0.6.0" }
azalea-core = { path = "../azalea-core", version = "0.6.0" }
azalea-crypto = { path = "../azalea-crypto", version = "0.6.0" }
azalea-ecs = { path = "../azalea-ecs", version = "0.6.0" }
azalea-physics = { path = "../azalea-physics", version = "0.6.0" }
azalea-protocol = { path = "../azalea-protocol", version = "0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "0.6.0" }
azalea-world = { path = "../azalea-world", version = "0.6.0" }
bevy_app = "0.10.0"
bevy_ecs = "0.10.0"
bevy_log = "0.10.0"
bevy_tasks = "0.10.0"
bevy_time = "0.10.0"
azalea-inventory = { path = "../azalea-inventory", version = "0.1.0" }
bevy_log = "0.9.1"
bevy_tasks = "0.9.1"
bevy_time = "0.9.1"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
futures = "0.3.25"
log = "0.4.17"

View file

@ -1,18 +1,18 @@
//! Implementations of chat-related features.
use azalea_chat::FormattedText;
use azalea_ecs::{
app::{App, Plugin},
entity::Entity,
event::{EventReader, EventWriter},
schedule::IntoSystemDescriptor,
};
use azalea_protocol::packets::game::{
clientbound_player_chat_packet::ClientboundPlayerChatPacket,
clientbound_system_chat_packet::ClientboundSystemChatPacket,
serverbound_chat_command_packet::ServerboundChatCommandPacket,
serverbound_chat_packet::{LastSeenMessagesUpdate, ServerboundChatPacket},
};
use bevy_app::{App, Plugin};
use bevy_ecs::{
entity::Entity,
event::{EventReader, EventWriter},
schedule::{IntoSystemConfig, IntoSystemConfigs},
};
use std::{
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
@ -159,12 +159,12 @@ impl Plugin for ChatPlugin {
app.add_event::<SendChatEvent>()
.add_event::<SendChatKindEvent>()
.add_event::<ChatReceivedEvent>()
.add_system(handle_send_chat_event.label("handle_send_chat_event"))
.add_system(
handle_send_chat_kind_event
.label("handle_send_chat_kind_event")
.after(handle_send_chat_event)
.after(handle_send_packet_event),
.add_systems(
(
handle_send_chat_event,
handle_send_chat_kind_event.after(handle_send_packet_event),
)
.chain(),
);
}
}

View file

@ -7,25 +7,16 @@ use crate::{
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
LocalPlayer, PhysicsState, SendPacketEvent,
},
movement::{local_player_ai_step, send_position, sprint_listener, walk_listener},
movement::PlayerMovePlugin,
packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
player::retroactively_add_game_profile_component,
task_pool::TaskPoolPlugin,
Account, PlayerInfo, StartSprintEvent, StartWalkEvent,
Account, PlayerInfo,
};
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
use azalea_chat::FormattedText;
use azalea_ecs::{
app::{App, Plugin, PluginGroup, PluginGroupBuilder},
bundle::Bundle,
component::Component,
entity::Entity,
schedule::{IntoSystemDescriptor, ReportExecutionOrderAmbiguities, Schedule, Stage, SystemSet},
AppTickExt,
};
use azalea_ecs::{ecs::Ecs, TickPlugin};
use azalea_physics::PhysicsPlugin;
use azalea_physics::{PhysicsPlugin, PhysicsSet};
use azalea_protocol::{
connect::{Connection, ConnectionError},
packets::{
@ -47,13 +38,23 @@ use azalea_protocol::{
resolver, ServerAddress,
};
use azalea_world::{
entity::{EntityPlugin, Local, WorldName},
PartialWorld, World, WorldContainer,
entity::{EntityPlugin, EntityUpdateSet, Local, WorldName},
Instance, PartialWorld, WorldContainer,
};
use bevy_app::{App, CoreSchedule, Plugin, PluginGroup, PluginGroupBuilder};
use bevy_ecs::{
bundle::Bundle,
component::Component,
entity::Entity,
schedule::IntoSystemConfig,
schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
world::World,
};
use bevy_log::LogPlugin;
use bevy_time::{prelude::FixedTime, TimePlugin};
use log::{debug, error};
use parking_lot::{Mutex, RwLock};
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc};
use std::{collections::HashMap, fmt::Debug, io, net::SocketAddr, sync::Arc, time::Duration};
use thiserror::Error;
use tokio::{sync::mpsc, time};
use uuid::Uuid;
@ -87,7 +88,7 @@ pub struct Client {
/// The entity component system. You probably don't need to access this
/// directly. Note that if you're using a shared world (i.e. a swarm), this
/// will contain all entities in all worlds.
pub ecs: Arc<Mutex<Ecs>>,
pub ecs: Arc<Mutex<World>>,
/// Use this to force the client to run the schedule outside of a tick.
pub run_schedule_sender: mpsc::UnboundedSender<()>,
@ -121,7 +122,7 @@ impl Client {
pub fn new(
profile: GameProfile,
entity: Entity,
ecs: Arc<Mutex<Ecs>>,
ecs: Arc<Mutex<World>>,
run_schedule_sender: mpsc::UnboundedSender<()>,
) -> Self {
Self {
@ -181,7 +182,7 @@ impl Client {
/// Create a [`Client`] when you already have the ECS made with
/// [`start_ecs`]. You'd usually want to use [`Self::join`] instead.
pub async fn start_client(
ecs_lock: Arc<Mutex<Ecs>>,
ecs_lock: Arc<Mutex<World>>,
account: &Account,
address: &ServerAddress,
resolved_address: &SocketAddr,
@ -227,7 +228,7 @@ impl Client {
packet_writer_sender,
// default to an empty world, it'll be set correctly later when we
// get the login packet
Arc::new(RwLock::new(World::default())),
Arc::new(RwLock::new(Instance::default())),
read_packets_task,
write_packets_task,
);
@ -384,13 +385,13 @@ impl Client {
});
}
pub fn local_player<'a>(&'a self, ecs: &'a mut Ecs) -> &'a LocalPlayer {
pub fn local_player<'a>(&'a self, ecs: &'a mut World) -> &'a LocalPlayer {
self.query::<&LocalPlayer>(ecs)
}
pub fn local_player_mut<'a>(
&'a self,
ecs: &'a mut Ecs,
) -> azalea_ecs::ecs::Mut<'a, LocalPlayer> {
ecs: &'a mut World,
) -> bevy_ecs::world::Mut<'a, LocalPlayer> {
self.query::<&mut LocalPlayer>(ecs)
}
@ -418,7 +419,7 @@ impl Client {
/// client, then it'll be the same as the world the client has loaded.
/// If the client using a shared world, then the shared world will be a
/// superset of the client's world.
pub fn world(&self) -> Arc<RwLock<World>> {
pub fn world(&self) -> Arc<RwLock<Instance>> {
let world_name = self.component::<WorldName>();
let ecs = self.ecs.lock();
let world_container = ecs.resource::<WorldContainer>();
@ -496,33 +497,20 @@ pub struct JoinedClientBundle {
pub struct AzaleaPlugin;
impl Plugin for AzaleaPlugin {
fn build(&self, app: &mut App) {
app.add_event::<StartWalkEvent>()
.add_event::<StartSprintEvent>();
// Minecraft ticks happen every 50ms
app.insert_resource(FixedTime::new(Duration::from_millis(50)));
app.add_tick_system_set(
SystemSet::new()
.with_system(send_position.after("ai_step"))
.with_system(update_in_loaded_chunk.before(send_position).after("travel"))
.with_system(
local_player_ai_step
.before(azalea_physics::ai_step)
.label("ai_step"),
),
app.add_system(
update_in_loaded_chunk
.after(PhysicsSet)
.after(handle_send_packet_event),
);
// fire the Death event when the player dies.
app.add_system(death_event);
// walk and sprint event listeners
app.add_system(walk_listener.label("walk_listener"))
.add_system(
sprint_listener
.label("sprint_listener")
.before("walk_listener"),
);
// add GameProfileComponent when we get an AddPlayerEvent
app.add_system(retroactively_add_game_profile_component.after("update_indexes"));
app.add_system(retroactively_add_game_profile_component.after(EntityUpdateSet::Index));
app.add_event::<SendPacketEvent>()
.add_system(handle_send_packet_event);
@ -547,7 +535,12 @@ pub fn init_ecs_app() -> App {
let mut app = App::new();
app.insert_resource(ReportExecutionOrderAmbiguities);
app.edit_schedule(CoreSchedule::Main, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Warn,
..Default::default()
});
});
app.add_plugins(DefaultPlugins);
app
@ -557,17 +550,19 @@ pub fn init_ecs_app() -> App {
/// first.
#[doc(hidden)]
pub fn start_ecs(
app: App,
mut app: App,
run_schedule_receiver: mpsc::UnboundedReceiver<()>,
run_schedule_sender: mpsc::UnboundedSender<()>,
) -> Arc<Mutex<Ecs>> {
) -> Arc<Mutex<World>> {
app.setup();
// all resources should have been added by now so we can take the ecs from the
// app
let ecs = Arc::new(Mutex::new(app.world));
tokio::spawn(run_schedule_loop(
ecs.clone(),
app.schedule,
app.outer_schedule_label,
run_schedule_receiver,
));
tokio::spawn(tick_run_schedule_loop(run_schedule_sender));
@ -576,14 +571,16 @@ pub fn start_ecs(
}
async fn run_schedule_loop(
ecs: Arc<Mutex<Ecs>>,
mut schedule: Schedule,
ecs: Arc<Mutex<World>>,
outer_schedule_label: Box<dyn ScheduleLabel>,
mut run_schedule_receiver: mpsc::UnboundedReceiver<()>,
) {
loop {
// whenever we get an event from run_schedule_receiver, run the schedule
run_schedule_receiver.recv().await;
schedule.run(&mut ecs.lock());
let mut ecs = ecs.lock();
ecs.run_schedule_ref(&*outer_schedule_label);
ecs.clear_trackers();
}
}
@ -612,7 +609,7 @@ impl PluginGroup for DefaultPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(LogPlugin::default())
.add(TickPlugin::default())
.add(TimePlugin::default())
.add(PacketHandlerPlugin)
.add(AzaleaPlugin)
.add(EntityPlugin)
@ -622,5 +619,6 @@ impl PluginGroup for DefaultPlugins {
.add(InventoryPlugin)
.add(ChatPlugin)
.add(DisconnectPlugin)
.add(PlayerMovePlugin)
}
}

View file

@ -1,30 +1,30 @@
//! Disconnect a client from the server.
use azalea_ecs::{
app::{App, CoreStage, Plugin},
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::{
component::Component,
entity::Entity,
event::{EventReader, EventWriter},
query::Changed,
schedule::IntoSystemDescriptor,
schedule::IntoSystemConfigs,
system::{Commands, Query},
AppTickExt,
};
use derive_more::Deref;
use crate::{client::JoinedClientBundle, movement::send_position, LocalPlayer};
use crate::{client::JoinedClientBundle, LocalPlayer};
pub struct DisconnectPlugin;
impl Plugin for DisconnectPlugin {
fn build(&self, app: &mut App) {
app.add_event::<DisconnectEvent>()
.add_system_to_stage(CoreStage::PostUpdate, handle_disconnect)
.add_tick_system(
update_read_packets_task_running_component
.before(disconnect_on_read_packets_ended)
.before(send_position),
app.add_event::<DisconnectEvent>().add_systems(
(
update_read_packets_task_running_component,
disconnect_on_read_packets_ended,
remove_components_from_disconnected_players,
)
.add_tick_system(disconnect_on_read_packets_ended);
.in_base_set(CoreSet::PostUpdate)
.chain(),
);
}
}
@ -35,7 +35,10 @@ pub struct DisconnectEvent {
/// System that removes the [`JoinedClientBundle`] from the entity when it
/// receives a [`DisconnectEvent`].
pub fn handle_disconnect(mut commands: Commands, mut events: EventReader<DisconnectEvent>) {
pub fn remove_components_from_disconnected_players(
mut commands: Commands,
mut events: EventReader<DisconnectEvent>,
) {
for DisconnectEvent { entity } in events.iter() {
commands.entity(*entity).remove::<JoinedClientBundle>();
}

View file

@ -1,10 +1,10 @@
use std::sync::Arc;
use azalea_ecs::{
use bevy_ecs::{
component::Component,
ecs::Ecs,
entity::Entity,
query::{ROQueryItem, ReadOnlyWorldQuery, WorldQuery},
world::World,
};
use parking_lot::Mutex;
@ -22,7 +22,7 @@ impl Client {
/// .is_some();
/// # }
/// ```
pub fn query<'w, Q: WorldQuery>(&self, ecs: &'w mut Ecs) -> <Q as WorldQuery>::Item<'w> {
pub fn query<'w, Q: WorldQuery>(&self, ecs: &'w mut World) -> <Q as WorldQuery>::Item<'w> {
ecs.query::<Q>()
.get_mut(ecs, self.entity)
.expect("Our client is missing a required component.")
@ -38,7 +38,7 @@ impl Client {
/// Note that this will very likely change in the future.
/// ```
/// use azalea_client::{Client, GameProfileComponent};
/// use azalea_ecs::query::With;
/// use bevy_ecs::query::With;
/// use azalea_world::entity::{Position, metadata::Player};
///
/// # fn example(mut bot: Client, sender_name: String) {
@ -74,7 +74,7 @@ impl Client {
}
pub trait EntityPredicate<Q: ReadOnlyWorldQuery, Filter: ReadOnlyWorldQuery> {
fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity>;
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity>;
}
impl<F, Q, Filter> EntityPredicate<(Q,), Filter> for F
where
@ -82,7 +82,7 @@ where
Q: ReadOnlyWorldQuery,
Filter: ReadOnlyWorldQuery,
{
fn find(&self, ecs_lock: Arc<Mutex<Ecs>>) -> Option<Entity> {
fn find(&self, ecs_lock: Arc<Mutex<World>>) -> Option<Entity> {
let mut ecs = ecs_lock.lock();
let mut query = ecs.query_filtered::<(Entity, Q), Filter>();
let entity = query.iter(&ecs).find(|(_, q)| (self)(q)).map(|(e, _)| e);

View file

@ -3,18 +3,12 @@
use std::sync::Arc;
use azalea_ecs::{
app::{App, Plugin},
component::Component,
event::EventReader,
query::Added,
system::Query,
AppTickExt,
};
use azalea_protocol::packets::game::{
clientbound_player_combat_kill_packet::ClientboundPlayerCombatKillPacket, ClientboundGamePacket,
};
use azalea_world::entity::MinecraftEntityId;
use bevy_app::{App, CoreSchedule, IntoSystemAppConfig, Plugin};
use bevy_ecs::{component::Component, event::EventReader, query::Added, system::Query};
use derive_more::{Deref, DerefMut};
use tokio::sync::mpsc;
@ -115,7 +109,7 @@ impl Plugin for EventPlugin {
.add_system(remove_player_listener)
.add_system(death_listener)
.add_system(keepalive_listener)
.add_tick_system(tick_listener);
.add_system(tick_listener.in_schedule(CoreSchedule::FixedUpdate));
}
}

View file

@ -1,14 +1,10 @@
use azalea_core::{BlockPos, Direction};
use azalea_ecs::{
app::{App, Plugin},
entity::Entity,
event::EventReader,
system::Query,
};
use azalea_protocol::packets::game::{
serverbound_interact_packet::InteractionHand,
serverbound_use_item_on_packet::{BlockHitResult, ServerboundUseItemOnPacket},
};
use bevy_app::{App, Plugin};
use bevy_ecs::{entity::Entity, event::EventReader, system::Query};
use log::warn;
use crate::{Client, LocalPlayer};

View file

@ -1,12 +1,7 @@
use azalea_core::Slot;
use azalea_ecs::{
app::{App, Plugin},
component::Component,
entity::Entity,
event::EventReader,
system::Query,
};
use azalea_inventory::Menu;
use bevy_app::{App, Plugin};
use bevy_ecs::{component::Component, entity::Entity, event::EventReader, system::Query};
pub struct InventoryPlugin;
impl Plugin for InventoryPlugin {

View file

@ -28,7 +28,6 @@ mod player;
pub mod task_pool;
pub use account::Account;
pub use azalea_ecs as ecs;
pub use client::{init_ecs_app, start_ecs, Client, ClientInformation, JoinError};
pub use events::Event;
pub use local_player::{GameProfileComponent, LocalPlayer};

View file

@ -2,14 +2,13 @@ use std::{collections::HashMap, io, sync::Arc};
use azalea_auth::game_profile::GameProfile;
use azalea_core::ChunkPos;
use azalea_ecs::component::Component;
use azalea_ecs::entity::Entity;
use azalea_ecs::event::EventReader;
use azalea_ecs::{query::Added, system::Query};
use azalea_protocol::packets::game::ServerboundGamePacket;
use azalea_world::{
entity::{self, Dead},
PartialWorld, World,
Instance, PartialWorld,
};
use bevy_ecs::{
component::Component, entity::Entity, event::EventReader, query::Added, system::Query,
};
use derive_more::{Deref, DerefMut};
use parking_lot::RwLock;
@ -44,7 +43,7 @@ pub struct LocalPlayer {
pub partial_world: Arc<RwLock<PartialWorld>>,
/// The world is the combined [`PartialWorld`]s of all clients in the same
/// world. (Only relevant if you're using a shared world, i.e. a swarm)
pub world: Arc<RwLock<World>>,
pub world: Arc<RwLock<Instance>>,
/// A task that reads packets from the server. The client is disconnected
/// when this task ends.
@ -88,7 +87,7 @@ impl LocalPlayer {
pub fn new(
entity: Entity,
packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
world: Arc<RwLock<World>>,
world: Arc<RwLock<Instance>>,
read_packets_task: JoinHandle<()>,
write_packets_task: JoinHandle<()>,
) -> Self {
@ -129,7 +128,7 @@ impl Drop for LocalPlayer {
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
pub fn update_in_loaded_chunk(
mut commands: azalea_ecs::system::Commands,
mut commands: bevy_ecs::system::Commands,
query: Query<(Entity, &LocalPlayer, &entity::Position)>,
) {
for (entity, local_player, position) in &query {

View file

@ -1,7 +1,8 @@
use crate::client::Client;
use crate::local_player::{LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState};
use azalea_ecs::entity::Entity;
use azalea_ecs::{event::EventReader, query::With, system::Query};
use crate::local_player::{
update_in_loaded_chunk, LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState,
};
use azalea_physics::{force_jump_listener, PhysicsSet};
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
use azalea_protocol::packets::game::{
serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
@ -13,6 +14,14 @@ use azalea_world::{
entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId},
MoveEntityError,
};
use bevy_app::{App, CoreSchedule, IntoSystemAppConfigs, Plugin};
use bevy_ecs::{
entity::Entity,
event::EventReader,
query::With,
schedule::{IntoSystemConfig, IntoSystemConfigs},
system::Query,
};
use std::backtrace::Backtrace;
use thiserror::Error;
@ -34,6 +43,28 @@ impl From<MoveEntityError> for MovePlayerError {
}
}
pub struct PlayerMovePlugin;
impl Plugin for PlayerMovePlugin {
fn build(&self, app: &mut App) {
app.add_event::<StartWalkEvent>()
.add_event::<StartSprintEvent>()
.add_systems(
(sprint_listener, walk_listener)
.chain()
.before(force_jump_listener),
)
.add_systems(
(
local_player_ai_step.in_set(PhysicsSet),
send_position.after(update_in_loaded_chunk),
)
.chain()
.in_schedule(CoreSchedule::FixedUpdate),
);
}
}
impl Client {
/// Set whether we're jumping. This acts as if you held space in
/// vanilla. If you want to jump once, use the `jump` function.

View file

@ -1,15 +1,6 @@
use std::{collections::HashSet, io::Cursor, sync::Arc};
use azalea_core::{ChunkPos, ResourceLocation, Vec3};
use azalea_ecs::{
app::{App, CoreStage, Plugin},
component::Component,
ecs::Ecs,
entity::Entity,
event::{EventReader, EventWriter, Events},
schedule::{StageLabel, SystemStage},
system::{Commands, Query, ResMut, SystemState},
};
use azalea_protocol::{
connect::{ReadConnection, WriteConnection},
packets::game::{
@ -25,12 +16,21 @@ use azalea_protocol::{
use azalea_world::{
entity::{
metadata::{apply_metadata, Health, PlayerMetadataBundle},
set_rotation, Dead, EntityBundle, EntityKind, LastSentPosition, MinecraftEntityId, Physics,
PlayerBundle, Position, WorldName,
set_rotation, Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition,
MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
},
entity::{LoadedBy, RelativeEntityUpdate},
PartialWorld, WorldContainer,
};
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::{
component::Component,
entity::Entity,
event::{EventReader, EventWriter, Events},
schedule::IntoSystemConfig,
system::{Commands, Query, ResMut, SystemState},
world::World,
};
use log::{debug, error, trace, warn};
use parking_lot::Mutex;
use tokio::sync::mpsc;
@ -47,7 +47,7 @@ use crate::{
/// ```
/// # use azalea_client::packet_handling::PacketEvent;
/// # use azalea_protocol::packets::game::ClientboundGamePacket;
/// # use azalea_ecs::event::EventReader;
/// # use bevy_ecs::event::EventReader;
///
/// fn handle_packets(mut events: EventReader<PacketEvent>) {
/// for PacketEvent {
@ -73,25 +73,22 @@ pub struct PacketEvent {
pub struct PacketHandlerPlugin;
#[derive(StageLabel)]
pub struct SendPacketEventsStage;
impl Plugin for PacketHandlerPlugin {
fn build(&self, app: &mut App) {
app.add_stage_before(
CoreStage::PreUpdate,
SendPacketEventsStage,
SystemStage::parallel(),
)
.add_system_to_stage(SendPacketEventsStage, send_packet_events)
.add_system_to_stage(CoreStage::PreUpdate, process_packet_events)
.init_resource::<Events<PacketEvent>>()
.add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>()
.add_event::<ChatReceivedEvent>()
.add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>();
app.add_system(send_packet_events.in_base_set(CoreSet::First))
.add_system(
process_packet_events
.in_base_set(CoreSet::PreUpdate)
// we want to index and deindex right after
.before(EntityUpdateSet::Deindex),
)
.init_resource::<Events<PacketEvent>>()
.add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>()
.add_event::<ChatReceivedEvent>()
.add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>();
}
}
@ -169,7 +166,7 @@ pub fn send_packet_events(
}
}
fn process_packet_events(ecs: &mut Ecs) {
fn process_packet_events(ecs: &mut World) {
let mut events_owned = Vec::new();
let mut system_state: SystemState<EventReader<PacketEvent>> = SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);
@ -713,8 +710,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity {
let new_position = p.position;
commands.add(RelativeEntityUpdate {
entity,
commands.entity(entity).add(RelativeEntityUpdate {
partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity| {
let mut position = entity.get_mut::<Position>().unwrap();
@ -745,8 +741,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity {
let delta = p.delta.clone();
commands.add(RelativeEntityUpdate {
entity,
commands.entity(entity).add(RelativeEntityUpdate {
partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity_mut| {
let mut position = entity_mut.get_mut::<Position>().unwrap();
@ -774,8 +769,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity {
let delta = p.delta.clone();
commands.add(RelativeEntityUpdate {
entity,
commands.entity(entity).add(RelativeEntityUpdate {
partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity_mut| {
let mut position = entity_mut.get_mut::<Position>().unwrap();

View file

@ -1,11 +1,11 @@
use azalea_auth::game_profile::GameProfile;
use azalea_chat::FormattedText;
use azalea_core::GameType;
use azalea_ecs::{
use azalea_world::entity::EntityInfos;
use bevy_ecs::{
event::EventReader,
system::{Commands, Res},
};
use azalea_world::entity::EntityInfos;
use uuid::Uuid;
use crate::{packet_handling::AddPlayerEvent, GameProfileComponent};

View file

@ -1,11 +1,16 @@
//! Borrowed from `bevy_core`.
use azalea_ecs::{
app::{App, Plugin},
schedule::IntoSystemDescriptor,
system::Resource,
use std::marker::PhantomData;
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::{
schedule::IntoSystemConfig,
system::{NonSend, Resource},
};
use bevy_tasks::{
tick_global_task_pools_on_main_thread, AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool,
TaskPoolBuilder,
};
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
/// Setup of default task pools: `AsyncComputeTaskPool`, `ComputeTaskPool`,
/// `IoTaskPool`.
@ -22,13 +27,16 @@ impl Plugin for TaskPoolPlugin {
self.task_pool_options.create_default_pools();
#[cfg(not(target_arch = "wasm32"))]
app.add_system_to_stage(
azalea_ecs::app::CoreStage::Last,
bevy_tasks::tick_global_task_pools_on_main_thread.at_end(),
);
app.add_system(tick_global_task_pools.in_base_set(CoreSet::Last));
}
}
pub struct NonSendMarker(PhantomData<*mut ()>);
#[cfg(not(target_arch = "wasm32"))]
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
tick_global_task_pools_on_main_thread();
}
/// Helper for configuring and creating the default task pools. For end-users
/// who want full control, set up [`TaskPoolPlugin`](TaskPoolPlugin)
#[derive(Clone, Resource)]

View file

@ -13,7 +13,7 @@ azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
bevy_ecs = { version = "0.9.1", default-features = false, optional = true }
bevy_ecs = { version = "0.10.0", default-features = false, optional = true }
uuid = "^1.1.2"
[features]

View file

@ -12,6 +12,8 @@ macro_rules! vec3_impl {
Self { x, y, z }
}
/// Get the distance of this vector to the origin by doing `x^2 + y^2 +
/// z^2`.
pub fn length_sqr(&self) -> $type {
self.x * self.x + self.y * self.y + self.z * self.z
}
@ -139,6 +141,11 @@ impl BlockPos {
z: self.z as f64 + 0.5,
}
}
/// Get the distance of this vector from the origin by doing `x + y + z`.
pub fn length_manhattan(&self) -> u32 {
(self.x.abs() + self.y.abs() + self.z.abs()) as u32
}
}
/// Chunk coordinates are used to represent where a chunk is in the world. You
@ -148,12 +155,21 @@ pub struct ChunkPos {
pub x: i32,
pub z: i32,
}
impl ChunkPos {
pub fn new(x: i32, z: i32) -> Self {
ChunkPos { x, z }
}
}
impl Add<ChunkPos> for ChunkPos {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
z: self.z + rhs.z,
}
}
}
/// The coordinates of a chunk section in the world.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]

View file

@ -1,14 +0,0 @@
[package]
description = "ECS stuff used in Azalea"
edition = "2021"
license = "MIT"
name = "azalea-ecs"
version = "0.6.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
azalea-ecs-macros = {path = "./azalea-ecs-macros", version = "^0.6.0"}
bevy_app = "0.9.1"
bevy_ecs = {version = "0.9.1", default-features = false}
tokio = {version = "1.25.0", features = ["time"]}

View file

@ -1,15 +0,0 @@
[package]
description = "Azalea ECS Macros"
edition = "2021"
license = "MIT OR Apache-2.0"
name = "azalea-ecs-macros"
version = "0.6.0"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0"
quote = "1.0"
syn = "1.0"
toml = "0.7.0"

View file

@ -1,125 +0,0 @@
use crate::utils::{get_lit_str, Symbol};
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result};
use crate::utils;
pub fn derive_resource(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let azalea_ecs_path: Path = crate::azalea_ecs_path();
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #azalea_ecs_path::system::_BevyResource for #struct_name #type_generics #where_clause {
}
})
}
pub fn derive_component(input: TokenStream) -> TokenStream {
let mut ast = parse_macro_input!(input as DeriveInput);
let azalea_ecs_path: Path = crate::azalea_ecs_path();
let attrs = match parse_component_attr(&ast) {
Ok(attrs) => attrs,
Err(e) => return e.into_compile_error().into(),
};
let storage = storage_path(&azalea_ecs_path, attrs.storage);
ast.generics
.make_where_clause()
.predicates
.push(parse_quote! { Self: Send + Sync + 'static });
let struct_name = &ast.ident;
let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl();
TokenStream::from(quote! {
impl #impl_generics #azalea_ecs_path::component::_BevyComponent for #struct_name #type_generics #where_clause {
type Storage = #storage;
}
})
}
pub const COMPONENT: Symbol = Symbol("component");
pub const STORAGE: Symbol = Symbol("storage");
struct Attrs {
storage: StorageTy,
}
#[derive(Clone, Copy)]
enum StorageTy {
Table,
SparseSet,
}
// values for `storage` attribute
const TABLE: &str = "Table";
const SPARSE_SET: &str = "SparseSet";
fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
let meta_items = utils::parse_attrs(ast, COMPONENT)?;
let mut attrs = Attrs {
storage: StorageTy::Table,
};
for meta in meta_items {
use syn::{
Meta::NameValue,
NestedMeta::{Lit, Meta},
};
match meta {
Meta(NameValue(m)) if m.path == STORAGE => {
attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() {
TABLE => StorageTy::Table,
SPARSE_SET => StorageTy::SparseSet,
s => {
return Err(Error::new_spanned(
m.lit,
format!(
"Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'."
),
))
}
};
}
Meta(meta_item) => {
return Err(Error::new_spanned(
meta_item.path(),
format!(
"unknown component attribute `{}`",
meta_item.path().into_token_stream()
),
));
}
Lit(lit) => {
return Err(Error::new_spanned(
lit,
"unexpected literal in component attribute",
))
}
}
}
Ok(attrs)
}
fn storage_path(azalea_ecs_path: &Path, ty: StorageTy) -> TokenStream2 {
let typename = match ty {
StorageTy::Table => Ident::new("TableStorage", Span::call_site()),
StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()),
};
quote! { #azalea_ecs_path::component::#typename }
}

View file

@ -1,466 +0,0 @@
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
Attribute, Data, DataStruct, DeriveInput, Field, Fields,
};
use crate::azalea_ecs_path;
#[derive(Default)]
struct FetchStructAttributes {
pub is_mutable: bool,
pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>,
}
static MUTABLE_ATTRIBUTE_NAME: &str = "mutable";
static DERIVE_ATTRIBUTE_NAME: &str = "derive";
mod field_attr_keywords {
syn::custom_keyword!(ignore);
}
pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query";
pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
let visibility = ast.vis;
let mut fetch_struct_attributes = FetchStructAttributes::default();
for attr in &ast.attrs {
if !attr
.path
.get_ident()
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
{
continue;
}
attr.parse_args_with(|input: ParseStream| {
let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?;
for meta in meta {
let ident = meta.path().get_ident().unwrap_or_else(|| {
panic!(
"Unrecognized attribute: `{}`",
meta.path().to_token_stream()
)
});
if ident == MUTABLE_ATTRIBUTE_NAME {
if let syn::Meta::Path(_) = meta {
fetch_struct_attributes.is_mutable = true;
} else {
panic!(
"The `{MUTABLE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments"
);
}
} else if ident == DERIVE_ATTRIBUTE_NAME {
if let syn::Meta::List(meta_list) = meta {
fetch_struct_attributes
.derive_args
.extend(meta_list.nested.iter().cloned());
} else {
panic!(
"Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute"
);
}
} else {
panic!(
"Unrecognized attribute: `{}`",
meta.path().to_token_stream()
);
}
}
Ok(())
})
.unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format"));
}
let path = azalea_ecs_path();
let user_generics = ast.generics.clone();
let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl();
let user_generics_with_world = {
let mut generics = ast.generics.clone();
generics.params.insert(0, parse_quote!('__w));
generics
};
let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) =
user_generics_with_world.split_for_impl();
let struct_name = ast.ident.clone();
let read_only_struct_name = if fetch_struct_attributes.is_mutable {
Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site())
} else {
struct_name.clone()
};
let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site());
let read_only_item_struct_name = if fetch_struct_attributes.is_mutable {
Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site())
} else {
item_struct_name.clone()
};
let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site());
let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable {
Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site())
} else {
fetch_struct_name.clone()
};
let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site());
let fields = match &ast.data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => &fields.named,
_ => panic!("Expected a struct with named fields"),
};
let mut ignored_field_attrs = Vec::new();
let mut ignored_field_visibilities = Vec::new();
let mut ignored_field_idents = Vec::new();
let mut ignored_field_types = Vec::new();
let mut field_attrs = Vec::new();
let mut field_visibilities = Vec::new();
let mut field_idents = Vec::new();
let mut field_types = Vec::new();
let mut read_only_field_types = Vec::new();
for field in fields {
let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field);
let field_ident = field.ident.as_ref().unwrap().clone();
if is_ignored {
ignored_field_attrs.push(attrs);
ignored_field_visibilities.push(field.vis.clone());
ignored_field_idents.push(field_ident.clone());
ignored_field_types.push(field.ty.clone());
} else {
field_attrs.push(attrs);
field_visibilities.push(field.vis.clone());
field_idents.push(field_ident.clone());
let field_ty = field.ty.clone();
field_types.push(quote!(#field_ty));
read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly));
}
}
let derive_args = &fetch_struct_attributes.derive_args;
// `#[derive()]` is valid syntax
let derive_macro_call = quote! { #[derive(#derive_args)] };
let impl_fetch = |is_readonly: bool| {
let struct_name = if is_readonly {
&read_only_struct_name
} else {
&struct_name
};
let item_struct_name = if is_readonly {
&read_only_item_struct_name
} else {
&item_struct_name
};
let fetch_struct_name = if is_readonly {
&read_only_fetch_struct_name
} else {
&fetch_struct_name
};
let field_types = if is_readonly {
&read_only_field_types
} else {
&field_types
};
quote! {
#derive_macro_call
#[doc = "Automatically generated [`WorldQuery`] item type for [`"]
#[doc = stringify!(#struct_name)]
#[doc = "`], returned when iterating over query results."]
#[automatically_derived]
#visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
#(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)*
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
}
#[doc(hidden)]
#[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"]
#[doc = stringify!(#struct_name)]
#[doc = "`], used to define the world data accessed by this query."]
#[automatically_derived]
#visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world {
#(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)*
#(#ignored_field_idents: #ignored_field_types,)*
}
// SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field
unsafe impl #user_impl_generics #path::query::WorldQuery
for #struct_name #user_ty_generics #user_where_clauses {
type Item<'__w> = #item_struct_name #user_ty_generics_with_world;
type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world;
type ReadOnly = #read_only_struct_name #user_ty_generics;
type State = #state_struct_name #user_ty_generics;
fn shrink<'__wlong: '__wshort, '__wshort>(
item: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wlong>
) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wshort> {
#item_struct_name {
#(
#field_idents: <#field_types>::shrink(item.#field_idents),
)*
#(
#ignored_field_idents: item.#ignored_field_idents,
)*
}
}
unsafe fn init_fetch<'__w>(
_world: &'__w #path::world::World,
state: &Self::State,
_last_change_tick: u32,
_change_tick: u32
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
#fetch_struct_name {
#(#field_idents:
<#field_types>::init_fetch(
_world,
&state.#field_idents,
_last_change_tick,
_change_tick
),
)*
#(#ignored_field_idents: Default::default(),)*
}
}
unsafe fn clone_fetch<'__w>(
_fetch: &<Self as #path::query::WorldQuery>::Fetch<'__w>
) -> <Self as #path::query::WorldQuery>::Fetch<'__w> {
#fetch_struct_name {
#(
#field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents),
)*
#(
#ignored_field_idents: Default::default(),
)*
}
}
const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*;
const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*;
/// SAFETY: we call `set_archetype` for each member that implements `Fetch`
#[inline]
unsafe fn set_archetype<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_state: &Self::State,
_archetype: &'__w #path::archetype::Archetype,
_table: &'__w #path::storage::Table
) {
#(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)*
}
/// SAFETY: we call `set_table` for each member that implements `Fetch`
#[inline]
unsafe fn set_table<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_state: &Self::State,
_table: &'__w #path::storage::Table
) {
#(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)*
}
/// SAFETY: we call `fetch` for each member that implements `Fetch`.
#[inline(always)]
unsafe fn fetch<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: Entity,
_table_row: usize
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
Self::Item {
#(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)*
#(#ignored_field_idents: Default::default(),)*
}
}
#[allow(unused_variables)]
#[inline(always)]
unsafe fn filter_fetch<'__w>(
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
_entity: Entity,
_table_row: usize
) -> bool {
true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))*
}
fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) {
#( <#field_types>::update_component_access(&state.#field_idents, _access); )*
}
fn update_archetype_component_access(
state: &Self::State,
_archetype: &#path::archetype::Archetype,
_access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId>
) {
#(
<#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access);
)*
}
fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics {
#state_struct_name {
#(#field_idents: <#field_types>::init_state(world),)*
#(#ignored_field_idents: Default::default(),)*
}
}
fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool {
true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))*
}
}
}
};
let mutable_impl = impl_fetch(false);
let readonly_impl = if fetch_struct_attributes.is_mutable {
let world_query_impl = impl_fetch(true);
quote! {
#[doc(hidden)]
#[doc = "Automatically generated internal [`WorldQuery`] type for [`"]
#[doc = stringify!(#struct_name)]
#[doc = "`], used for read-only access."]
#[automatically_derived]
#visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses {
#( #field_idents: #read_only_field_types, )*
#(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)*
}
#world_query_impl
}
} else {
quote! {}
};
let read_only_asserts = if fetch_struct_attributes.is_mutable {
quote! {
// Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only.
// This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery`
// but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway
#( assert_readonly::<#read_only_field_types>(); )*
}
} else {
quote! {
// Statically checks that the safety guarantee of `ReadOnlyWorldQuery` for `$fetch_struct_name` actually holds true.
// We need this to make sure that we don't compile `ReadOnlyWorldQuery` if our struct contains nested `WorldQuery`
// members that don't implement it. I.e.:
// ```
// #[derive(WorldQuery)]
// pub struct Foo { a: &'static mut MyComponent }
// ```
#( assert_readonly::<#field_types>(); )*
}
};
TokenStream::from(quote! {
#mutable_impl
#readonly_impl
#[doc(hidden)]
#[doc = "Automatically generated internal [`WorldQuery`] state type for [`"]
#[doc = stringify!(#struct_name)]
#[doc = "`], used for caching."]
#[automatically_derived]
#visibility struct #state_struct_name #user_impl_generics #user_where_clauses {
#(#field_idents: <#field_types as #path::query::WorldQuery>::State,)*
#(#ignored_field_idents: #ignored_field_types,)*
}
/// SAFETY: we assert fields are readonly below
unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery
for #read_only_struct_name #user_ty_generics #user_where_clauses {}
#[allow(dead_code)]
const _: () = {
fn assert_readonly<T>()
where
T: #path::query::ReadOnlyWorldQuery,
{
}
// We generate a readonly assertion for every struct member.
fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world {
#read_only_asserts
}
};
// The original struct will most likely be left unused. As we don't want our users having
// to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed
// workaround.
#[allow(dead_code)]
const _: () = {
fn dead_code_workaround #user_impl_generics (
q: #struct_name #user_ty_generics,
q2: #read_only_struct_name #user_ty_generics
) #user_where_clauses {
#(q.#field_idents;)*
#(q.#ignored_field_idents;)*
#(q2.#field_idents;)*
#(q2.#ignored_field_idents;)*
}
};
})
}
struct WorldQueryFieldInfo {
/// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute.
is_ignored: bool,
/// All field attributes except for `world_query` ones.
attrs: Vec<Attribute>,
}
fn read_world_query_field_info(field: &Field) -> WorldQueryFieldInfo {
let is_ignored = field
.attrs
.iter()
.find(|attr| {
attr.path
.get_ident()
.map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME)
})
.map_or(false, |attr| {
let mut is_ignored = false;
attr.parse_args_with(|input: ParseStream| {
if input
.parse::<Option<field_attr_keywords::ignore>>()?
.is_some()
{
is_ignored = true;
}
Ok(())
})
.unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format"));
is_ignored
});
let attrs = field
.attrs
.iter()
.filter(|attr| {
attr.path
.get_ident()
.map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME)
})
.cloned()
.collect();
WorldQueryFieldInfo { is_ignored, attrs }
}

View file

@ -1,525 +0,0 @@
//! A fork of bevy_ecs_macros that uses azalea_ecs instead of bevy_ecs.
extern crate proc_macro;
mod component;
mod fetch;
pub(crate) mod utils;
use crate::fetch::derive_world_query_impl;
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{format_ident, quote};
use syn::{
parse::{Parse, ParseStream},
parse_macro_input,
punctuated::Punctuated,
spanned::Spanned,
token::Comma,
DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result,
Token, TypeParam,
};
use utils::{derive_label, get_named_struct_fields, BevyManifest};
struct AllTuples {
macro_ident: Ident,
start: usize,
end: usize,
idents: Vec<Ident>,
}
impl Parse for AllTuples {
fn parse(input: ParseStream) -> Result<Self> {
let macro_ident = input.parse::<Ident>()?;
input.parse::<Comma>()?;
let start = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let end = input.parse::<LitInt>()?.base10_parse()?;
input.parse::<Comma>()?;
let mut idents = vec![input.parse::<Ident>()?];
while input.parse::<Comma>().is_ok() {
idents.push(input.parse::<Ident>()?);
}
Ok(AllTuples {
macro_ident,
start,
end,
idents,
})
}
}
#[proc_macro]
pub fn all_tuples(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as AllTuples);
let len = input.end - input.start;
let mut ident_tuples = Vec::with_capacity(len);
for i in input.start..=input.end {
let idents = input
.idents
.iter()
.map(|ident| format_ident!("{}{}", ident, i));
if input.idents.len() < 2 {
ident_tuples.push(quote! {
#(#idents)*
});
} else {
ident_tuples.push(quote! {
(#(#idents),*)
});
}
}
let macro_ident = &input.macro_ident;
let invocations = (input.start..=input.end).map(|i| {
let ident_tuples = &ident_tuples[..i];
quote! {
#macro_ident!(#(#ident_tuples),*);
}
});
TokenStream::from(quote! {
#(
#invocations
)*
})
}
enum BundleFieldKind {
Component,
Ignore,
}
const BUNDLE_ATTRIBUTE_NAME: &str = "bundle";
const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore";
#[proc_macro_derive(Bundle, attributes(bundle))]
pub fn derive_bundle(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let ecs_path = azalea_ecs_path();
let named_fields = match get_named_struct_fields(&ast.data) {
Ok(fields) => &fields.named,
Err(e) => return e.into_compile_error().into(),
};
let mut field_kind = Vec::with_capacity(named_fields.len());
'field_loop: for field in named_fields.iter() {
for attr in &field.attrs {
if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) {
if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() {
if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() {
if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) {
field_kind.push(BundleFieldKind::Ignore);
continue 'field_loop;
}
return syn::Error::new(
path.span(),
format!(
"Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`"
),
)
.into_compile_error()
.into();
}
return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into();
}
}
}
field_kind.push(BundleFieldKind::Component);
}
let field = named_fields
.iter()
.map(|field| field.ident.as_ref().unwrap())
.collect::<Vec<_>>();
let field_type = named_fields
.iter()
.map(|field| &field.ty)
.collect::<Vec<_>>();
let mut field_component_ids = Vec::new();
let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new();
for ((field_type, field_kind), field) in
field_type.iter().zip(field_kind.iter()).zip(field.iter())
{
match field_kind {
BundleFieldKind::Component => {
field_component_ids.push(quote! {
<#field_type as #ecs_path::bundle::_BevyBundle>::component_ids(components, storages, &mut *ids);
});
field_get_components.push(quote! {
self.#field.get_components(&mut *func);
});
field_from_components.push(quote! {
#field: <#field_type as #ecs_path::bundle::_BevyBundle>::from_components(ctx, &mut *func),
});
}
BundleFieldKind::Ignore => {
field_from_components.push(quote! {
#field: ::std::default::Default::default(),
});
}
}
}
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let struct_name = &ast.ident;
TokenStream::from(quote! {
/// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
unsafe impl #impl_generics #ecs_path::bundle::_BevyBundle for #struct_name #ty_generics #where_clause {
fn component_ids(
components: &mut #ecs_path::component::Components,
storages: &mut #ecs_path::storage::Storages,
ids: &mut impl FnMut(#ecs_path::component::ComponentId)
){
#(#field_component_ids)*
}
#[allow(unused_variables, non_snake_case)]
unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self
where
__F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_>
{
Self {
#(#field_from_components)*
}
}
#[allow(unused_variables)]
fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) {
#(#field_get_components)*
}
}
})
}
fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> {
(0..count)
.map(|i| Ident::new(&fmt_string(i), Span::call_site()))
.collect::<Vec<Ident>>()
}
#[proc_macro]
pub fn impl_param_set(_input: TokenStream) -> TokenStream {
let mut tokens = TokenStream::new();
let max_params = 8;
let params = get_idents(|i| format!("P{i}"), max_params);
let params_fetch = get_idents(|i| format!("PF{i}"), max_params);
let metas = get_idents(|i| format!("m{i}"), max_params);
let mut param_fn_muts = Vec::new();
for (i, param) in params.iter().enumerate() {
let fn_name = Ident::new(&format!("p{i}"), Span::call_site());
let index = Index::from(i);
param_fn_muts.push(quote! {
pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item {
// SAFETY: systems run without conflicts with other systems.
// Conflicting params in ParamSet are not accessible at the same time
// ParamSets are guaranteed to not conflict with other SystemParams
unsafe {
<#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick)
}
}
});
}
for param_count in 1..=max_params {
let param = &params[0..param_count];
let param_fetch = &params_fetch[0..param_count];
let meta = &metas[0..param_count];
let param_fn_mut = &param_fn_muts[0..param_count];
tokens.extend(TokenStream::from(quote! {
impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)>
{
type Fetch = ParamSetState<(#(#param::Fetch,)*)>;
}
// SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)>
where #(#param_fetch: ReadOnlySystemParamFetch,)*
{ }
// SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts
// with any prior access, a panic will occur.
unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)>
{
fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self {
#(
// Pretend to add each param to the system alone, see if it conflicts
let mut #meta = system_meta.clone();
#meta.component_access_set.clear();
#meta.archetype_component_access.clear();
#param_fetch::init(world, &mut #meta);
let #param = #param_fetch::init(world, &mut system_meta.clone());
)*
#(
system_meta
.component_access_set
.extend(#meta.component_access_set);
system_meta
.archetype_component_access
.extend(&#meta.archetype_component_access);
)*
ParamSetState((#(#param,)*))
}
fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) {
let (#(#param,)*) = &mut self.0;
#(
#param.new_archetype(archetype, system_meta);
)*
}
fn apply(&mut self, world: &mut World) {
self.0.apply(world)
}
}
impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)>
{
type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>;
#[inline]
unsafe fn get_param(
state: &'s mut Self,
system_meta: &SystemMeta,
world: &'w World,
change_tick: u32,
) -> Self::Item {
ParamSet {
param_states: &mut state.0,
system_meta: system_meta.clone(),
world,
change_tick,
}
}
}
impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)>
{
#(#param_fn_mut)*
}
}));
}
tokens
}
#[derive(Default)]
struct SystemParamFieldAttributes {
pub ignore: bool,
}
static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param";
/// Implement `SystemParam` to use a struct as a parameter in a system
#[proc_macro_derive(SystemParam, attributes(system_param))]
pub fn derive_system_param(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let fields = match get_named_struct_fields(&ast.data) {
Ok(fields) => &fields.named,
Err(e) => return e.into_compile_error().into(),
};
let path = azalea_ecs_path();
let field_attributes = fields
.iter()
.map(|field| {
(
field,
field
.attrs
.iter()
.find(|a| *a.path.get_ident().as_ref().unwrap() == SYSTEM_PARAM_ATTRIBUTE_NAME)
.map_or_else(SystemParamFieldAttributes::default, |a| {
syn::custom_keyword!(ignore);
let mut attributes = SystemParamFieldAttributes::default();
a.parse_args_with(|input: ParseStream| {
if input.parse::<Option<ignore>>()?.is_some() {
attributes.ignore = true;
}
Ok(())
})
.expect("Invalid 'system_param' attribute format.");
attributes
}),
)
})
.collect::<Vec<(&Field, SystemParamFieldAttributes)>>();
let mut fields = Vec::new();
let mut field_indices = Vec::new();
let mut field_types = Vec::new();
let mut ignored_fields = Vec::new();
let mut ignored_field_types = Vec::new();
for (i, (field, attrs)) in field_attributes.iter().enumerate() {
if attrs.ignore {
ignored_fields.push(field.ident.as_ref().unwrap());
ignored_field_types.push(&field.ty);
} else {
fields.push(field.ident.as_ref().unwrap());
field_types.push(&field.ty);
field_indices.push(Index::from(i));
}
}
let generics = ast.generics;
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let lifetimeless_generics: Vec<_> = generics
.params
.iter()
.filter(|g| matches!(g, GenericParam::Type(_)))
.collect();
let mut punctuated_generics = Punctuated::<_, Token![,]>::new();
punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g {
GenericParam::Type(g) => GenericParam::Type(TypeParam {
default: None,
..g.clone()
}),
_ => unreachable!(),
}));
let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new();
punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g {
GenericParam::Type(g) => &g.ident,
_ => unreachable!(),
}));
let struct_name = &ast.ident;
let fetch_struct_visibility = &ast.vis;
TokenStream::from(quote! {
// We define the FetchState struct in an anonymous scope to avoid polluting the user namespace.
// The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via
// <EventReader<'static, 'static, T> as SystemParam>::Fetch
const _: () = {
impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause {
type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>;
}
#[doc(hidden)]
#fetch_struct_visibility struct FetchState <TSystemParamState, #punctuated_generic_idents> {
state: TSystemParamState,
marker: std::marker::PhantomData<fn()->(#punctuated_generic_idents)>
}
unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {
fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self {
Self {
state: TSystemParamState::init(world, system_meta),
marker: std::marker::PhantomData,
}
}
fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) {
self.state.new_archetype(archetype, system_meta)
}
fn apply(&mut self, world: &mut #path::world::World) {
self.state.apply(world)
}
}
impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause {
type Item = #struct_name #ty_generics;
unsafe fn get_param(
state: &'s mut Self,
system_meta: &#path::system::SystemMeta,
world: &'w #path::world::World,
change_tick: u32,
) -> Self::Item {
#struct_name {
#(#fields: <<#field_types as #path::system::SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)*
#(#ignored_fields: <#ignored_field_types>::default(),)*
}
}
}
// Safety: The `ParamState` is `ReadOnlySystemParamFetch`, so this can only read from the `World`
unsafe impl<TSystemParamState: #path::system::SystemParamState + #path::system::ReadOnlySystemParamFetch, #punctuated_generics> #path::system::ReadOnlySystemParamFetch for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {}
};
})
}
/// Implement `WorldQuery` to use a struct as a parameter in a query
#[proc_macro_derive(WorldQuery, attributes(world_query))]
pub fn derive_world_query(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
derive_world_query_impl(ast)
}
/// Generates an impl of the `SystemLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with
/// `#[system_label(ignore_fields)]`.
#[proc_macro_derive(SystemLabel, attributes(system_label))]
pub fn derive_system_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = azalea_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path
.segments
.push(format_ident!("SystemLabel").into());
derive_label(input, &trait_path, "system_label")
}
/// Generates an impl of the `StageLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with
/// `#[stage_label(ignore_fields)]`.
#[proc_macro_derive(StageLabel, attributes(stage_label))]
pub fn derive_stage_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = azalea_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path
.segments
.push(format_ident!("_BevyStageLabel").into());
derive_label(input, &trait_path, "stage_label")
}
/// Generates an impl of the `RunCriteriaLabel` trait.
///
/// This works only for unit structs, or enums with only unit variants.
/// You may force a struct or variant to behave as if it were fieldless with
/// `#[run_criteria_label(ignore_fields)]`.
#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))]
pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let mut trait_path = azalea_ecs_path();
trait_path.segments.push(format_ident!("schedule").into());
trait_path
.segments
.push(format_ident!("RunCriteriaLabel").into());
derive_label(input, &trait_path, "run_criteria_label")
}
pub(crate) fn azalea_ecs_path() -> syn::Path {
BevyManifest::default().get_path("azalea_ecs")
}
#[proc_macro_derive(Resource)]
pub fn derive_resource(input: TokenStream) -> TokenStream {
component::derive_resource(input)
}
#[proc_macro_derive(Component, attributes(component))]
pub fn derive_component(input: TokenStream) -> TokenStream {
component::derive_component(input)
}

View file

@ -1,45 +0,0 @@
#![allow(dead_code)]
use syn::DeriveInput;
use super::symbol::Symbol;
pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> {
let mut list = Vec::new();
for attr in ast.attrs.iter().filter(|a| a.path == attr_name) {
match attr.parse_meta()? {
syn::Meta::List(meta) => list.extend(meta.nested.into_iter()),
other => {
return Err(syn::Error::new_spanned(
other,
format!("expected #[{attr_name}(...)]"),
))
}
}
}
Ok(list)
}
pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> {
if let syn::Lit::Str(lit) = lit {
Ok(lit)
} else {
Err(syn::Error::new_spanned(
lit,
format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"),
))
}
}
pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> {
if let syn::Lit::Bool(lit) = lit {
Ok(lit.value())
} else {
Err(syn::Error::new_spanned(
lit,
format!(
"expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`"
),
))
}
}

View file

@ -1,227 +0,0 @@
#![allow(dead_code)]
extern crate proc_macro;
mod attrs;
mod shape;
mod symbol;
pub use attrs::*;
pub use shape::*;
pub use symbol::*;
use proc_macro::TokenStream;
use quote::{quote, quote_spanned};
use std::{env, path::PathBuf};
use syn::spanned::Spanned;
use toml::{map::Map, Value};
pub struct BevyManifest {
manifest: Map<String, Value>,
}
impl Default for BevyManifest {
fn default() -> Self {
Self {
manifest: env::var_os("CARGO_MANIFEST_DIR")
.map(PathBuf::from)
.map(|mut path| {
path.push("Cargo.toml");
let manifest = std::fs::read_to_string(path).unwrap();
toml::from_str(&manifest).unwrap()
})
.unwrap(),
}
}
}
impl BevyManifest {
pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> {
const AZALEA: &str = "azalea";
const AZALEA_ECS: &str = "azalea_ecs";
const BEVY_ECS: &str = "bevy_ecs";
const BEVY: &str = "bevy";
fn dep_package(dep: &Value) -> Option<&str> {
if dep.as_str().is_some() {
None
} else {
dep.as_table()
.unwrap()
.get("package")
.map(|name| name.as_str().unwrap())
}
}
let find_in_deps = |deps: &Map<String, Value>| -> Option<syn::Path> {
let package = if let Some(dep) = deps.get(name) {
return Some(Self::parse_str(dep_package(dep).unwrap_or(name)));
} else if let Some(dep) = deps.get(AZALEA) {
dep_package(dep).unwrap_or(AZALEA)
} else if let Some(dep) = deps.get(AZALEA_ECS) {
dep_package(dep).unwrap_or(AZALEA_ECS)
} else if let Some(dep) = deps.get(BEVY_ECS) {
dep_package(dep).unwrap_or(BEVY_ECS)
} else if let Some(dep) = deps.get(BEVY) {
dep_package(dep).unwrap_or(BEVY)
} else {
return None;
};
let mut path = Self::parse_str::<syn::Path>(package);
if let Some(module) = name.strip_prefix("azalea_") {
path.segments.push(Self::parse_str(module));
}
Some(path)
};
let deps = self
.manifest
.get("dependencies")
.map(|deps| deps.as_table().unwrap());
let deps_dev = self
.manifest
.get("dev-dependencies")
.map(|deps| deps.as_table().unwrap());
deps.and_then(find_in_deps)
.or_else(|| deps_dev.and_then(find_in_deps))
}
/// Returns the path for the crate with the given name.
///
/// This is a convenience method for constructing a [manifest] and
/// calling the [`get_path`] method.
///
/// This method should only be used where you just need the path and can't
/// cache the [manifest]. If caching is possible, it's recommended to create
/// the [manifest] yourself and use the [`get_path`] method.
///
/// [`get_path`]: Self::get_path
/// [manifest]: Self
pub fn get_path_direct(name: &str) -> syn::Path {
Self::default().get_path(name)
}
pub fn get_path(&self, name: &str) -> syn::Path {
self.maybe_get_path(name)
.unwrap_or_else(|| Self::parse_str(name))
}
pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T {
syn::parse(path.parse::<TokenStream>().unwrap()).unwrap()
}
}
/// Derive a label trait
///
/// # Args
///
/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label
/// trait
/// - `trait_path`: The path [`syn::Path`] to the label trait
pub fn derive_label(
input: syn::DeriveInput,
trait_path: &syn::Path,
attr_name: &str,
) -> TokenStream {
// return true if the variant specified is an `ignore_fields` attribute
fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool {
if attr.path.get_ident().as_ref().unwrap() != &attr_name {
return false;
}
syn::custom_keyword!(ignore_fields);
attr.parse_args_with(|input: syn::parse::ParseStream| {
let ignore = input.parse::<Option<ignore_fields>>()?.is_some();
Ok(ignore)
})
.unwrap()
}
let ident = input.ident.clone();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
where_token: Default::default(),
predicates: Default::default(),
});
where_clause
.predicates
.push(syn::parse2(quote! { Self: 'static }).unwrap());
let as_str = match input.data {
syn::Data::Struct(d) => {
// see if the user tried to ignore fields incorrectly
if let Some(attr) = d
.fields
.iter()
.flat_map(|f| &f.attrs)
.find(|a| is_ignore(a, attr_name))
{
let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration");
return quote_spanned! {
attr.span() => compile_error!(#err_msg);
}
.into();
}
// Structs must either be fieldless, or explicitly ignore the fields.
let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name));
if matches!(d.fields, syn::Fields::Unit) || ignore_fields {
let lit = ident.to_string();
quote! { #lit }
} else {
let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
return quote_spanned! {
d.fields.span() => compile_error!(#err_msg);
}
.into();
}
}
syn::Data::Enum(d) => {
// check if the user put #[label(ignore_fields)] in the wrong place
if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) {
let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations");
return quote_spanned! {
attr.span() => compile_error!(#err_msg);
}
.into();
}
let arms = d.variants.iter().map(|v| {
// Variants must either be fieldless, or explicitly ignore the fields.
let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name));
if matches!(v.fields, syn::Fields::Unit) | ignore_fields {
let mut path = syn::Path::from(ident.clone());
path.segments.push(v.ident.clone().into());
let lit = format!("{ident}::{}", v.ident.clone());
quote! { #path { .. } => #lit }
} else {
let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`");
quote_spanned! {
v.fields.span() => _ => { compile_error!(#err_msg); }
}
}
});
quote! {
match self {
#(#arms),*
}
}
}
syn::Data::Union(_) => {
return quote_spanned! {
input.span() => compile_error!("Unions cannot be used as labels.");
}
.into();
}
};
(quote! {
impl #impl_generics #trait_path for #ident #ty_generics #where_clause {
fn as_str(&self) -> &'static str {
#as_str
}
}
})
.into()
}

View file

@ -1,21 +0,0 @@
use proc_macro::Span;
use syn::{Data, DataStruct, Error, Fields, FieldsNamed};
/// Get the fields of a data structure if that structure is a struct with named
/// fields; otherwise, return a compile error that points to the site of the
/// macro invocation.
pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> {
match data {
Data::Struct(DataStruct {
fields: Fields::Named(fields),
..
}) => Ok(fields),
_ => Err(Error::new(
// This deliberately points to the call site rather than the structure
// body; marking the entire body as the source of the error makes it
// impossible to figure out which `derive` has a problem.
Span::call_site().into(),
"Only structs with named fields are supported",
)),
}
}

View file

@ -1,35 +0,0 @@
use std::fmt::{self, Display};
use syn::{Ident, Path};
#[derive(Copy, Clone)]
pub struct Symbol(pub &'static str);
impl PartialEq<Symbol> for Ident {
fn eq(&self, word: &Symbol) -> bool {
self == word.0
}
}
impl<'a> PartialEq<Symbol> for &'a Ident {
fn eq(&self, word: &Symbol) -> bool {
*self == word.0
}
}
impl PartialEq<Symbol> for Path {
fn eq(&self, word: &Symbol) -> bool {
self.is_ident(word.0)
}
}
impl<'a> PartialEq<Symbol> for &'a Path {
fn eq(&self, word: &Symbol) -> bool {
self.is_ident(word.0)
}
}
impl Display for Symbol {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str(self.0)
}
}

View file

@ -1,157 +0,0 @@
#![feature(trait_alias)]
//! Re-export important parts of [`bevy_ecs`] and [`bevy_app`] and make them
//! more compatible with Azalea.
//!
//! This is completely compatible with `bevy_ecs`, so it won't cause issues if
//! you use plugins meant for Bevy.
//!
//! Changes:
//! - Add [`TickPlugin`], [`TickStage`] and [`AppTickExt`] (which adds
//! `app.add_tick_system` and `app.add_tick_system_set`).
//! - Change the macros to use azalea/azalea_ecs instead of bevy/bevy_ecs
//! - Rename `world::World` to [`ecs::Ecs`]
//! - Re-export `bevy_app` in the [`app`] module.
//!
//! [`bevy_ecs`]: https://docs.rs/bevy_ecs
//! [`bevy_app`]: https://docs.rs/bevy_app
use std::time::{Duration, Instant};
pub mod ecs {
pub use bevy_ecs::world::World as Ecs;
pub use bevy_ecs::world::{EntityMut, EntityRef, Mut};
}
pub mod component {
pub use azalea_ecs_macros::Component;
pub use bevy_ecs::component::{ComponentId, ComponentStorage, Components, TableStorage};
// we do this because re-exporting Component would re-export the macro as well,
// which is bad (since we have our own Component macro)
// instead, we have to do this so Component is a trait alias and the original
// impl-able trait is still available as _BevyComponent
pub trait Component = bevy_ecs::component::Component;
pub use bevy_ecs::component::Component as _BevyComponent;
}
pub mod bundle {
pub use azalea_ecs_macros::Bundle;
pub trait Bundle = bevy_ecs::bundle::Bundle;
pub use bevy_ecs::bundle::Bundle as _BevyBundle;
}
pub mod system {
pub use azalea_ecs_macros::Resource;
pub use bevy_ecs::system::{
Command, Commands, EntityCommands, Query, Res, ResMut, SystemState,
};
pub trait Resource = bevy_ecs::system::Resource;
pub use bevy_ecs::system::Resource as _BevyResource;
}
pub mod schedule {
pub use azalea_ecs_macros::StageLabel;
pub use bevy_ecs::schedule::{
IntoRunCriteria, IntoSystemDescriptor, ReportExecutionOrderAmbiguities, Schedule, Stage,
SystemSet, SystemStage,
};
pub trait StageLabel = bevy_ecs::schedule::StageLabel;
pub use bevy_ecs::schedule::StageLabel as _BevyStageLabel;
}
pub use bevy_app as app;
pub use bevy_ecs::{entity, event, ptr, query, storage};
use app::{App, CoreStage, Plugin};
use bevy_ecs::schedule::*;
use ecs::Ecs;
pub struct TickPlugin {
/// How often a tick should happen. 50 milliseconds by default. Set to 0 to
/// tick every update.
pub tick_interval: Duration,
}
impl Plugin for TickPlugin {
fn build(&self, app: &mut App) {
app.add_stage_before(
CoreStage::Update,
TickLabel,
TickStage {
interval: self.tick_interval,
next_tick: Instant::now(),
stage: Box::new(SystemStage::parallel()),
},
);
}
}
impl Default for TickPlugin {
fn default() -> Self {
Self {
tick_interval: Duration::from_millis(50),
}
}
}
#[derive(StageLabel)]
struct TickLabel;
/// A [`Stage`] that runs every 50 milliseconds.
pub struct TickStage {
pub interval: Duration,
pub next_tick: Instant,
stage: Box<dyn Stage>,
}
impl Stage for TickStage {
fn run(&mut self, ecs: &mut Ecs) {
// if the interval is 0, that means it runs every tick
if self.interval.is_zero() {
self.stage.run(ecs);
return;
}
// keep calling run until it's caught up
// TODO: Minecraft bursts up to 10 ticks and then skips, we should too (but
// check the source so we do it right)
while Instant::now() > self.next_tick {
self.next_tick += self.interval;
self.stage.run(ecs);
}
}
}
pub trait AppTickExt {
fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App;
fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App;
}
impl AppTickExt for App {
/// Adds a set of ECS systems that will run every 50 milliseconds.
///
/// Note that you should NOT have `EventReader`s in tick systems, as this
/// will make them sometimes be missed.
fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App {
let tick_stage = self
.schedule
.get_stage_mut::<TickStage>(TickLabel)
.expect("Tick Stage not found");
let stage = tick_stage
.stage
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
stage.add_system_set(system_set);
self
}
/// Adds a new ECS system that will run every 50 milliseconds.
///
/// Note that you should NOT have `EventReader`s in tick systems, as this
/// will make them sometimes be missed.
fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App {
let tick_stage = self
.schedule
.get_stage_mut::<TickStage>(TickLabel)
.expect("Tick Stage not found");
let stage = tick_stage
.stage
.downcast_mut::<SystemStage>()
.expect("Fixed Timestep sub-stage is not a SystemStage");
stage.add_system(system);
self
}
}

View file

@ -9,13 +9,15 @@ version = "0.6.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
azalea-block = {path = "../azalea-block", version = "^0.6.0"}
azalea-core = {path = "../azalea-core", version = "^0.6.0"}
azalea-ecs = {version = "0.6.0", path = "../azalea-ecs"}
azalea-registry = {path = "../azalea-registry", version = "^0.6.0"}
azalea-world = {path = "../azalea-world", version = "^0.6.0"}
azalea-block = { path = "../azalea-block", version = "^0.6.0" }
azalea-core = { path = "../azalea-core", version = "^0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
azalea-world = { path = "../azalea-world", version = "^0.6.0" }
bevy_app = "0.10.0"
bevy_ecs = "0.10.0"
once_cell = "1.16.0"
parking_lot = "^0.12.1"
[dev-dependencies]
bevy_time = "0.10.0"
uuid = "^1.1.2"

View file

@ -7,7 +7,7 @@ mod world_collisions;
use azalea_core::{Axis, Vec3, AABB, EPSILON};
use azalea_world::{
entity::{self},
MoveEntityError, World,
Instance, MoveEntityError,
};
pub use blocks::BlockWithShape;
pub use discrete_voxel_shape::*;
@ -52,7 +52,7 @@ pub enum MoverType {
// return var4;
// }
fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 {
fn collide(movement: &Vec3, world: &Instance, physics: &entity::Physics) -> Vec3 {
let entity_bounding_box = physics.bounding_box;
// TODO: get_entity_collisions
// let entity_collisions = world.get_entity_collisions(self,
@ -73,7 +73,7 @@ fn collide(movement: &Vec3, world: &World, physics: &entity::Physics) -> Vec3 {
pub fn move_colliding(
_mover_type: &MoverType,
movement: &Vec3,
world: &World,
world: &Instance,
position: &mut entity::Position,
physics: &mut entity::Physics,
) -> Result<(), MoveEntityError> {
@ -186,7 +186,7 @@ pub fn move_colliding(
fn collide_bounding_box(
movement: &Vec3,
entity_bounding_box: &AABB,
world: &World,
world: &Instance,
entity_collisions: Vec<VoxelShape>,
) -> Vec3 {
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);

View file

@ -2,16 +2,16 @@ use super::Shapes;
use crate::collision::{BlockWithShape, VoxelShape, AABB};
use azalea_block::BlockState;
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
use azalea_world::{Chunk, World};
use azalea_world::{Chunk, Instance};
use parking_lot::RwLock;
use std::sync::Arc;
pub fn get_block_collisions(world: &World, aabb: AABB) -> BlockCollisions<'_> {
pub fn get_block_collisions(world: &Instance, aabb: AABB) -> BlockCollisions<'_> {
BlockCollisions::new(world, aabb)
}
pub struct BlockCollisions<'a> {
pub world: &'a World,
pub world: &'a Instance,
pub aabb: AABB,
pub entity_shape: VoxelShape,
pub cursor: Cursor3d,
@ -19,7 +19,7 @@ pub struct BlockCollisions<'a> {
}
impl<'a> BlockCollisions<'a> {
pub fn new(world: &'a World, aabb: AABB) -> Self {
pub fn new(world: &'a Instance, aabb: AABB) -> Self {
let origin_x = (aabb.min_x - EPSILON).floor() as i32 - 1;
let origin_y = (aabb.min_y - EPSILON).floor() as i32 - 1;
let origin_z = (aabb.min_z - EPSILON).floor() as i32 - 1;

View file

@ -5,37 +5,38 @@ pub mod collision;
use azalea_block::{Block, BlockState};
use azalea_core::{BlockPos, Vec3};
use azalea_ecs::{
app::{App, Plugin},
entity::Entity,
event::{EventReader, EventWriter},
query::With,
schedule::IntoSystemDescriptor,
system::{Query, Res},
AppTickExt,
};
use azalea_world::{
entity::{
metadata::Sprinting, move_relative, Attributes, Jumping, Local, Physics, Position,
WorldName,
},
World, WorldContainer,
Instance, WorldContainer,
};
use bevy_app::{App, CoreSchedule, IntoSystemAppConfigs, Plugin};
use bevy_ecs::{
entity::Entity,
event::{EventReader, EventWriter},
query::With,
schedule::{IntoSystemConfig, IntoSystemConfigs, SystemSet},
system::{Query, Res},
};
use collision::{move_colliding, MoverType};
/// A Bevy [`SystemSet`] for running physics that makes entities do things.
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
pub struct PhysicsSet;
pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) {
app.add_event::<ForceJumpEvent>()
.add_system(
force_jump_listener
.label("force_jump_listener")
.after("walk_listener")
.after("sprint_listener")
.before(azalea_world::entity::update_bounding_box),
)
.add_tick_system(travel.label("travel").after(ai_step))
.add_tick_system(ai_step.label("ai_step"));
.add_system(force_jump_listener.before(azalea_world::entity::update_bounding_box))
.add_systems(
(ai_step, travel)
.chain()
.in_set(PhysicsSet)
.in_schedule(CoreSchedule::FixedUpdate),
);
}
}
@ -156,7 +157,7 @@ pub fn ai_step(
/// Jump even if we aren't on the ground.
pub struct ForceJumpEvent(pub Entity);
fn force_jump_listener(
pub fn force_jump_listener(
mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>,
world_container: Res<WorldContainer>,
mut events: EventReader<ForceJumpEvent>,
@ -201,7 +202,7 @@ fn get_block_pos_below_that_affects_movement(position: &Position) -> BlockPos {
fn handle_relative_friction_and_calculate_movement(
block_friction: f32,
world: &World,
world: &Instance,
physics: &mut Physics,
position: &mut Position,
attributes: &Attributes,
@ -251,7 +252,7 @@ fn get_friction_influenced_speed(physics: &Physics, attributes: &Attributes, fri
/// Returns the what the entity's jump should be multiplied by based on the
/// block they're standing on.
fn block_jump_factor(world: &World, position: &Position) -> f32 {
fn block_jump_factor(world: &Instance, position: &Position) -> f32 {
let block_at_pos = world.chunks.get_block_state(&position.into());
let block_below = world
.chunks
@ -279,7 +280,7 @@ fn block_jump_factor(world: &World, position: &Position) -> f32 {
// public double getJumpBoostPower() {
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; }
fn jump_power(world: &World, position: &Position) -> f32 {
fn jump_power(world: &Instance, position: &Position) -> f32 {
0.42 * block_jump_factor(world, position)
}
@ -303,22 +304,21 @@ mod tests {
use super::*;
use azalea_core::{ChunkPos, ResourceLocation};
use azalea_ecs::{app::App, TickPlugin};
use azalea_world::{
entity::{EntityBundle, EntityPlugin, MinecraftEntityId},
Chunk, PartialWorld,
};
use bevy_app::App;
use bevy_time::fixed_timestep::FixedTime;
use uuid::Uuid;
/// You need an app to spawn entities in the world and do updates.
fn make_test_app() -> App {
let mut app = App::new();
app.add_plugin(TickPlugin {
tick_interval: Duration::ZERO,
})
.add_plugin(PhysicsPlugin)
.add_plugin(EntityPlugin)
.init_resource::<WorldContainer>();
app.add_plugin(PhysicsPlugin)
.add_plugin(EntityPlugin)
.insert_resource(FixedTime::new(Duration::from_millis(50)))
.init_resource::<WorldContainer>();
app
}
@ -353,6 +353,7 @@ mod tests {
// y should start at 70
assert_eq!(entity_pos.y, 70.);
}
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
{
let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -361,6 +362,7 @@ mod tests {
let entity_physics = app.world.get::<Physics>(entity).unwrap().clone();
assert!(entity_physics.delta.y < 0.);
}
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
{
let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -413,6 +415,7 @@ mod tests {
block_state.is_some(),
"Block state should exist, if this fails that means the chunk wasn't loaded and the block didn't get placed"
);
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
{
let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -421,6 +424,7 @@ mod tests {
let entity_physics = app.world.get::<Physics>(entity).unwrap().clone();
assert!(entity_physics.delta.y < 0.);
}
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
{
let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -463,8 +467,8 @@ mod tests {
.id();
let block_state = partial_world.chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 },
azalea_block::StoneSlabBlock {
kind: azalea_block::Type::Bottom,
azalea_block::blocks::StoneSlab {
kind: azalea_block::properties::Type::Bottom,
waterlogged: false,
}
.into(),
@ -476,6 +480,7 @@ mod tests {
);
// do a few steps so we fall on the slab
for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
}
let entity_pos = app.world.get::<Position>(entity).unwrap();
@ -516,8 +521,8 @@ mod tests {
.id();
let block_state = world_lock.write().chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 },
azalea_block::StoneSlabBlock {
kind: azalea_block::Type::Top,
azalea_block::blocks::StoneSlab {
kind: azalea_block::properties::Type::Top,
waterlogged: false,
}
.into(),
@ -528,6 +533,7 @@ mod tests {
);
// do a few steps so we fall on the slab
for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
}
let entity_pos = app.world.get::<Position>(entity).unwrap();
@ -568,11 +574,11 @@ mod tests {
.id();
let block_state = world_lock.write().chunks.set_block_state(
&BlockPos { x: 0, y: 69, z: 0 },
azalea_block::CobblestoneWallBlock {
east: azalea_block::EastWall::Low,
north: azalea_block::NorthWall::Low,
south: azalea_block::SouthWall::Low,
west: azalea_block::WestWall::Low,
azalea_block::blocks::CobblestoneWall {
east: azalea_block::properties::EastWall::Low,
north: azalea_block::properties::NorthWall::Low,
south: azalea_block::properties::SouthWall::Low,
west: azalea_block::properties::WestWall::Low,
up: false,
waterlogged: false,
}
@ -584,6 +590,7 @@ mod tests {
);
// do a few steps so we fall on the wall
for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
}
@ -629,11 +636,11 @@ mod tests {
y: 69,
z: -8,
},
azalea_block::CobblestoneWallBlock {
east: azalea_block::EastWall::Low,
north: azalea_block::NorthWall::Low,
south: azalea_block::SouthWall::Low,
west: azalea_block::WestWall::Low,
azalea_block::blocks::CobblestoneWall {
east: azalea_block::properties::EastWall::Low,
north: azalea_block::properties::NorthWall::Low,
south: azalea_block::properties::SouthWall::Low,
west: azalea_block::properties::WestWall::Low,
up: false,
waterlogged: false,
}
@ -645,6 +652,7 @@ mod tests {
);
// do a few steps so we fall on the wall
for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update();
}

View file

@ -9,32 +9,39 @@ version = "0.6.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
async-compression = {version = "^0.3.8", features = ["tokio", "zlib"], optional = true}
async-compression = { version = "^0.3.8", features = [
"tokio",
"zlib",
], optional = true }
async-recursion = "1.0.0"
azalea-auth = {path = "../azalea-auth", version = "^0.6.0" }
azalea-block = {path = "../azalea-block", default-features = false, version = "^0.6.0" }
azalea-brigadier = {path = "../azalea-brigadier", version = "^0.6.0", features = ["azalea-buf"]}
azalea-buf = {path = "../azalea-buf", version = "^0.6.0" }
azalea-chat = {path = "../azalea-chat", version = "^0.6.0" }
azalea-core = {path = "../azalea-core", optional = true, version = "^0.6.0" }
azalea-crypto = {path = "../azalea-crypto", version = "^0.6.0" }
azalea-nbt = {path = "../azalea-nbt", version = "^0.6.0" }
azalea-protocol-macros = {path = "./azalea-protocol-macros", version = "^0.6.0" }
azalea-registry = {path = "../azalea-registry", version = "^0.6.0" }
azalea-world = {path = "../azalea-world", version = "^0.6.0" }
bevy_ecs = { version = "0.9.1", default-features = false }
azalea-auth = { path = "../azalea-auth", version = "^0.6.0" }
azalea-block = { path = "../azalea-block", default-features = false, version = "^0.6.0" }
azalea-brigadier = { path = "../azalea-brigadier", version = "^0.6.0", features = [
"azalea-buf",
] }
azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
azalea-core = { path = "../azalea-core", optional = true, version = "^0.6.0" }
azalea-crypto = { path = "../azalea-crypto", version = "^0.6.0" }
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "^0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
azalea-world = { path = "../azalea-world", version = "^0.6.0" }
bevy_ecs = { version = "0.10.0", default-features = false }
byteorder = "^1.4.3"
bytes = "^1.1.0"
flate2 = "1.0.23"
futures = "0.3.24"
futures-util = "0.3.24"
log = "0.4.17"
serde = {version = "1.0.130", features = ["serde_derive"]}
serde = { version = "1.0.130", features = ["serde_derive"] }
serde_json = "^1.0.72"
thiserror = "1.0.37"
tokio = {version = "^1.24.2", features = ["io-util", "net", "macros"]}
tokio-util = {version = "0.7.4", features = ["codec"]}
trust-dns-resolver = {version = "^0.22.0", default-features = false, features = ["tokio-runtime"]}
tokio = { version = "^1.24.2", features = ["io-util", "net", "macros"] }
tokio-util = { version = "0.7.4", features = ["codec"] }
trust-dns-resolver = { version = "^0.22.0", default-features = false, features = [
"tokio-runtime",
] }
uuid = "1.1.2"
[features]

View file

@ -0,0 +1,7 @@
use azalea_protocol_macros::ClientboundGamePacket;
use azalea_buf::McBuf;
#[derive(Clone, Debug, McBuf, ClientboundGamePacket)]
pub struct ClientboundChunksBiomesPacket {
pub chunk_biome_data: todo!(),
}

View file

@ -15,9 +15,10 @@ azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
azalea-core = { path = "../azalea-core", version = "^0.6.0", features = [
"bevy_ecs",
] }
azalea-ecs = { version = "0.6.0", path = "../azalea-ecs" }
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "^0.6.0" }
bevy_app = "0.10.0"
bevy_ecs = "0.10.0"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
enum-as-inner = "0.5.1"
log = "0.4.17"

View file

@ -158,13 +158,13 @@ impl BitStorage {
.unwrap()
}
/// Get the data at the given index.
///
/// # Panics
///
/// This function will panic if the given index is greater than or equal to
/// the size of this storage.
pub fn get(&self, index: usize) -> u64 {
// Validate.inclusiveBetween(0L, (long)(this.size - 1), (long)var1);
// int var2 = this.cellIndex(var1);
// long var3 = this.data[var2];
// int var5 = (var1 - var2 * this.valuesPerLong) * this.bits;
// return (int)(var3 >> var5 & this.mask);
assert!(
index < self.size,
"Index {} out of bounds (must be less than {})",

View file

@ -1,5 +1,5 @@
use azalea_core::ResourceLocation;
use azalea_ecs::system::Resource;
use bevy_ecs::system::Resource;
use log::error;
use nohash_hasher::IntMap;
use parking_lot::RwLock;
@ -8,7 +8,7 @@ use std::{
sync::{Arc, Weak},
};
use crate::{ChunkStorage, World};
use crate::{ChunkStorage, Instance};
/// A container of [`World`]s. Worlds are stored as a Weak pointer here, so
/// if no clients are using a world it will be forgotten.
@ -26,7 +26,7 @@ pub struct WorldContainer {
// telling them apart. We hope most servers are nice and don't do that though. It's only an
// issue when there's multiple clients with the same WorldContainer in different worlds
// anyways.
pub worlds: HashMap<ResourceLocation, Weak<RwLock<World>>>,
pub worlds: HashMap<ResourceLocation, Weak<RwLock<Instance>>>,
}
impl WorldContainer {
@ -37,7 +37,7 @@ impl WorldContainer {
}
/// Get a world from the container.
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<RwLock<World>>> {
pub fn get(&self, name: &ResourceLocation) -> Option<Arc<RwLock<Instance>>> {
self.worlds.get(name).and_then(|world| world.upgrade())
}
@ -49,7 +49,7 @@ impl WorldContainer {
name: ResourceLocation,
height: u32,
min_y: i32,
) -> Arc<RwLock<World>> {
) -> Arc<RwLock<Instance>> {
if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
let existing = existing_lock.read();
if existing.chunks.height != height {
@ -66,7 +66,7 @@ impl WorldContainer {
}
existing_lock.clone()
} else {
let world = Arc::new(RwLock::new(World {
let world = Arc::new(RwLock::new(Instance {
chunks: ChunkStorage::new(height, min_y),
entities_by_chunk: HashMap::new(),
entity_by_id: IntMap::default(),

View file

@ -6,7 +6,7 @@ use std::{
};
use azalea_buf::{BufReadError, McBuf, McBufReadable, McBufWritable};
use azalea_ecs::component::Component;
use bevy_ecs::component::Component;
use thiserror::Error;
use uuid::{uuid, Uuid};

View file

@ -6,7 +6,7 @@ use azalea_buf::{
};
use azalea_chat::FormattedText;
use azalea_core::{BlockPos, Direction, GlobalPos, Particle, Slot};
use azalea_ecs::component::Component;
use bevy_ecs::component::Component;
use derive_more::Deref;
use enum_as_inner::EnumAsInner;
use nohash_hasher::IntSet;

View file

@ -1,5 +1,5 @@
use azalea_core::{Vec3, AABB};
use azalea_ecs::{query::Changed, system::Query};
use bevy_ecs::{query::Changed, system::Query};
use super::{Physics, Position};

View file

@ -9,15 +9,14 @@ use crate::{
update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer,
};
use azalea_core::ChunkPos;
use azalea_ecs::{
app::{App, CoreStage, Plugin},
use bevy_app::{App, CoreSet, Plugin};
use bevy_ecs::{
component::Component,
ecs::Ecs,
ecs::EntityMut,
entity::Entity,
query::{Added, Changed, With, Without},
schedule::{IntoSystemDescriptor, SystemSet},
system::{Command, Commands, Query, Res, ResMut, Resource},
schedule::{IntoSystemConfig, IntoSystemConfigs, SystemSet},
system::{Commands, EntityCommand, Query, Res, ResMut, Resource},
world::{EntityMut, World},
};
use derive_more::{Deref, DerefMut};
use log::{debug, warn};
@ -32,6 +31,18 @@ use uuid::Uuid;
use super::Local;
/// A Bevy [`SystemSet`] for various types of entity updates.
#[derive(SystemSet, Debug, Hash, Eq, PartialEq, Clone)]
pub enum EntityUpdateSet {
/// Remove ECS entities that refer to an entity that was already in the ECS
/// before.
Deduplicate,
/// Create search indexes for entities.
Index,
/// Remove despawned entities from search indexes.
Deindex,
}
/// Plugin handling some basic entity functionality.
pub struct EntityPlugin;
impl Plugin for EntityPlugin {
@ -40,30 +51,31 @@ impl Plugin for EntityPlugin {
// added to indexes during update (done by this plugin)
// modified during update
// despawned post-update (done by this plugin)
app.add_system_set_to_stage(
CoreStage::PreUpdate,
SystemSet::new().with_system(remove_despawned_entities_from_indexes),
app.add_system(
remove_despawned_entities_from_indexes
.in_base_set(CoreSet::PreUpdate)
.in_set(EntityUpdateSet::Deindex),
)
.add_system_set_to_stage(
CoreStage::PostUpdate,
SystemSet::new()
.with_system(deduplicate_entities.label("deduplicate_entities"))
.with_system(deduplicate_local_entities.label("deduplicate_entities")),
.add_systems(
(deduplicate_entities, deduplicate_local_entities)
.in_base_set(CoreSet::PostUpdate)
.in_set(EntityUpdateSet::Deduplicate),
)
.add_system_set(
SystemSet::new()
.with_system(update_entity_chunk_positions)
.with_system(update_uuid_index.label("update_indexes"))
.with_system(update_entity_by_id_index.label("update_indexes")),
)
.add_system_set(
SystemSet::new()
.with_system(add_updates_received.label("add_updates_received"))
.with_system(debug_new_entity)
.with_system(debug_detect_updates_received_on_local_entities)
.with_system(add_dead)
.with_system(update_bounding_box),
.add_systems(
(
update_entity_chunk_positions,
update_uuid_index,
update_entity_by_id_index,
)
.in_set(EntityUpdateSet::Index),
)
.add_systems((
add_updates_received,
debug_new_entity,
debug_detect_updates_received_on_local_entities,
add_dead,
update_bounding_box,
))
.init_resource::<EntityInfos>();
}
}
@ -134,26 +146,24 @@ impl PartialEntityInfos {
/// other clients within render distance will get too. You usually don't need
/// this when the change isn't relative either.
pub struct RelativeEntityUpdate {
pub entity: Entity,
pub partial_world: Arc<RwLock<PartialWorld>>,
// a function that takes the entity and updates it
pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>,
}
impl Command for RelativeEntityUpdate {
fn write(self, world: &mut Ecs) {
impl EntityCommand for RelativeEntityUpdate {
fn write(self, entity: Entity, world: &mut World) {
let partial_entity_infos = &mut self.partial_world.write().entity_infos;
let mut entity = world.entity_mut(self.entity);
let mut entity_mut = world.entity_mut(entity);
if Some(self.entity) == partial_entity_infos.owner_entity {
if Some(entity) == partial_entity_infos.owner_entity {
// if the entity owns this partial world, it's always allowed to update itself
(self.update)(&mut entity);
(self.update)(&mut entity_mut);
return;
};
let entity_id = *entity.get::<MinecraftEntityId>().unwrap();
let Some(updates_received) = entity.get_mut::<UpdatesReceived>() else {
let entity_id = *entity_mut.get::<MinecraftEntityId>().unwrap();
let Some(updates_received) = entity_mut.get_mut::<UpdatesReceived>() else {
// a client tried to update another client, which isn't allowed
return;
};
@ -170,9 +180,9 @@ impl Command for RelativeEntityUpdate {
.updates_received
.insert(entity_id, new_updates_received);
**entity.get_mut::<UpdatesReceived>().unwrap() = new_updates_received;
**entity_mut.get_mut::<UpdatesReceived>().unwrap() = new_updates_received;
let mut entity = world.entity_mut(self.entity);
let mut entity = world.entity_mut(entity);
(self.update)(&mut entity);
}
}

File diff suppressed because it is too large Load diff

View file

@ -12,7 +12,7 @@ use self::{attributes::AttributeInstance, metadata::Health};
pub use attributes::Attributes;
use azalea_block::BlockState;
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
use azalea_ecs::{
use bevy_ecs::{
bundle::Bundle,
component::Component,
entity::Entity,
@ -22,7 +22,9 @@ use azalea_ecs::{
pub use data::*;
use derive_more::{Deref, DerefMut};
pub use dimensions::{update_bounding_box, EntityDimensions};
pub use info::{EntityInfos, EntityPlugin, LoadedBy, PartialEntityInfos, RelativeEntityUpdate};
pub use info::{
EntityInfos, EntityPlugin, EntityUpdateSet, LoadedBy, PartialEntityInfos, RelativeEntityUpdate,
};
use std::fmt::Debug;
use uuid::Uuid;

View file

@ -0,0 +1,247 @@
//! Iterators for iterating over Minecraft blocks and chunks, based on
//! [prismarine-world's iterators](https://github.com/PrismarineJS/prismarine-world/blob/master/src/iterators.js).
use azalea_core::{BlockPos, ChunkPos};
/// An octahedron iterator, useful for iterating over blocks in a world.
///
/// ```
/// # use azalea_core::BlockPos;
/// # use azalea_world::iterators::BlockIterator;
///
/// let mut iter = BlockIterator::new(BlockPos::default(), 4);
/// for block_pos in iter {
/// println!("{:?}", block_pos);
/// }
/// ```
pub struct BlockIterator {
start: BlockPos,
max_distance: u32,
pos: BlockPos,
apothem: u32,
left: i32,
right: i32,
}
impl BlockIterator {
pub fn new(start: BlockPos, max_distance: u32) -> Self {
Self {
start,
max_distance,
pos: BlockPos {
x: -1,
y: -1,
z: -1,
},
apothem: 1,
left: 1,
right: 2,
}
}
}
impl Iterator for BlockIterator {
type Item = BlockPos;
fn next(&mut self) -> Option<Self::Item> {
if self.apothem > self.max_distance {
return None;
}
self.right -= 1;
if self.right < 0 {
self.left -= 1;
if self.left < 0 {
self.pos.z += 2;
if self.pos.z > 1 {
self.pos.y += 2;
if self.pos.y > 1 {
self.pos.x += 2;
if self.pos.x > 1 {
self.apothem += 1;
self.pos.x = -1;
}
self.pos.y = -1;
}
self.pos.z = -1;
}
self.left = self.apothem as i32;
}
self.right = self.left;
}
let x = self.pos.x * self.right;
let y = self.pos.y * ((self.apothem as i32) - self.left);
let z = self.pos.z * ((self.apothem as i32) - (i32::abs(x) + i32::abs(y)));
Some(BlockPos { x, y, z } + self.start)
}
}
/// A spiral iterator, useful for iterating over chunks in a world. Use
/// `ChunkIterator` to sort by x+y+z (Manhattan) distance.
///
/// ```
/// # use azalea_core::ChunkPos;
/// # use azalea_world::iterators::SquareChunkIterator;
///
/// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 4);
/// for chunk_pos in iter {
/// println!("{:?}", chunk_pos);
/// }
/// ```
pub struct SquareChunkIterator {
start: ChunkPos,
number_of_points: u32,
dir: ChunkPos,
segment_len: u32,
pos: ChunkPos,
segment_passed: u32,
current_iter: u32,
}
impl SquareChunkIterator {
pub fn new(start: ChunkPos, max_distance: u32) -> Self {
Self {
start,
number_of_points: u32::pow(max_distance * 2 - 1, 2),
dir: ChunkPos { x: 1, z: 0 },
segment_len: 1,
pos: ChunkPos::default(),
segment_passed: 0,
current_iter: 0,
}
}
/// Change the distance that this iterator won't go past.
///
/// ```
/// # use azalea_core::ChunkPos;
/// # use azalea_world::iterators::SquareChunkIterator;
///
/// let mut iter = SquareChunkIterator::new(ChunkPos::default(), 2);
/// while let Some(chunk_pos) = iter.next() {
/// println!("{:?}", chunk_pos);
/// }
/// iter.set_max_distance(4);
/// while let Some(chunk_pos) = iter.next() {
/// println!("{:?}", chunk_pos);
/// }
/// ```
pub fn set_max_distance(&mut self, max_distance: u32) {
self.number_of_points = u32::pow(max_distance * 2 - 1, 2);
}
}
impl Iterator for SquareChunkIterator {
type Item = ChunkPos;
fn next(&mut self) -> Option<Self::Item> {
if self.current_iter > self.number_of_points {
return None;
}
let output = self.start + self.dir;
// make a step, add the direction to the current position
self.pos.x += self.dir.x;
self.pos.z += self.dir.z;
self.segment_passed += 1;
if self.segment_passed == self.segment_len {
// done with current segment
self.segment_passed = 0;
// rotate directions
(self.dir.x, self.dir.z) = (-self.dir.z, self.dir.x);
// increase segment length if necessary
if self.dir.z == 0 {
self.segment_len += 1;
}
}
self.current_iter += 1;
Some(output)
}
}
/// A diagonal spiral iterator, useful for iterating over chunks in a world.
///
/// ```
/// # use azalea_core::ChunkPos;
/// # use azalea_world::iterators::ChunkIterator;
///
/// let mut iter = ChunkIterator::new(ChunkPos::default(), 4);
/// for chunk_pos in iter {
/// println!("{:?}", chunk_pos);
/// }
/// ```
pub struct ChunkIterator {
pub max_distance: u32,
pub start: ChunkPos,
pub pos: ChunkPos,
pub layer: i32,
pub leg: i32,
}
impl ChunkIterator {
pub fn new(start: ChunkPos, max_distance: u32) -> Self {
Self {
max_distance,
start,
pos: ChunkPos { x: 2, z: -1 },
layer: 1,
leg: -1,
}
}
}
impl Iterator for ChunkIterator {
type Item = ChunkPos;
fn next(&mut self) -> Option<Self::Item> {
match self.leg {
-1 => {
self.leg = 0;
return Some(self.start);
}
0 => {
if self.max_distance == 1 {
return None;
}
self.pos.x -= 1;
self.pos.z += 1;
if self.pos.x == 0 {
self.leg = 1;
}
}
1 => {
self.pos.x -= 1;
self.pos.z -= 1;
if self.pos.z == 0 {
self.leg = 2;
}
}
2 => {
self.pos.x += 1;
self.pos.z -= 1;
if self.pos.x == 0 {
self.leg = 3;
}
}
3 => {
self.pos.x += 1;
self.pos.z += 1;
if self.pos.z == 0 {
self.pos.x += 1;
self.leg = 0;
self.layer += 1;
if self.layer == self.max_distance as i32 {
return None;
}
}
}
_ => unreachable!(),
}
Some(self.start + self.pos)
}
}

View file

@ -7,6 +7,7 @@ mod bit_storage;
mod chunk_storage;
mod container;
pub mod entity;
pub mod iterators;
pub mod palette;
mod world;

View file

@ -12,6 +12,11 @@ pub enum PalettedContainerType {
#[derive(Clone, Debug)]
pub struct PalettedContainer {
pub bits_per_entry: u8,
/// This is usually a list of unique values that appear in the container so
/// they can be indexed by the bit storage.
///
/// Sometimes it doesn't contain anything if there's too many unique items
/// in the bit storage, though.
pub palette: Palette,
/// Compacted list of indices pointing to entry IDs in the Palette.
pub storage: BitStorage,
@ -37,7 +42,7 @@ impl PalettedContainer {
container_type: &'static PalettedContainerType,
) -> Result<Self, BufReadError> {
let bits_per_entry = u8::read_from(buf)?;
let palette_type = PaletteType::from_bits_and_type(bits_per_entry, container_type);
let palette_type = PaletteKind::from_bits_and_type(bits_per_entry, container_type);
let palette = palette_type.read(buf)?;
let size = container_type.size();
@ -57,15 +62,33 @@ impl PalettedContainer {
}
/// Calculates the index of the given coordinates.
pub fn get_index(&self, x: usize, y: usize, z: usize) -> usize {
pub fn index_from_coords(&self, x: usize, y: usize, z: usize) -> usize {
let size_bits = self.container_type.size_bits();
(((y << size_bits) | z) << size_bits) | x
}
pub fn coords_from_index(&self, index: usize) -> (usize, usize, usize) {
let size_bits = self.container_type.size_bits();
let mask = (1 << size_bits) - 1;
(
index & mask,
(index >> size_bits >> size_bits) & mask,
(index >> size_bits) & mask,
)
}
/// Returns the value at the given index.
///
/// # Panics
///
/// This function panics if the index is greater than or equal to the number
/// of things in the storage. (So for block states, it must be less than
/// 4096).
pub fn get_at_index(&self, index: usize) -> u32 {
// first get the pallete id
let paletted_value = self.storage.get(index);
// and then get the value from that id
self.palette.value_for(paletted_value as usize)
}
@ -73,14 +96,14 @@ impl PalettedContainer {
pub fn get(&self, x: usize, y: usize, z: usize) -> u32 {
// let paletted_value = self.storage.get(self.get_index(x, y, z));
// self.palette.value_for(paletted_value as usize)
self.get_at_index(self.get_index(x, y, z))
self.get_at_index(self.index_from_coords(x, y, z))
}
/// Sets the id at the given coordinates and return the previous id
pub fn get_and_set(&mut self, x: usize, y: usize, z: usize, value: u32) -> u32 {
let paletted_value = self.id_for(value);
self.storage
.get_and_set(self.get_index(x, y, z), paletted_value as u64) as u32
.get_and_set(self.index_from_coords(x, y, z), paletted_value as u64) as u32
}
/// Sets the id at the given index and return the previous id. You probably
@ -92,12 +115,12 @@ impl PalettedContainer {
/// Sets the id at the given coordinates and return the previous id
pub fn set(&mut self, x: usize, y: usize, z: usize, value: u32) {
self.set_at_index(self.get_index(x, y, z), value);
self.set_at_index(self.index_from_coords(x, y, z), value);
}
fn create_or_reuse_data(&self, bits_per_entry: u8) -> PalettedContainer {
let new_palette_type =
PaletteType::from_bits_and_type(bits_per_entry, &self.container_type);
PaletteKind::from_bits_and_type(bits_per_entry, &self.container_type);
// note for whoever is trying to optimize this: vanilla has this
// but it causes a stack overflow since it's not changing the bits per entry
// i don't know how to fix this properly so glhf
@ -188,13 +211,14 @@ impl McBufWritable for PalettedContainer {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PaletteType {
pub enum PaletteKind {
SingleValue,
Linear,
Hashmap,
Global,
}
/// A representation of the different types of chunk palettes Minecraft uses.
#[derive(Clone, Debug)]
pub enum Palette {
/// ID of the corresponding entry in its global palette
@ -211,13 +235,7 @@ impl Palette {
match self {
Palette::SingleValue(v) => *v,
Palette::Linear(v) => v[id],
Palette::Hashmap(v) => {
if id >= v.len() {
0
} else {
v[id]
}
}
Palette::Hashmap(v) => v.get(id).copied().unwrap_or_default(),
Palette::Global => id as u32,
}
}
@ -241,49 +259,49 @@ impl McBufWritable for Palette {
}
}
impl PaletteType {
impl PaletteKind {
pub fn from_bits_and_type(bits_per_entry: u8, container_type: &PalettedContainerType) -> Self {
match container_type {
PalettedContainerType::BlockStates => match bits_per_entry {
0 => PaletteType::SingleValue,
1..=4 => PaletteType::Linear,
5..=8 => PaletteType::Hashmap,
_ => PaletteType::Global,
0 => PaletteKind::SingleValue,
1..=4 => PaletteKind::Linear,
5..=8 => PaletteKind::Hashmap,
_ => PaletteKind::Global,
},
PalettedContainerType::Biomes => match bits_per_entry {
0 => PaletteType::SingleValue,
1..=3 => PaletteType::Linear,
_ => PaletteType::Global,
0 => PaletteKind::SingleValue,
1..=3 => PaletteKind::Linear,
_ => PaletteKind::Global,
},
}
}
pub fn read(&self, buf: &mut Cursor<&[u8]>) -> Result<Palette, BufReadError> {
Ok(match self {
PaletteType::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
PaletteType::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
PaletteType::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
PaletteType::Global => Palette::Global,
PaletteKind::SingleValue => Palette::SingleValue(u32::var_read_from(buf)?),
PaletteKind::Linear => Palette::Linear(Vec::<u32>::var_read_from(buf)?),
PaletteKind::Hashmap => Palette::Hashmap(Vec::<u32>::var_read_from(buf)?),
PaletteKind::Global => Palette::Global,
})
}
pub fn as_empty_palette(&self) -> Palette {
match self {
PaletteType::SingleValue => Palette::SingleValue(0),
PaletteType::Linear => Palette::Linear(Vec::new()),
PaletteType::Hashmap => Palette::Hashmap(Vec::new()),
PaletteType::Global => Palette::Global,
PaletteKind::SingleValue => Palette::SingleValue(0),
PaletteKind::Linear => Palette::Linear(Vec::new()),
PaletteKind::Hashmap => Palette::Hashmap(Vec::new()),
PaletteKind::Global => Palette::Global,
}
}
}
impl From<&Palette> for PaletteType {
impl From<&Palette> for PaletteKind {
fn from(palette: &Palette) -> Self {
match palette {
Palette::SingleValue(_) => PaletteType::SingleValue,
Palette::Linear(_) => PaletteType::Linear,
Palette::Hashmap(_) => PaletteType::Hashmap,
Palette::Global => PaletteType::Global,
Palette::SingleValue(_) => PaletteKind::SingleValue,
Palette::Linear(_) => PaletteKind::Linear,
Palette::Hashmap(_) => PaletteKind::Hashmap,
Palette::Global => PaletteKind::Global,
}
}
}
@ -313,14 +331,14 @@ mod tests {
assert_eq!(palette_container.bits_per_entry, 0);
assert_eq!(palette_container.get_at_index(0), 0);
assert_eq!(
PaletteType::from(&palette_container.palette),
PaletteType::SingleValue
PaletteKind::from(&palette_container.palette),
PaletteKind::SingleValue
);
palette_container.set_at_index(0, 1);
assert_eq!(palette_container.get_at_index(0), 1);
assert_eq!(
PaletteType::from(&palette_container.palette),
PaletteType::Linear
PaletteKind::from(&palette_container.palette),
PaletteKind::Linear
);
}
@ -359,4 +377,22 @@ mod tests {
palette_container.set_at_index(16, 16); // 5 bits
assert_eq!(palette_container.bits_per_entry, 5);
}
#[test]
fn test_coords_from_index() {
let palette_container =
PalettedContainer::new(&PalettedContainerType::BlockStates).unwrap();
for x in 0..15 {
for y in 0..15 {
for z in 0..15 {
assert_eq!(
palette_container
.coords_from_index(palette_container.index_from_coords(x, y, z)),
(x, y, z)
);
}
}
}
}
}

View file

@ -2,10 +2,13 @@ use crate::{
entity::{
EntityInfos, EntityUuid, LoadedBy, Local, MinecraftEntityId, PartialEntityInfos, WorldName,
},
iterators::ChunkIterator,
palette::Palette,
ChunkStorage, PartialChunkStorage, WorldContainer,
};
use azalea_core::ChunkPos;
use azalea_ecs::{
use azalea_block::{BlockState, BlockStates};
use azalea_core::{BlockPos, ChunkPos};
use bevy_ecs::{
entity::Entity,
query::{Changed, With, Without},
system::{Commands, Query, Res, ResMut},
@ -172,7 +175,7 @@ pub fn update_uuid_index(
/// A world where the chunks are stored as weak pointers. This is used for
/// shared worlds.
#[derive(Default, Debug)]
pub struct World {
pub struct Instance {
pub chunks: ChunkStorage,
/// An index of all the entities we know are in the chunks of the world
@ -182,11 +185,81 @@ pub struct World {
pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
}
impl World {
impl Instance {
/// Get an ECS [`Entity`] from a Minecraft entity ID.
pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> {
self.entity_by_id.get(entity_id).copied()
}
/// Find the coordinates of a block in the world.
///
/// Note that this is sorted by `x+y+z` and not `x^2+y^2+z^2`, for
/// optimization purposes.
pub fn find_block(
&self,
nearest_to: impl Into<BlockPos>,
block_states: &BlockStates,
) -> Option<BlockPos> {
// iterate over every chunk in a 3d spiral pattern
// and then check the palette for the block state
let nearest_to: BlockPos = nearest_to.into();
let start_chunk: ChunkPos = (&nearest_to).into();
let iter = ChunkIterator::new(start_chunk, 32);
for chunk_pos in iter {
let chunk = self.chunks.get(&chunk_pos).unwrap();
let mut nearest_found_pos: Option<BlockPos> = None;
let mut nearest_found_distance = 0;
for (section_index, section) in chunk.read().sections.iter().enumerate() {
let maybe_has_block = match &section.states.palette {
Palette::SingleValue(id) => block_states.contains(&BlockState { id: *id }),
Palette::Linear(ids) => ids
.iter()
.any(|&id| block_states.contains(&BlockState { id })),
Palette::Hashmap(ids) => ids
.iter()
.any(|&id| block_states.contains(&BlockState { id })),
Palette::Global => true,
};
if !maybe_has_block {
continue;
}
for i in 0..4096 {
let block_state = section.states.get_at_index(i);
let block_state = BlockState { id: block_state };
if block_states.contains(&block_state) {
let (section_x, section_y, section_z) = section.states.coords_from_index(i);
let (x, y, z) = (
chunk_pos.x * 16 + (section_x as i32),
self.chunks.min_y + (section_index * 16) as i32 + section_y as i32,
chunk_pos.z * 16 + (section_z as i32),
);
let this_block_pos = BlockPos { x, y, z };
let this_block_distance = (nearest_to - this_block_pos).length_manhattan();
// only update if it's closer
if nearest_found_pos.is_none()
|| this_block_distance < nearest_found_distance
{
nearest_found_pos = Some(this_block_pos);
nearest_found_distance = this_block_distance;
}
}
}
}
// if we found the position, return it
if nearest_found_pos.is_some() {
return nearest_found_pos;
}
}
None
}
}
impl Debug for PartialWorld {
@ -236,7 +309,7 @@ pub fn update_entity_by_id_index(
}
}
impl From<ChunkStorage> for World {
impl From<ChunkStorage> for Instance {
/// Make an empty world from this `ChunkStorage`. This is meant to be a
/// convenience function for tests.
fn from(chunks: ChunkStorage) -> Self {

View file

@ -8,31 +8,31 @@ version = "0.6.0"
[package.metadata.release]
pre-release-replacements = [
{file = "README.md", search = "`azalea = \"[a-z0-9\\.-]+\"`", replace = "`azalea = \"{{version}}\"`"},
{ file = "README.md", search = "`azalea = \"[a-z0-9\\.-]+\"`", replace = "`azalea = \"{{version}}\"`" },
]
[dependencies]
anyhow = "^1.0.65"
async-trait = "0.1.58"
azalea-block = {version = "0.6.0", path = "../azalea-block"}
azalea-chat = {version = "0.6.0", path = "../azalea-chat"}
azalea-client = {version = "0.6.0", path = "../azalea-client"}
azalea-core = {version = "0.6.0", path = "../azalea-core"}
azalea-ecs = {version = "0.6.0", path = "../azalea-ecs"}
azalea-physics = {version = "0.6.0", path = "../azalea-physics"}
azalea-protocol = {version = "0.6.0", path = "../azalea-protocol"}
azalea-registry = {version = "0.6.0", path = "../azalea-registry"}
azalea-world = {version = "0.6.0", path = "../azalea-world"}
bevy_tasks = "0.9.1"
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]}
azalea-block = { version = "0.6.0", path = "../azalea-block" }
azalea-chat = { version = "0.6.0", path = "../azalea-chat" }
azalea-client = { version = "0.6.0", path = "../azalea-client" }
azalea-core = { version = "0.6.0", path = "../azalea-core" }
azalea-physics = { version = "0.6.0", path = "../azalea-physics" }
azalea-protocol = { version = "0.6.0", path = "../azalea-protocol" }
azalea-registry = { version = "0.6.0", path = "../azalea-registry" }
azalea-world = { version = "0.6.0", path = "../azalea-world" }
bevy_app = "0.10.0"
bevy_ecs = "0.10.0"
bevy_tasks = "0.10.0"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
futures = "0.3.25"
futures-lite = "1.12.0"
log = "0.4.17"
nohash-hasher = "0.2.0"
num-traits = "0.2.15"
parking_lot = {version = "^0.12.1", features = ["deadlock_detection"]}
parking_lot = { version = "^0.12.1", features = ["deadlock_detection"] }
priority-queue = "1.3.0"
thiserror = "^1.0.37"
tokio = "^1.24.2"
uuid = "1.2.2"

View file

@ -146,6 +146,25 @@ async fn handle(mut bot: Client, event: Event, _state: State) -> anyhow::Result<
let inventory = bot.query::<&InventoryComponent>(&mut ecs);
println!("inventory: {:?}", inventory.menu());
}
"findblock" => {
let target_pos = bot.world().read().find_block(
bot.component::<Position>(),
&azalea_registry::Block::DiamondBlock.into(),
);
bot.chat(&format!("target_pos: {target_pos:?}",));
}
"gotoblock" => {
let target_pos = bot.world().read().find_block(
bot.component::<Position>(),
&azalea_registry::Block::DiamondBlock.into(),
);
if let Some(target_pos) = target_pos {
// +1 to stand on top of the block
bot.goto(BlockPosGoal::from(target_pos.up(1)));
} else {
bot.chat("no diamond block found");
}
}
_ => {}
}
}

View file

@ -0,0 +1 @@
These examples don't work yet and were only written to help design APIs. They will work in the future (probably with minor changes).

View file

@ -38,7 +38,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
bot.goto(pathfinder::Goals::NearXZ(5, azalea::BlockXZ(0, 0)))
.await;
let chest = bot
.open_container(&bot.world().find_one_block(|b| b.id == "minecraft:chest"))
.open_container(&bot.world().find_block(azalea_registry::Block::Chest))
.await
.unwrap();
bot.take_amount_from_container(&chest, 5, |i| i.id == "#minecraft:planks")
@ -47,8 +47,7 @@ async fn handle(bot: Client, event: Event, state: State) -> anyhow::Result<()> {
let crafting_table = bot
.open_crafting_table(
&bot.world
.find_one_block(|b| b.id == "minecraft:crafting_table"),
&bot.world.find_block(azalea_registry::Block::CraftingTable),
)
.await
.unwrap();

2
azalea/examples/pvp.rs → azalea/examples/todo/pvp.rs Executable file → Normal file
View file

@ -1,9 +1,9 @@
use std::time::Duration;
use azalea::ecs::query::With;
use azalea::entity::metadata::Player;
use azalea::{pathfinder, Account, Client, Event, GameProfileComponent};
use azalea::{prelude::*, swarm::prelude::*};
use azalea_ecs::query::With;
#[tokio::main]
async fn main() {

View file

@ -1,14 +1,14 @@
use azalea_core::Vec3;
use azalea_ecs::{
app::{App, Plugin, PluginGroup, PluginGroupBuilder},
use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin, PluginGroup, PluginGroupBuilder};
use crate::ecs::{
component::Component,
entity::Entity,
event::EventReader,
query::{With, Without},
schedule::IntoSystemDescriptor,
schedule::IntoSystemConfig,
system::{Commands, Query},
AppTickExt,
};
use azalea_core::Vec3;
use azalea_physics::{force_jump_listener, PhysicsSet};
use azalea_world::entity::{metadata::Player, set_rotation, Jumping, Local, Physics, Position};
use std::f64::consts::PI;
@ -20,14 +20,14 @@ impl Plugin for BotPlugin {
fn build(&self, app: &mut App) {
app.add_event::<LookAtEvent>()
.add_event::<JumpEvent>()
.add_system(insert_bot)
.add_system(
look_at_listener
.before("force_jump_listener")
.before(azalea_world::entity::update_bounding_box),
)
.add_system(jump_listener.label("jump_listener"))
.add_tick_system(stop_jumping.after("ai_step"));
.add_systems((
insert_bot,
look_at_listener.before(force_jump_listener),
jump_listener,
stop_jumping
.in_schedule(CoreSchedule::FixedUpdate)
.after(PhysicsSet),
));
}
}

View file

@ -6,18 +6,15 @@ pub mod pathfinder;
pub mod prelude;
pub mod swarm;
use app::{App, Plugin, PluginGroup};
pub use azalea_block as blocks;
pub use azalea_client::*;
pub use azalea_core::{BlockPos, Vec3};
use azalea_ecs::{
app::{App, Plugin},
component::Component,
};
pub use azalea_protocol as protocol;
pub use azalea_registry::EntityKind;
pub use azalea_world::{entity, World};
pub use azalea_world::{entity, Instance};
use bot::DefaultBotPlugins;
use ecs::app::PluginGroup;
use ecs::component::Component;
use futures::Future;
use protocol::{
resolver::{self, ResolverError},
@ -26,7 +23,10 @@ use protocol::{
use thiserror::Error;
use tokio::sync::mpsc;
pub type HandleFn<Fut, S> = fn(Client, Event, S) -> Fut;
pub use bevy_app as app;
pub use bevy_ecs as ecs;
pub type HandleFn<Fut, S> = fn(Client, azalea_client::Event, S) -> Fut;
#[derive(Error, Debug)]
pub enum StartError {
@ -142,6 +142,7 @@ where
// An event that causes the schedule to run. This is only used internally.
let (run_schedule_sender, run_schedule_receiver) = mpsc::unbounded_channel();
let ecs_lock = start_ecs(self.app, run_schedule_receiver, run_schedule_sender.clone());
let (bot, mut rx) = Client::start_client(

View file

@ -4,18 +4,18 @@ mod mtdstarlite;
use crate::bot::{JumpEvent, LookAtEvent};
use crate::{SprintDirection, WalkDirection};
use azalea_client::{StartSprintEvent, StartWalkEvent};
use azalea_core::{BlockPos, CardinalDirection};
use azalea_ecs::{
app::{App, Plugin},
use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin};
use crate::ecs::{
component::Component,
entity::Entity,
event::{EventReader, EventWriter},
query::{With, Without},
schedule::IntoSystemDescriptor,
schedule::IntoSystemConfig,
system::{Commands, Query, Res},
AppTickExt,
};
use azalea_client::{StartSprintEvent, StartWalkEvent};
use azalea_core::{BlockPos, CardinalDirection};
use azalea_physics::PhysicsSet;
use azalea_world::entity::metadata::Player;
use azalea_world::entity::Local;
use azalea_world::{
@ -36,7 +36,13 @@ impl Plugin for PathfinderPlugin {
fn build(&self, app: &mut App) {
app.add_event::<GotoEvent>()
.add_event::<PathFoundEvent>()
.add_tick_system(tick_execute_path.before("ai_step"))
.add_system(
// Adding `.in_schedule(CoreSchedule::FixedUpdate)` makes a system run every
// Minecraft tick (every 50 milliseconds).
tick_execute_path
.in_schedule(CoreSchedule::FixedUpdate)
.before(PhysicsSet),
)
.add_system(goto_listener)
.add_system(add_default_pathfinder)
.add_system(handle_tasks.before(path_found_listener))

View file

@ -1,10 +1,10 @@
use super::{Node, VerticalVel};
use azalea_core::{BlockPos, CardinalDirection};
use azalea_physics::collision::{self, BlockWithShape};
use azalea_world::World;
use azalea_world::Instance;
/// whether this block is passable
fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
fn is_block_passable(pos: &BlockPos, world: &Instance) -> bool {
if let Some(block) = world.chunks.get_block_state(pos) {
block.shape() == &collision::empty_shape()
} else {
@ -13,7 +13,7 @@ fn is_block_passable(pos: &BlockPos, world: &World) -> bool {
}
/// whether this block has a solid hitbox (i.e. we can stand on it)
fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
fn is_block_solid(pos: &BlockPos, world: &Instance) -> bool {
if let Some(block) = world.chunks.get_block_state(pos) {
block.shape() == &collision::block_shape()
} else {
@ -22,14 +22,14 @@ fn is_block_solid(pos: &BlockPos, world: &World) -> bool {
}
/// Whether this block and the block above are passable
fn is_passable(pos: &BlockPos, world: &World) -> bool {
fn is_passable(pos: &BlockPos, world: &Instance) -> bool {
is_block_passable(pos, world) && is_block_passable(&pos.up(1), world)
}
/// Whether we can stand in this position. Checks if the block below is solid,
/// and that the two blocks above that are passable.
fn is_standable(pos: &BlockPos, world: &World) -> bool {
fn is_standable(pos: &BlockPos, world: &Instance) -> bool {
is_block_solid(&pos.down(1), world) && is_passable(pos, world)
}
@ -37,7 +37,7 @@ const JUMP_COST: f32 = 0.5;
const WALK_ONE_BLOCK_COST: f32 = 1.0;
pub trait Move: Send + Sync {
fn cost(&self, world: &World, node: &Node) -> f32;
fn cost(&self, world: &Instance, node: &Node) -> f32;
/// Returns by how much the entity's position should be changed when this
/// move is executed.
fn offset(&self) -> BlockPos;
@ -51,7 +51,7 @@ pub trait Move: Send + Sync {
pub struct ForwardMove(pub CardinalDirection);
impl Move for ForwardMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &Instance, node: &Node) -> f32 {
if is_standable(&(node.pos + self.offset()), world)
&& node.vertical_vel == VerticalVel::None
{
@ -67,7 +67,7 @@ impl Move for ForwardMove {
pub struct AscendMove(pub CardinalDirection);
impl Move for AscendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &Instance, node: &Node) -> f32 {
if node.vertical_vel == VerticalVel::None
&& is_block_passable(&node.pos.up(2), world)
&& is_standable(&(node.pos + self.offset()), world)
@ -89,7 +89,7 @@ impl Move for AscendMove {
}
pub struct DescendMove(pub CardinalDirection);
impl Move for DescendMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &Instance, node: &Node) -> f32 {
// check whether 3 blocks vertically forward are passable
if node.vertical_vel == VerticalVel::None
&& is_standable(&(node.pos + self.offset()), world)
@ -112,7 +112,7 @@ impl Move for DescendMove {
}
pub struct DiagonalMove(pub CardinalDirection);
impl Move for DiagonalMove {
fn cost(&self, world: &World, node: &Node) -> f32 {
fn cost(&self, world: &Instance, node: &Node) -> f32 {
if node.vertical_vel != VerticalVel::None {
return f32::INFINITY;
}

View file

@ -3,4 +3,6 @@
pub use crate::{bot::BotClientExt, pathfinder::PathfinderClientExt, ClientBuilder};
pub use azalea_client::{Account, Client, Event};
pub use azalea_ecs::{component::Component, system::Resource};
// this is necessary to make the macros that reference bevy_ecs work
pub use crate::ecs as bevy_ecs;
pub use crate::ecs::{component::Component, system::Resource};

View file

@ -13,14 +13,14 @@
// in Swarm that's set to the smallest index of all the bots, and we remove all
// messages from the queue that are before that index.
use azalea_client::chat::{ChatPacket, ChatReceivedEvent};
use azalea_ecs::{
app::{App, Plugin},
use crate::ecs::{
component::Component,
event::{EventReader, EventWriter},
schedule::IntoSystemDescriptor,
schedule::IntoSystemConfigs,
system::{Commands, Query, Res, ResMut, Resource},
};
use azalea_client::chat::{ChatPacket, ChatReceivedEvent};
use bevy_app::{App, Plugin};
use std::collections::VecDeque;
use super::{Swarm, SwarmEvent};
@ -30,8 +30,7 @@ pub struct SwarmChatPlugin;
impl Plugin for SwarmChatPlugin {
fn build(&self, app: &mut App) {
app.add_event::<NewChatMessageEvent>()
.add_system(chat_listener.label("chat_listener"))
.add_system(update_min_index_and_shrink_queue.after("chat_listener"))
.add_systems((chat_listener, update_min_index_and_shrink_queue).chain())
.insert_resource(GlobalChatState {
chat_queue: VecDeque::new(),
chat_min_index: 0,
@ -151,7 +150,7 @@ fn update_min_index_and_shrink_queue(
#[cfg(test)]
mod tests {
use azalea_ecs::{ecs::Ecs, event::Events, system::SystemState};
use bevy_ecs::{event::Events, prelude::World, system::SystemState};
use super::*;
@ -161,8 +160,7 @@ mod tests {
// event mangement in drain_events
app.init_resource::<Events<ChatReceivedEvent>>()
.init_resource::<Events<NewChatMessageEvent>>()
.add_system(chat_listener.label("chat_listener"))
.add_system(update_min_index_and_shrink_queue.after("chat_listener"))
.add_systems((chat_listener, update_min_index_and_shrink_queue).chain())
.insert_resource(GlobalChatState {
chat_queue: VecDeque::new(),
chat_min_index: 0,
@ -170,7 +168,7 @@ mod tests {
app
}
fn drain_events(ecs: &mut Ecs) -> Vec<ChatPacket> {
fn drain_events(ecs: &mut World) -> Vec<ChatPacket> {
let mut system_state: SystemState<ResMut<Events<NewChatMessageEvent>>> =
SystemState::new(ecs);
let mut events = system_state.get_mut(ecs);

View file

@ -1,11 +1,7 @@
use azalea_client::LocalPlayer;
use azalea_ecs::{
app::{App, Plugin},
event::EventWriter,
query::With,
system::{Query, ResMut, Resource},
};
use azalea_world::entity::MinecraftEntityId;
use bevy_app::{App, Plugin};
use bevy_ecs::prelude::*;
use derive_more::{Deref, DerefMut};
pub struct SwarmPlugin;

View file

@ -6,19 +6,14 @@ pub mod prelude;
use crate::{bot::DefaultBotPlugins, HandleFn};
use azalea_client::{chat::ChatPacket, init_ecs_app, start_ecs, Account, Client, Event, JoinError};
use azalea_ecs::{
app::{App, Plugin, PluginGroup, PluginGroupBuilder},
component::Component,
ecs::Ecs,
entity::Entity,
system::Resource,
};
use azalea_protocol::{
connect::ConnectionError,
resolver::{self, ResolverError},
ServerAddress,
};
use azalea_world::WorldContainer;
use bevy_app::{App, Plugin, PluginGroup, PluginGroupBuilder};
use bevy_ecs::{component::Component, entity::Entity, system::Resource, world::World};
use futures::future::join_all;
use log::error;
use parking_lot::{Mutex, RwLock};
@ -35,7 +30,7 @@ use tokio::sync::mpsc;
/// It's used to make the [`Swarm::add`] function work.
#[derive(Clone, Resource)]
pub struct Swarm {
pub ecs_lock: Arc<Mutex<Ecs>>,
pub ecs_lock: Arc<Mutex<World>>,
bots: Arc<Mutex<HashMap<Entity, Client>>>,

View file

@ -46,7 +46,7 @@ use super::{EntityDataItem, EntityDataValue, OptionalUnsignedInt, Pose, Rotation
use azalea_block::BlockState;
use azalea_chat::FormattedText;
use azalea_core::{BlockPos, Direction, Particle, Slot};
use azalea_ecs::{bundle::Bundle, component::Component};
use bevy_ecs::{bundle::Bundle, component::Component};
use derive_more::{Deref, DerefMut};
use thiserror::Error;
use uuid::Uuid;
@ -183,7 +183,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# impl Allay {
# pub fn apply_metadata(
# entity: &mut azalea_ecs::system::EntityCommands,
# entity: &mut bevy_ecs::system::EntityCommands,
# d: EntityDataItem,
# ) -> Result<(), UpdateMetadataError> {
# match d.index {
@ -196,7 +196,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# }
code.append(f'impl {struct_name} {{')
code.append(
f' pub fn apply_metadata(entity: &mut azalea_ecs::system::EntityCommands, d: EntityDataItem) -> Result<(), UpdateMetadataError> {{')
f' pub fn apply_metadata(entity: &mut bevy_ecs::system::EntityCommands, d: EntityDataItem) -> Result<(), UpdateMetadataError> {{')
code.append(f' match d.index {{')
parent_last_index = -1
@ -400,7 +400,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# and now make the main apply_metadata
# pub fn apply_metadata(
# entity: &mut azalea_ecs::system::EntityCommands,
# entity: &mut bevy_ecs::system::EntityCommands,
# items: Vec<EntityDataItem>,
# ) -> Result<(), UpdateMetadataError> {
# if entity.contains::<Allay>() {
@ -414,7 +414,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# }
code.append(
f'''pub fn apply_metadata(
entity: &mut azalea_ecs::system::EntityCommands,
entity: &mut bevy_ecs::system::EntityCommands,
entity_kind: azalea_registry::EntityKind,
items: Vec<EntityDataItem>,
) -> Result<(), UpdateMetadataError> {{
@ -436,7 +436,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
code.append('}')
code.append('')
# pub fn apply_default_metadata(entity: &mut azalea_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {
# pub fn apply_default_metadata(entity: &mut bevy_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {
# match kind {
# azalea_registry::EntityKind::AreaEffectCloud => {
# entity.insert(AreaEffectCloudMetadataBundle::default());
@ -444,7 +444,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# }
# }
code.append(
'pub fn apply_default_metadata(entity: &mut azalea_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {')
'pub fn apply_default_metadata(entity: &mut bevy_ecs::system::EntityCommands, kind: azalea_registry::EntityKind) {')
code.append(' match kind {')
for entity_id in burger_entity_data:
if entity_id.startswith('~'):