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

Bevy 0.10 (#79)

* replace 0.9.1 with 0.10.0

* start migrating to bevy .10

* well it compiles

* doesn't immediately panic

* remove unused imports

* fmt

* delete azalea-ecs

* make RelativeEntityUpdate an EntityCommand

* fix a doc test

* explain what FixedUpdate does
This commit is contained in:
mat 2023-03-07 14:14:36 -06:00 committed by GitHub
parent bf4ff51789
commit 719379a8a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
51 changed files with 632 additions and 2261 deletions

215
Cargo.lock generated
View file

@ -79,7 +79,7 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833"
dependencies = [ dependencies = [
"concurrent-queue 2.1.0", "concurrent-queue",
"event-listener", "event-listener",
"futures-core", "futures-core",
] ]
@ -105,7 +105,7 @@ checksum = "17adb73da160dfb475c183343c8cccd80721ea5a605d3eb57125f0a7b7a92d0b"
dependencies = [ dependencies = [
"async-lock", "async-lock",
"async-task", "async-task",
"concurrent-queue 2.1.0", "concurrent-queue",
"fastrand", "fastrand",
"futures-lite", "futures-lite",
"slab", "slab",
@ -176,11 +176,12 @@ dependencies = [
"azalea-chat", "azalea-chat",
"azalea-client", "azalea-client",
"azalea-core", "azalea-core",
"azalea-ecs",
"azalea-physics", "azalea-physics",
"azalea-protocol", "azalea-protocol",
"azalea-registry", "azalea-registry",
"azalea-world", "azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_tasks", "bevy_tasks",
"derive_more", "derive_more",
"futures", "futures",
@ -284,11 +285,12 @@ dependencies = [
"azalea-chat", "azalea-chat",
"azalea-core", "azalea-core",
"azalea-crypto", "azalea-crypto",
"azalea-ecs",
"azalea-physics", "azalea-physics",
"azalea-protocol", "azalea-protocol",
"azalea-registry", "azalea-registry",
"azalea-world", "azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_log", "bevy_log",
"bevy_tasks", "bevy_tasks",
"bevy_time", "bevy_time",
@ -331,26 +333,6 @@ dependencies = [
"uuid", "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.0",
]
[[package]] [[package]]
name = "azalea-language" name = "azalea-language"
version = "0.6.0" version = "0.6.0"
@ -381,9 +363,11 @@ version = "0.6.0"
dependencies = [ dependencies = [
"azalea-block", "azalea-block",
"azalea-core", "azalea-core",
"azalea-ecs",
"azalea-registry", "azalea-registry",
"azalea-world", "azalea-world",
"bevy_app",
"bevy_ecs",
"bevy_time",
"once_cell", "once_cell",
"parking_lot", "parking_lot",
"uuid", "uuid",
@ -461,9 +445,10 @@ dependencies = [
"azalea-buf", "azalea-buf",
"azalea-chat", "azalea-chat",
"azalea-core", "azalea-core",
"azalea-ecs",
"azalea-nbt", "azalea-nbt",
"azalea-registry", "azalea-registry",
"bevy_app",
"bevy_ecs",
"derive_more", "derive_more",
"enum-as-inner", "enum-as-inner",
"log", "log",
@ -497,9 +482,9 @@ checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]] [[package]]
name = "bevy_app" name = "bevy_app"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "536e4d0018347478545ed8b6cb6e57b9279ee984868e81b7c0e78e0fb3222e42" checksum = "960c6e444dc6a25dd51a2196f04872ae9e2e876802b66c391104849ec9225e38"
dependencies = [ dependencies = [
"bevy_derive", "bevy_derive",
"bevy_ecs", "bevy_ecs",
@ -512,9 +497,9 @@ dependencies = [
[[package]] [[package]]
name = "bevy_derive" name = "bevy_derive"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7baf73c58d41c353c6fd08e6764a2e7420c9f19e8227b391c50981db6d0282a6" checksum = "cdf11701c01bf4dc7a3fac9f4547f3643d3db4cc1682af40c8c86e2f8734b617"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"quote", "quote",
@ -523,9 +508,9 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ecs" name = "bevy_ecs"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c071d7c6bc9801253485e05d0c257284150de755391902746837ba21c0cf74" checksum = "fdc5b19451128091e8507c9247888359ca0bfa895e7f6ca749ccc55c5463bef6"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"bevy_ecs_macros", "bevy_ecs_macros",
@ -536,16 +521,16 @@ dependencies = [
"downcast-rs", "downcast-rs",
"event-listener", "event-listener",
"fixedbitset", "fixedbitset",
"fxhash", "rustc-hash",
"serde", "serde",
"thread_local", "thread_local",
] ]
[[package]] [[package]]
name = "bevy_ecs_macros" name = "bevy_ecs_macros"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c15bd45438eeb681ad74f2d205bb07a5699f98f9524462a30ec764afab2742ce" checksum = "b1e79757319533bde006a4f30c268223ec6426371297182925932075ccfdae30"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"proc-macro2", "proc-macro2",
@ -555,9 +540,9 @@ dependencies = [
[[package]] [[package]]
name = "bevy_log" name = "bevy_log"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c480bac54cf4ae76edc3ae9ae3fa7c5e1b385e7f2111ef5ec3fd00cf3a7998b" checksum = "47dcb09ec71145c80d88a84181cc1449d30f23c571bdd58c59c10eece82dfaa5"
dependencies = [ dependencies = [
"android_log-sys", "android_log-sys",
"bevy_app", "bevy_app",
@ -571,20 +556,20 @@ dependencies = [
[[package]] [[package]]
name = "bevy_macro_utils" name = "bevy_macro_utils"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "022bb69196deeea691b6997414af85bbd7f2b34a8914c4aa7a7ff4dfa44f7677" checksum = "f24ca3363292f1435641fbafd5c24ce362137dd7d69bee56dcaaa2bc1d512ffe"
dependencies = [ dependencies = [
"quote", "quote",
"syn", "syn",
"toml 0.5.11", "toml_edit",
] ]
[[package]] [[package]]
name = "bevy_math" name = "bevy_math"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434c77ab766c806ed9062ef8a7285b3b02b47df51f188d4496199c3ac062eaf" checksum = "5e45e46c2ac0a92db3ae622f2ed690928fe2612e7c9470a46d0ed4c2c77e2e95"
dependencies = [ dependencies = [
"glam", "glam",
"serde", "serde",
@ -592,15 +577,15 @@ dependencies = [
[[package]] [[package]]
name = "bevy_ptr" name = "bevy_ptr"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ec44f7655039546bc5d34d98de877083473f3e9b2b81d560c528d6d74d3eff4" checksum = "a96c24da064370917b92c2a84527e6a73b620c50ac5ef8b1af8c04ccf5256a7c"
[[package]] [[package]]
name = "bevy_reflect" name = "bevy_reflect"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6deae303a7f69dc243b2fa35b5e193cc920229f448942080c8eb2dbd9de6d37a" checksum = "ab880e0eed9df5c99ce1a2f89edc11cdef1bc78413719b29e9ad7e3bc27f4c20"
dependencies = [ dependencies = [
"bevy_math", "bevy_math",
"bevy_ptr", "bevy_ptr",
@ -618,9 +603,9 @@ dependencies = [
[[package]] [[package]]
name = "bevy_reflect_derive" name = "bevy_reflect_derive"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2bf4cb9cd5acb4193f890f36cb63679f1502e2de025e66a63b194b8b133d018" checksum = "3b361b8671bdffe93978270dd770b03b48560c3127fdf9003f98111fb806bb11"
dependencies = [ dependencies = [
"bevy_macro_utils", "bevy_macro_utils",
"bit-set", "bit-set",
@ -632,14 +617,14 @@ dependencies = [
[[package]] [[package]]
name = "bevy_tasks" name = "bevy_tasks"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "680b16b53df9c9f24681dd95f4d772d83760bd19adf8bca00f358a3aad997853" checksum = "3e368e4177fe70d695d5cb67fb7480fa262de79948d9b883a21788b9abf5a85a"
dependencies = [ dependencies = [
"async-channel", "async-channel",
"async-executor", "async-executor",
"async-task", "async-task",
"concurrent-queue 1.2.4", "concurrent-queue",
"futures-lite", "futures-lite",
"once_cell", "once_cell",
"wasm-bindgen-futures", "wasm-bindgen-futures",
@ -647,31 +632,46 @@ dependencies = [
[[package]] [[package]]
name = "bevy_time" name = "bevy_time"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5c38a6d3ea929c7f81e6adf5a6c62cf7e8c40f5106c2174d6057e9d8ea624d" checksum = "d2f2863cfc08fa38909e047a1bbc2dd71d0836057ed0840c69ace9dff3e0c298"
dependencies = [ dependencies = [
"bevy_app", "bevy_app",
"bevy_ecs", "bevy_ecs",
"bevy_reflect", "bevy_reflect",
"bevy_utils", "bevy_utils",
"crossbeam-channel", "crossbeam-channel",
"thiserror",
] ]
[[package]] [[package]]
name = "bevy_utils" name = "bevy_utils"
version = "0.9.1" version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16750aae52cd35bd7b60eb61cee883420b250e11b4a290b8d44b2b2941795739" checksum = "04d90ce493910ad9af3b4220ea6864c7d1472761086a98230ecac59c8d547e95"
dependencies = [ dependencies = [
"ahash 0.7.6", "ahash 0.7.6",
"bevy_utils_proc_macros",
"getrandom", "getrandom",
"hashbrown", "hashbrown",
"instant", "instant",
"petgraph",
"thiserror",
"tracing", "tracing",
"uuid", "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]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.3" version = "0.5.3"
@ -738,12 +738,6 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
[[package]]
name = "cache-padded"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c"
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.3.0" version = "0.3.0"
@ -802,15 +796,6 @@ dependencies = [
"unicode-width", "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]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.1.0" version = "2.1.0"
@ -1199,15 +1184,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.6" version = "0.14.6"
@ -1239,9 +1215,9 @@ checksum = "221996f774192f0f718773def8201c4ae31f02616a54ccfc2d358bb0e5cefdec"
[[package]] [[package]]
name = "glam" name = "glam"
version = "0.22.0" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12f597d56c1bd55a811a1be189459e8fad2bbc272616375602443bdfb37fa774" checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"serde", "serde",
@ -1580,15 +1556,6 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
[[package]]
name = "nom8"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae01545c9c7fc4486ab7debaf2aad7003ac19431791868fb2e8066df97fad2f8"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "nu-ansi-term" name = "nu-ansi-term"
version = "0.46.0" version = "0.46.0"
@ -1829,9 +1796,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.50" version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -2003,6 +1970,12 @@ version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]] [[package]]
name = "rustc_version" name = "rustc_version"
version = "0.4.0" version = "0.4.0"
@ -2111,15 +2084,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_spanned"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c68e921cef53841b8925c2abadd27c9b891d9613bdc43d6b823062866df38e8"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"
@ -2209,9 +2173,9 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.107" version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2357,47 +2321,21 @@ dependencies = [
"tracing", "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.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f560bc7fb3eb31f5eee1340c68a2160cad39605b7b9c9ec32045ddbdee13b85"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]] [[package]]
name = "toml_datetime" name = "toml_datetime"
version = "0.6.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "886f31a9b85b6182cabd4d8b07df3b451afcc216563748201490940d2a28ed36" checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.19.0" version = "0.19.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233d8716cdc5d20ec88a18a839edaf545edc71efa4a5ff700ef4a102c26cd8fa" checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"nom8",
"serde",
"serde_spanned",
"toml_datetime", "toml_datetime",
"winnow",
] ]
[[package]] [[package]]
@ -2822,6 +2760,15 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.10.1" version = "0.10.1"

View file

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

View file

@ -16,14 +16,15 @@ azalea-block = { path = "../azalea-block", version = "0.6.0" }
azalea-chat = { path = "../azalea-chat", version = "0.6.0" } azalea-chat = { path = "../azalea-chat", version = "0.6.0" }
azalea-core = { path = "../azalea-core", version = "0.6.0" } azalea-core = { path = "../azalea-core", version = "0.6.0" }
azalea-crypto = { path = "../azalea-crypto", 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-physics = { path = "../azalea-physics", version = "0.6.0" }
azalea-protocol = { path = "../azalea-protocol", version = "0.6.0" } azalea-protocol = { path = "../azalea-protocol", version = "0.6.0" }
azalea-registry = { path = "../azalea-registry", version = "0.6.0" } azalea-registry = { path = "../azalea-registry", version = "0.6.0" }
azalea-world = { path = "../azalea-world", version = "0.6.0" } azalea-world = { path = "../azalea-world", version = "0.6.0" }
bevy_log = "0.9.1" bevy_app = "0.10.0"
bevy_tasks = "0.9.1" bevy_ecs = "0.10.0"
bevy_time = "0.9.1" bevy_log = "0.10.0"
bevy_tasks = "0.10.0"
bevy_time = "0.10.0"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
futures = "0.3.25" futures = "0.3.25"
log = "0.4.17" log = "0.4.17"

View file

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

View file

@ -6,25 +6,16 @@ use crate::{
death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent, death_event, handle_send_packet_event, update_in_loaded_chunk, GameProfileComponent,
LocalPlayer, PhysicsState, SendPacketEvent, LocalPlayer, PhysicsState, SendPacketEvent,
}, },
movement::{local_player_ai_step, send_position, sprint_listener, walk_listener}, movement::PlayerMovePlugin,
packet_handling::{self, PacketHandlerPlugin, PacketReceiver}, packet_handling::{self, PacketHandlerPlugin, PacketReceiver},
player::retroactively_add_game_profile_component, player::retroactively_add_game_profile_component,
task_pool::TaskPoolPlugin, task_pool::TaskPoolPlugin,
Account, PlayerInfo, StartSprintEvent, StartWalkEvent, Account, PlayerInfo,
}; };
use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError}; use azalea_auth::{game_profile::GameProfile, sessionserver::ClientSessionServerError};
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
use azalea_ecs::{ use azalea_physics::{PhysicsPlugin, PhysicsSet};
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_protocol::{ use azalea_protocol::{
connect::{Connection, ConnectionError}, connect::{Connection, ConnectionError},
packets::{ packets::{
@ -46,13 +37,23 @@ use azalea_protocol::{
resolver, ServerAddress, resolver, ServerAddress,
}; };
use azalea_world::{ use azalea_world::{
entity::{EntityPlugin, Local, WorldName}, entity::{EntityPlugin, EntityUpdateSet, Local, WorldName},
PartialWorld, World, WorldContainer, 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_log::LogPlugin;
use bevy_time::{prelude::FixedTime, TimePlugin};
use log::{debug, error}; use log::{debug, error};
use parking_lot::{Mutex, RwLock}; 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 thiserror::Error;
use tokio::{sync::mpsc, time}; use tokio::{sync::mpsc, time};
use uuid::Uuid; use uuid::Uuid;
@ -86,7 +87,7 @@ pub struct Client {
/// The entity component system. You probably don't need to access this /// 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 /// directly. Note that if you're using a shared world (i.e. a swarm), this
/// will contain all entities in all worlds. /// 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. /// Use this to force the client to run the schedule outside of a tick.
pub run_schedule_sender: mpsc::UnboundedSender<()>, pub run_schedule_sender: mpsc::UnboundedSender<()>,
@ -120,7 +121,7 @@ impl Client {
pub fn new( pub fn new(
profile: GameProfile, profile: GameProfile,
entity: Entity, entity: Entity,
ecs: Arc<Mutex<Ecs>>, ecs: Arc<Mutex<World>>,
run_schedule_sender: mpsc::UnboundedSender<()>, run_schedule_sender: mpsc::UnboundedSender<()>,
) -> Self { ) -> Self {
Self { Self {
@ -180,7 +181,7 @@ impl Client {
/// Create a [`Client`] when you already have the ECS made with /// Create a [`Client`] when you already have the ECS made with
/// [`start_ecs`]. You'd usually want to use [`Self::join`] instead. /// [`start_ecs`]. You'd usually want to use [`Self::join`] instead.
pub async fn start_client( pub async fn start_client(
ecs_lock: Arc<Mutex<Ecs>>, ecs_lock: Arc<Mutex<World>>,
account: &Account, account: &Account,
address: &ServerAddress, address: &ServerAddress,
resolved_address: &SocketAddr, resolved_address: &SocketAddr,
@ -226,7 +227,7 @@ impl Client {
packet_writer_sender, packet_writer_sender,
// default to an empty world, it'll be set correctly later when we // default to an empty world, it'll be set correctly later when we
// get the login packet // get the login packet
Arc::new(RwLock::new(World::default())), Arc::new(RwLock::new(Instance::default())),
read_packets_task, read_packets_task,
write_packets_task, write_packets_task,
); );
@ -382,13 +383,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) self.query::<&LocalPlayer>(ecs)
} }
pub fn local_player_mut<'a>( pub fn local_player_mut<'a>(
&'a self, &'a self,
ecs: &'a mut Ecs, ecs: &'a mut World,
) -> azalea_ecs::ecs::Mut<'a, LocalPlayer> { ) -> bevy_ecs::world::Mut<'a, LocalPlayer> {
self.query::<&mut LocalPlayer>(ecs) self.query::<&mut LocalPlayer>(ecs)
} }
@ -416,7 +417,7 @@ impl Client {
/// client, then it'll be the same as the world the client has loaded. /// 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 /// If the client using a shared world, then the shared world will be a
/// superset of the client's world. /// 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 world_name = self.component::<WorldName>();
let ecs = self.ecs.lock(); let ecs = self.ecs.lock();
let world_container = ecs.resource::<WorldContainer>(); let world_container = ecs.resource::<WorldContainer>();
@ -493,33 +494,20 @@ pub struct JoinedClientBundle {
pub struct AzaleaPlugin; pub struct AzaleaPlugin;
impl Plugin for AzaleaPlugin { impl Plugin for AzaleaPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<StartWalkEvent>() // Minecraft ticks happen every 50ms
.add_event::<StartSprintEvent>(); app.insert_resource(FixedTime::new(Duration::from_millis(50)));
app.add_tick_system_set( app.add_system(
SystemSet::new() update_in_loaded_chunk
.with_system(send_position.after("ai_step")) .after(PhysicsSet)
.with_system(update_in_loaded_chunk.before(send_position).after("travel")) .after(handle_send_packet_event),
.with_system(
local_player_ai_step
.before(azalea_physics::ai_step)
.label("ai_step"),
),
); );
// fire the Death event when the player dies. // fire the Death event when the player dies.
app.add_system(death_event); 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 // 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>() app.add_event::<SendPacketEvent>()
.add_system(handle_send_packet_event); .add_system(handle_send_packet_event);
@ -544,7 +532,12 @@ pub fn init_ecs_app() -> App {
let mut app = App::new(); 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.add_plugins(DefaultPlugins);
app app
@ -554,17 +547,19 @@ pub fn init_ecs_app() -> App {
/// first. /// first.
#[doc(hidden)] #[doc(hidden)]
pub fn start_ecs( pub fn start_ecs(
app: App, mut app: App,
run_schedule_receiver: mpsc::UnboundedReceiver<()>, run_schedule_receiver: mpsc::UnboundedReceiver<()>,
run_schedule_sender: mpsc::UnboundedSender<()>, 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 // all resources should have been added by now so we can take the ecs from the
// app // app
let ecs = Arc::new(Mutex::new(app.world)); let ecs = Arc::new(Mutex::new(app.world));
tokio::spawn(run_schedule_loop( tokio::spawn(run_schedule_loop(
ecs.clone(), ecs.clone(),
app.schedule, app.outer_schedule_label,
run_schedule_receiver, run_schedule_receiver,
)); ));
tokio::spawn(tick_run_schedule_loop(run_schedule_sender)); tokio::spawn(tick_run_schedule_loop(run_schedule_sender));
@ -573,14 +568,16 @@ pub fn start_ecs(
} }
async fn run_schedule_loop( async fn run_schedule_loop(
ecs: Arc<Mutex<Ecs>>, ecs: Arc<Mutex<World>>,
mut schedule: Schedule, outer_schedule_label: Box<dyn ScheduleLabel>,
mut run_schedule_receiver: mpsc::UnboundedReceiver<()>, mut run_schedule_receiver: mpsc::UnboundedReceiver<()>,
) { ) {
loop { loop {
// whenever we get an event from run_schedule_receiver, run the schedule // whenever we get an event from run_schedule_receiver, run the schedule
run_schedule_receiver.recv().await; 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();
} }
} }
@ -609,7 +606,7 @@ impl PluginGroup for DefaultPlugins {
fn build(self) -> PluginGroupBuilder { fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>() PluginGroupBuilder::start::<Self>()
.add(LogPlugin::default()) .add(LogPlugin::default())
.add(TickPlugin::default()) .add(TimePlugin::default())
.add(PacketHandlerPlugin) .add(PacketHandlerPlugin)
.add(AzaleaPlugin) .add(AzaleaPlugin)
.add(EntityPlugin) .add(EntityPlugin)
@ -618,5 +615,6 @@ impl PluginGroup for DefaultPlugins {
.add(TaskPoolPlugin::default()) .add(TaskPoolPlugin::default())
.add(ChatPlugin) .add(ChatPlugin)
.add(DisconnectPlugin) .add(DisconnectPlugin)
.add(PlayerMovePlugin)
} }
} }

View file

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

View file

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

View file

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

View file

@ -26,7 +26,6 @@ mod player;
pub mod task_pool; pub mod task_pool;
pub use account::Account; pub use account::Account;
pub use azalea_ecs as ecs;
pub use client::{init_ecs_app, start_ecs, Client, ClientInformation, JoinError}; pub use client::{init_ecs_app, start_ecs, Client, ClientInformation, JoinError};
pub use events::Event; pub use events::Event;
pub use local_player::{GameProfileComponent, LocalPlayer}; 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_auth::game_profile::GameProfile;
use azalea_core::ChunkPos; 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_protocol::packets::game::ServerboundGamePacket;
use azalea_world::{ use azalea_world::{
entity::{self, Dead}, 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 derive_more::{Deref, DerefMut};
use parking_lot::RwLock; use parking_lot::RwLock;
@ -44,7 +43,7 @@ pub struct LocalPlayer {
pub partial_world: Arc<RwLock<PartialWorld>>, pub partial_world: Arc<RwLock<PartialWorld>>,
/// The world is the combined [`PartialWorld`]s of all clients in the same /// 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) /// 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 /// A task that reads packets from the server. The client is disconnected
/// when this task ends. /// when this task ends.
@ -88,7 +87,7 @@ impl LocalPlayer {
pub fn new( pub fn new(
entity: Entity, entity: Entity,
packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>, packet_writer: mpsc::UnboundedSender<ServerboundGamePacket>,
world: Arc<RwLock<World>>, world: Arc<RwLock<Instance>>,
read_packets_task: JoinHandle<()>, read_packets_task: JoinHandle<()>,
write_packets_task: JoinHandle<()>, write_packets_task: JoinHandle<()>,
) -> Self { ) -> Self {
@ -129,7 +128,7 @@ impl Drop for LocalPlayer {
/// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s. /// Update the [`LocalPlayerInLoadedChunk`] component for all [`LocalPlayer`]s.
pub fn update_in_loaded_chunk( pub fn update_in_loaded_chunk(
mut commands: azalea_ecs::system::Commands, mut commands: bevy_ecs::system::Commands,
query: Query<(Entity, &LocalPlayer, &entity::Position)>, query: Query<(Entity, &LocalPlayer, &entity::Position)>,
) { ) {
for (entity, local_player, position) in &query { for (entity, local_player, position) in &query {

View file

@ -1,7 +1,8 @@
use crate::client::Client; use crate::client::Client;
use crate::local_player::{LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState}; use crate::local_player::{
use azalea_ecs::entity::Entity; update_in_loaded_chunk, LocalPlayer, LocalPlayerInLoadedChunk, PhysicsState,
use azalea_ecs::{event::EventReader, query::With, system::Query}; };
use azalea_physics::{force_jump_listener, PhysicsSet};
use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket; use azalea_protocol::packets::game::serverbound_player_command_packet::ServerboundPlayerCommandPacket;
use azalea_protocol::packets::game::{ use azalea_protocol::packets::game::{
serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket, serverbound_move_player_pos_packet::ServerboundMovePlayerPosPacket,
@ -13,6 +14,14 @@ use azalea_world::{
entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId}, entity::{self, metadata::Sprinting, Attributes, Jumping, MinecraftEntityId},
MoveEntityError, 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 std::backtrace::Backtrace;
use thiserror::Error; 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 { impl Client {
/// Set whether we're jumping. This acts as if you held space in /// Set whether we're jumping. This acts as if you held space in
/// vanilla. If you want to jump once, use the `jump` function. /// 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 std::{collections::HashSet, io::Cursor, sync::Arc};
use azalea_core::{ChunkPos, ResourceLocation, Vec3}; 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::{ use azalea_protocol::{
connect::{ReadConnection, WriteConnection}, connect::{ReadConnection, WriteConnection},
packets::game::{ packets::game::{
@ -25,12 +16,21 @@ use azalea_protocol::{
use azalea_world::{ use azalea_world::{
entity::{ entity::{
metadata::{apply_metadata, Health, PlayerMetadataBundle}, metadata::{apply_metadata, Health, PlayerMetadataBundle},
set_rotation, Dead, EntityBundle, EntityKind, LastSentPosition, MinecraftEntityId, Physics, set_rotation, Dead, EntityBundle, EntityKind, EntityUpdateSet, LastSentPosition,
PlayerBundle, Position, WorldName, MinecraftEntityId, Physics, PlayerBundle, Position, WorldName,
}, },
entity::{LoadedBy, RelativeEntityUpdate}, entity::{LoadedBy, RelativeEntityUpdate},
PartialWorld, WorldContainer, 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 log::{debug, error, trace, warn};
use parking_lot::Mutex; use parking_lot::Mutex;
use tokio::sync::mpsc; use tokio::sync::mpsc;
@ -46,7 +46,7 @@ use crate::{
/// ``` /// ```
/// # use azalea_client::packet_handling::PacketEvent; /// # use azalea_client::packet_handling::PacketEvent;
/// # use azalea_protocol::packets::game::ClientboundGamePacket; /// # use azalea_protocol::packets::game::ClientboundGamePacket;
/// # use azalea_ecs::event::EventReader; /// # use bevy_ecs::event::EventReader;
/// ///
/// fn handle_packets(mut events: EventReader<PacketEvent>) { /// fn handle_packets(mut events: EventReader<PacketEvent>) {
/// for PacketEvent { /// for PacketEvent {
@ -72,25 +72,22 @@ pub struct PacketEvent {
pub struct PacketHandlerPlugin; pub struct PacketHandlerPlugin;
#[derive(StageLabel)]
pub struct SendPacketEventsStage;
impl Plugin for PacketHandlerPlugin { impl Plugin for PacketHandlerPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_stage_before( app.add_system(send_packet_events.in_base_set(CoreSet::First))
CoreStage::PreUpdate, .add_system(
SendPacketEventsStage, process_packet_events
SystemStage::parallel(), .in_base_set(CoreSet::PreUpdate)
) // we want to index and deindex right after
.add_system_to_stage(SendPacketEventsStage, send_packet_events) .before(EntityUpdateSet::Deindex),
.add_system_to_stage(CoreStage::PreUpdate, process_packet_events) )
.init_resource::<Events<PacketEvent>>() .init_resource::<Events<PacketEvent>>()
.add_event::<AddPlayerEvent>() .add_event::<AddPlayerEvent>()
.add_event::<RemovePlayerEvent>() .add_event::<RemovePlayerEvent>()
.add_event::<UpdatePlayerEvent>() .add_event::<UpdatePlayerEvent>()
.add_event::<ChatReceivedEvent>() .add_event::<ChatReceivedEvent>()
.add_event::<DeathEvent>() .add_event::<DeathEvent>()
.add_event::<KeepAliveEvent>(); .add_event::<KeepAliveEvent>();
} }
} }
@ -168,7 +165,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 events_owned = Vec::new();
let mut system_state: SystemState<EventReader<PacketEvent>> = SystemState::new(ecs); let mut system_state: SystemState<EventReader<PacketEvent>> = SystemState::new(ecs);
let mut events = system_state.get_mut(ecs); let mut events = system_state.get_mut(ecs);
@ -715,8 +712,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity { if let Some(entity) = entity {
let new_position = p.position; let new_position = p.position;
commands.add(RelativeEntityUpdate { commands.entity(entity).add(RelativeEntityUpdate {
entity,
partial_world: local_player.partial_world.clone(), partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity| { update: Box::new(move |entity| {
let mut position = entity.get_mut::<Position>().unwrap(); let mut position = entity.get_mut::<Position>().unwrap();
@ -747,8 +743,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity { if let Some(entity) = entity {
let delta = p.delta.clone(); let delta = p.delta.clone();
commands.add(RelativeEntityUpdate { commands.entity(entity).add(RelativeEntityUpdate {
entity,
partial_world: local_player.partial_world.clone(), partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity_mut| { update: Box::new(move |entity_mut| {
let mut position = entity_mut.get_mut::<Position>().unwrap(); let mut position = entity_mut.get_mut::<Position>().unwrap();
@ -776,8 +771,7 @@ fn process_packet_events(ecs: &mut Ecs) {
if let Some(entity) = entity { if let Some(entity) = entity {
let delta = p.delta.clone(); let delta = p.delta.clone();
commands.add(RelativeEntityUpdate { commands.entity(entity).add(RelativeEntityUpdate {
entity,
partial_world: local_player.partial_world.clone(), partial_world: local_player.partial_world.clone(),
update: Box::new(move |entity_mut| { update: Box::new(move |entity_mut| {
let mut position = entity_mut.get_mut::<Position>().unwrap(); let mut position = entity_mut.get_mut::<Position>().unwrap();

View file

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

View file

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

View file

@ -9,10 +9,10 @@ version = "0.6.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
azalea-buf = {path = "../azalea-buf", version = "^0.6.0" } azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
azalea-chat = {path = "../azalea-chat", version = "^0.6.0" } azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
azalea-nbt = {path = "../azalea-nbt", version = "^0.6.0" } azalea-nbt = { path = "../azalea-nbt", 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" uuid = "^1.1.2"
[features] [features]

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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
azalea-block = {path = "../azalea-block", version = "^0.6.0"} azalea-block = { path = "../azalea-block", version = "^0.6.0" }
azalea-core = {path = "../azalea-core", 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-registry = {path = "../azalea-registry", version = "^0.6.0"} azalea-world = { path = "../azalea-world", 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" once_cell = "1.16.0"
parking_lot = "^0.12.1" parking_lot = "^0.12.1"
[dev-dependencies] [dev-dependencies]
bevy_time = "0.10.0"
uuid = "^1.1.2" uuid = "^1.1.2"

View file

@ -7,7 +7,7 @@ mod world_collisions;
use azalea_core::{Axis, Vec3, AABB, EPSILON}; use azalea_core::{Axis, Vec3, AABB, EPSILON};
use azalea_world::{ use azalea_world::{
entity::{self}, entity::{self},
MoveEntityError, World, Instance, MoveEntityError,
}; };
pub use blocks::BlockWithShape; pub use blocks::BlockWithShape;
pub use discrete_voxel_shape::*; pub use discrete_voxel_shape::*;
@ -52,7 +52,7 @@ pub enum MoverType {
// return var4; // 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; let entity_bounding_box = physics.bounding_box;
// TODO: get_entity_collisions // TODO: get_entity_collisions
// let entity_collisions = world.get_entity_collisions(self, // 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( pub fn move_colliding(
_mover_type: &MoverType, _mover_type: &MoverType,
movement: &Vec3, movement: &Vec3,
world: &World, world: &Instance,
position: &mut entity::Position, position: &mut entity::Position,
physics: &mut entity::Physics, physics: &mut entity::Physics,
) -> Result<(), MoveEntityError> { ) -> Result<(), MoveEntityError> {
@ -186,7 +186,7 @@ pub fn move_colliding(
fn collide_bounding_box( fn collide_bounding_box(
movement: &Vec3, movement: &Vec3,
entity_bounding_box: &AABB, entity_bounding_box: &AABB,
world: &World, world: &Instance,
entity_collisions: Vec<VoxelShape>, entity_collisions: Vec<VoxelShape>,
) -> Vec3 { ) -> Vec3 {
let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1); 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 crate::collision::{BlockWithShape, VoxelShape, AABB};
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON}; use azalea_core::{ChunkPos, ChunkSectionPos, Cursor3d, CursorIterationType, EPSILON};
use azalea_world::{Chunk, World}; use azalea_world::{Chunk, Instance};
use parking_lot::RwLock; use parking_lot::RwLock;
use std::sync::Arc; 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) BlockCollisions::new(world, aabb)
} }
pub struct BlockCollisions<'a> { pub struct BlockCollisions<'a> {
pub world: &'a World, pub world: &'a Instance,
pub aabb: AABB, pub aabb: AABB,
pub entity_shape: VoxelShape, pub entity_shape: VoxelShape,
pub cursor: Cursor3d, pub cursor: Cursor3d,
@ -19,7 +19,7 @@ pub struct BlockCollisions<'a> {
} }
impl<'a> 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_x = (aabb.min_x - EPSILON).floor() as i32 - 1;
let origin_y = (aabb.min_y - 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; 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_block::{Block, BlockState};
use azalea_core::{BlockPos, Vec3}; 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::{ use azalea_world::{
entity::{ entity::{
metadata::Sprinting, move_relative, Attributes, Jumping, Local, Physics, Position, metadata::Sprinting, move_relative, Attributes, Jumping, Local, Physics, Position,
WorldName, 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}; 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; pub struct PhysicsPlugin;
impl Plugin for PhysicsPlugin { impl Plugin for PhysicsPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<ForceJumpEvent>() app.add_event::<ForceJumpEvent>()
.add_system( .add_system(force_jump_listener.before(azalea_world::entity::update_bounding_box))
force_jump_listener .add_systems(
.label("force_jump_listener") (ai_step, travel)
.after("walk_listener") .chain()
.after("sprint_listener") .in_set(PhysicsSet)
.before(azalea_world::entity::update_bounding_box), .in_schedule(CoreSchedule::FixedUpdate),
) );
.add_tick_system(travel.label("travel").after(ai_step))
.add_tick_system(ai_step.label("ai_step"));
} }
} }
@ -156,7 +157,7 @@ pub fn ai_step(
/// Jump even if we aren't on the ground. /// Jump even if we aren't on the ground.
pub struct ForceJumpEvent(pub Entity); pub struct ForceJumpEvent(pub Entity);
fn force_jump_listener( pub fn force_jump_listener(
mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>, mut query: Query<(&mut Physics, &Position, &Sprinting, &WorldName)>,
world_container: Res<WorldContainer>, world_container: Res<WorldContainer>,
mut events: EventReader<ForceJumpEvent>, 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( fn handle_relative_friction_and_calculate_movement(
block_friction: f32, block_friction: f32,
world: &World, world: &Instance,
physics: &mut Physics, physics: &mut Physics,
position: &mut Position, position: &mut Position,
attributes: &Attributes, 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 /// Returns the what the entity's jump should be multiplied by based on the
/// block they're standing on. /// 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_at_pos = world.chunks.get_block_state(&position.into());
let block_below = world let block_below = world
.chunks .chunks
@ -279,7 +280,7 @@ fn block_jump_factor(world: &World, position: &Position) -> f32 {
// public double getJumpBoostPower() { // public double getJumpBoostPower() {
// return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F * // return this.hasEffect(MobEffects.JUMP) ? (double)(0.1F *
// (float)(this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } // (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) 0.42 * block_jump_factor(world, position)
} }
@ -303,22 +304,21 @@ mod tests {
use super::*; use super::*;
use azalea_core::{ChunkPos, ResourceLocation}; use azalea_core::{ChunkPos, ResourceLocation};
use azalea_ecs::{app::App, TickPlugin};
use azalea_world::{ use azalea_world::{
entity::{EntityBundle, EntityPlugin, MinecraftEntityId}, entity::{EntityBundle, EntityPlugin, MinecraftEntityId},
Chunk, PartialWorld, Chunk, PartialWorld,
}; };
use bevy_app::App;
use bevy_time::fixed_timestep::FixedTime;
use uuid::Uuid; use uuid::Uuid;
/// You need an app to spawn entities in the world and do updates. /// You need an app to spawn entities in the world and do updates.
fn make_test_app() -> App { fn make_test_app() -> App {
let mut app = App::new(); let mut app = App::new();
app.add_plugin(TickPlugin { app.add_plugin(PhysicsPlugin)
tick_interval: Duration::ZERO, .add_plugin(EntityPlugin)
}) .insert_resource(FixedTime::new(Duration::from_millis(50)))
.add_plugin(PhysicsPlugin) .init_resource::<WorldContainer>();
.add_plugin(EntityPlugin)
.init_resource::<WorldContainer>();
app app
} }
@ -353,6 +353,7 @@ mod tests {
// y should start at 70 // y should start at 70
assert_eq!(entity_pos.y, 70.); assert_eq!(entity_pos.y, 70.);
} }
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
{ {
let entity_pos = *app.world.get::<Position>(entity).unwrap(); 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(); let entity_physics = app.world.get::<Physics>(entity).unwrap().clone();
assert!(entity_physics.delta.y < 0.); assert!(entity_physics.delta.y < 0.);
} }
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
{ {
let entity_pos = *app.world.get::<Position>(entity).unwrap(); let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -413,6 +415,7 @@ mod tests {
block_state.is_some(), 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" "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(); app.update();
{ {
let entity_pos = *app.world.get::<Position>(entity).unwrap(); 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(); let entity_physics = app.world.get::<Physics>(entity).unwrap().clone();
assert!(entity_physics.delta.y < 0.); assert!(entity_physics.delta.y < 0.);
} }
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
{ {
let entity_pos = *app.world.get::<Position>(entity).unwrap(); let entity_pos = *app.world.get::<Position>(entity).unwrap();
@ -476,6 +480,7 @@ mod tests {
); );
// do a few steps so we fall on the slab // do a few steps so we fall on the slab
for _ in 0..20 { for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
} }
let entity_pos = app.world.get::<Position>(entity).unwrap(); let entity_pos = app.world.get::<Position>(entity).unwrap();
@ -528,6 +533,7 @@ mod tests {
); );
// do a few steps so we fall on the slab // do a few steps so we fall on the slab
for _ in 0..20 { for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
} }
let entity_pos = app.world.get::<Position>(entity).unwrap(); let entity_pos = app.world.get::<Position>(entity).unwrap();
@ -584,6 +590,7 @@ mod tests {
); );
// do a few steps so we fall on the wall // do a few steps so we fall on the wall
for _ in 0..20 { for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); app.update();
} }
@ -645,6 +652,7 @@ mod tests {
); );
// do a few steps so we fall on the wall // do a few steps so we fall on the wall
for _ in 0..20 { for _ in 0..20 {
app.world.run_schedule(CoreSchedule::FixedUpdate);
app.update(); 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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [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" async-recursion = "1.0.0"
azalea-auth = {path = "../azalea-auth", version = "^0.6.0" } azalea-auth = { path = "../azalea-auth", version = "^0.6.0" }
azalea-block = {path = "../azalea-block", default-features = false, 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-brigadier = { path = "../azalea-brigadier", version = "^0.6.0", features = [
azalea-buf = {path = "../azalea-buf", version = "^0.6.0" } "azalea-buf",
azalea-chat = {path = "../azalea-chat", version = "^0.6.0" } ] }
azalea-core = {path = "../azalea-core", optional = true, version = "^0.6.0" } azalea-buf = { path = "../azalea-buf", version = "^0.6.0" }
azalea-crypto = {path = "../azalea-crypto", version = "^0.6.0" } azalea-chat = { path = "../azalea-chat", version = "^0.6.0" }
azalea-nbt = {path = "../azalea-nbt", version = "^0.6.0" } azalea-core = { path = "../azalea-core", optional = true, version = "^0.6.0" }
azalea-protocol-macros = {path = "./azalea-protocol-macros", version = "^0.6.0" } azalea-crypto = { path = "../azalea-crypto", version = "^0.6.0" }
azalea-registry = {path = "../azalea-registry", version = "^0.6.0" } azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
azalea-world = {path = "../azalea-world", version = "^0.6.0" } azalea-protocol-macros = { path = "./azalea-protocol-macros", version = "^0.6.0" }
bevy_ecs = { version = "0.9.1", default-features = false } 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" byteorder = "^1.4.3"
bytes = "^1.1.0" bytes = "^1.1.0"
flate2 = "1.0.23" flate2 = "1.0.23"
futures = "0.3.24" futures = "0.3.24"
futures-util = "0.3.24" futures-util = "0.3.24"
log = "0.4.17" 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" serde_json = "^1.0.72"
thiserror = "1.0.37" thiserror = "1.0.37"
tokio = {version = "^1.24.2", features = ["io-util", "net", "macros"]} tokio = { version = "^1.24.2", features = ["io-util", "net", "macros"] }
tokio-util = {version = "0.7.4", features = ["codec"]} tokio-util = { version = "0.7.4", features = ["codec"] }
trust-dns-resolver = {version = "^0.22.0", default-features = false, features = ["tokio-runtime"]} trust-dns-resolver = { version = "^0.22.0", default-features = false, features = [
"tokio-runtime",
] }
uuid = "1.1.2" uuid = "1.1.2"
[features] [features]

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 = [ azalea-core = { path = "../azalea-core", version = "^0.6.0", features = [
"bevy_ecs", "bevy_ecs",
] } ] }
azalea-ecs = { version = "0.6.0", path = "../azalea-ecs" }
azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" } azalea-nbt = { path = "../azalea-nbt", version = "^0.6.0" }
azalea-registry = { path = "../azalea-registry", 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"] } derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
enum-as-inner = "0.5.1" enum-as-inner = "0.5.1"
log = "0.4.17" log = "0.4.17"

View file

@ -1,5 +1,5 @@
use azalea_core::ResourceLocation; use azalea_core::ResourceLocation;
use azalea_ecs::system::Resource; use bevy_ecs::system::Resource;
use log::error; use log::error;
use nohash_hasher::IntMap; use nohash_hasher::IntMap;
use parking_lot::RwLock; use parking_lot::RwLock;
@ -8,7 +8,7 @@ use std::{
sync::{Arc, Weak}, 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 /// 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. /// 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 // 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 // issue when there's multiple clients with the same WorldContainer in different worlds
// anyways. // anyways.
pub worlds: HashMap<ResourceLocation, Weak<RwLock<World>>>, pub worlds: HashMap<ResourceLocation, Weak<RwLock<Instance>>>,
} }
impl WorldContainer { impl WorldContainer {
@ -37,7 +37,7 @@ impl WorldContainer {
} }
/// Get a world from the container. /// 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()) self.worlds.get(name).and_then(|world| world.upgrade())
} }
@ -49,7 +49,7 @@ impl WorldContainer {
name: ResourceLocation, name: ResourceLocation,
height: u32, height: u32,
min_y: i32, min_y: i32,
) -> Arc<RwLock<World>> { ) -> Arc<RwLock<Instance>> {
if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) { if let Some(existing_lock) = self.worlds.get(&name).and_then(|world| world.upgrade()) {
let existing = existing_lock.read(); let existing = existing_lock.read();
if existing.chunks.height != height { if existing.chunks.height != height {
@ -66,7 +66,7 @@ impl WorldContainer {
} }
existing_lock.clone() existing_lock.clone()
} else { } else {
let world = Arc::new(RwLock::new(World { let world = Arc::new(RwLock::new(Instance {
chunks: ChunkStorage::new(height, min_y), chunks: ChunkStorage::new(height, min_y),
entities_by_chunk: HashMap::new(), entities_by_chunk: HashMap::new(),
entity_by_id: IntMap::default(), entity_by_id: IntMap::default(),

View file

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

View file

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

View file

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

View file

@ -9,15 +9,14 @@ use crate::{
update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer, update_entity_by_id_index, update_uuid_index, PartialWorld, WorldContainer,
}; };
use azalea_core::ChunkPos; use azalea_core::ChunkPos;
use azalea_ecs::{ use bevy_app::{App, CoreSet, Plugin};
app::{App, CoreStage, Plugin}, use bevy_ecs::{
component::Component, component::Component,
ecs::Ecs,
ecs::EntityMut,
entity::Entity, entity::Entity,
query::{Added, Changed, With, Without}, query::{Added, Changed, With, Without},
schedule::{IntoSystemDescriptor, SystemSet}, schedule::{IntoSystemConfig, IntoSystemConfigs, SystemSet},
system::{Command, Commands, Query, Res, ResMut, Resource}, system::{Commands, EntityCommand, Query, Res, ResMut, Resource},
world::{EntityMut, World},
}; };
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
use log::{debug, warn}; use log::{debug, warn};
@ -32,6 +31,18 @@ use uuid::Uuid;
use super::Local; 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. /// Plugin handling some basic entity functionality.
pub struct EntityPlugin; pub struct EntityPlugin;
impl Plugin for EntityPlugin { impl Plugin for EntityPlugin {
@ -40,30 +51,31 @@ impl Plugin for EntityPlugin {
// added to indexes during update (done by this plugin) // added to indexes during update (done by this plugin)
// modified during update // modified during update
// despawned post-update (done by this plugin) // despawned post-update (done by this plugin)
app.add_system_set_to_stage( app.add_system(
CoreStage::PreUpdate, remove_despawned_entities_from_indexes
SystemSet::new().with_system(remove_despawned_entities_from_indexes), .in_base_set(CoreSet::PreUpdate)
.in_set(EntityUpdateSet::Deindex),
) )
.add_system_set_to_stage( .add_systems(
CoreStage::PostUpdate, (deduplicate_entities, deduplicate_local_entities)
SystemSet::new() .in_base_set(CoreSet::PostUpdate)
.with_system(deduplicate_entities.label("deduplicate_entities")) .in_set(EntityUpdateSet::Deduplicate),
.with_system(deduplicate_local_entities.label("deduplicate_entities")),
) )
.add_system_set( .add_systems(
SystemSet::new() (
.with_system(update_entity_chunk_positions) update_entity_chunk_positions,
.with_system(update_uuid_index.label("update_indexes")) update_uuid_index,
.with_system(update_entity_by_id_index.label("update_indexes")), update_entity_by_id_index,
) )
.add_system_set( .in_set(EntityUpdateSet::Index),
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((
add_updates_received,
debug_new_entity,
debug_detect_updates_received_on_local_entities,
add_dead,
update_bounding_box,
))
.init_resource::<EntityInfos>(); .init_resource::<EntityInfos>();
} }
} }
@ -134,26 +146,24 @@ impl PartialEntityInfos {
/// other clients within render distance will get too. You usually don't need /// other clients within render distance will get too. You usually don't need
/// this when the change isn't relative either. /// this when the change isn't relative either.
pub struct RelativeEntityUpdate { pub struct RelativeEntityUpdate {
pub entity: Entity,
pub partial_world: Arc<RwLock<PartialWorld>>, pub partial_world: Arc<RwLock<PartialWorld>>,
// a function that takes the entity and updates it // a function that takes the entity and updates it
pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>, pub update: Box<dyn FnOnce(&mut EntityMut) + Send + Sync>,
} }
impl Command for RelativeEntityUpdate { impl EntityCommand for RelativeEntityUpdate {
fn write(self, world: &mut Ecs) { fn write(self, entity: Entity, world: &mut World) {
let partial_entity_infos = &mut self.partial_world.write().entity_infos; 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 // if the entity owns this partial world, it's always allowed to update itself
(self.update)(&mut entity); (self.update)(&mut entity_mut);
return; return;
}; };
let entity_id = *entity.get::<MinecraftEntityId>().unwrap(); let entity_id = *entity_mut.get::<MinecraftEntityId>().unwrap();
let Some(updates_received) = entity_mut.get_mut::<UpdatesReceived>() else {
let Some(updates_received) = entity.get_mut::<UpdatesReceived>() else {
// a client tried to update another client, which isn't allowed // a client tried to update another client, which isn't allowed
return; return;
}; };
@ -170,9 +180,9 @@ impl Command for RelativeEntityUpdate {
.updates_received .updates_received
.insert(entity_id, new_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); (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; pub use attributes::Attributes;
use azalea_block::BlockState; use azalea_block::BlockState;
use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB}; use azalea_core::{BlockPos, ChunkPos, ResourceLocation, Vec3, AABB};
use azalea_ecs::{ use bevy_ecs::{
bundle::Bundle, bundle::Bundle,
component::Component, component::Component,
entity::Entity, entity::Entity,
@ -22,7 +22,9 @@ use azalea_ecs::{
pub use data::*; pub use data::*;
use derive_more::{Deref, DerefMut}; use derive_more::{Deref, DerefMut};
pub use dimensions::{update_bounding_box, EntityDimensions}; 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 std::fmt::Debug;
use uuid::Uuid; use uuid::Uuid;

View file

@ -5,7 +5,7 @@ use crate::{
ChunkStorage, PartialChunkStorage, WorldContainer, ChunkStorage, PartialChunkStorage, WorldContainer,
}; };
use azalea_core::ChunkPos; use azalea_core::ChunkPos;
use azalea_ecs::{ use bevy_ecs::{
entity::Entity, entity::Entity,
query::{Changed, With, Without}, query::{Changed, With, Without},
system::{Commands, Query, Res, ResMut}, system::{Commands, Query, Res, ResMut},
@ -172,7 +172,7 @@ pub fn update_uuid_index(
/// A world where the chunks are stored as weak pointers. This is used for /// A world where the chunks are stored as weak pointers. This is used for
/// shared worlds. /// shared worlds.
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct World { pub struct Instance {
pub chunks: ChunkStorage, pub chunks: ChunkStorage,
/// An index of all the entities we know are in the chunks of the world /// An index of all the entities we know are in the chunks of the world
@ -182,7 +182,7 @@ pub struct World {
pub entity_by_id: IntMap<MinecraftEntityId, Entity>, pub entity_by_id: IntMap<MinecraftEntityId, Entity>,
} }
impl World { impl Instance {
/// Get an ECS [`Entity`] from a Minecraft entity ID. /// Get an ECS [`Entity`] from a Minecraft entity ID.
pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> { pub fn entity_by_id(&self, entity_id: &MinecraftEntityId) -> Option<Entity> {
self.entity_by_id.get(entity_id).copied() self.entity_by_id.get(entity_id).copied()
@ -236,7 +236,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 /// Make an empty world from this `ChunkStorage`. This is meant to be a
/// convenience function for tests. /// convenience function for tests.
fn from(chunks: ChunkStorage) -> Self { fn from(chunks: ChunkStorage) -> Self {

View file

@ -8,31 +8,31 @@ version = "0.6.0"
[package.metadata.release] [package.metadata.release]
pre-release-replacements = [ 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] [dependencies]
anyhow = "^1.0.65" anyhow = "^1.0.65"
async-trait = "0.1.58" async-trait = "0.1.58"
azalea-block = {version = "0.6.0", path = "../azalea-block"} azalea-block = { version = "0.6.0", path = "../azalea-block" }
azalea-chat = {version = "0.6.0", path = "../azalea-chat"} azalea-chat = { version = "0.6.0", path = "../azalea-chat" }
azalea-client = {version = "0.6.0", path = "../azalea-client"} azalea-client = { version = "0.6.0", path = "../azalea-client" }
azalea-core = {version = "0.6.0", path = "../azalea-core"} 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-physics = {version = "0.6.0", path = "../azalea-physics"} azalea-protocol = { version = "0.6.0", path = "../azalea-protocol" }
azalea-protocol = {version = "0.6.0", path = "../azalea-protocol"} azalea-registry = { version = "0.6.0", path = "../azalea-registry" }
azalea-registry = {version = "0.6.0", path = "../azalea-registry"} azalea-world = { version = "0.6.0", path = "../azalea-world" }
azalea-world = {version = "0.6.0", path = "../azalea-world"} bevy_app = "0.10.0"
bevy_tasks = "0.9.1" bevy_ecs = "0.10.0"
derive_more = {version = "0.99.17", features = ["deref", "deref_mut"]} bevy_tasks = "0.10.0"
derive_more = { version = "0.99.17", features = ["deref", "deref_mut"] }
futures = "0.3.25" futures = "0.3.25"
futures-lite = "1.12.0" futures-lite = "1.12.0"
log = "0.4.17" log = "0.4.17"
nohash-hasher = "0.2.0" nohash-hasher = "0.2.0"
num-traits = "0.2.15" 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" priority-queue = "1.3.0"
thiserror = "^1.0.37" thiserror = "^1.0.37"
tokio = "^1.24.2" tokio = "^1.24.2"
uuid = "1.2.2" uuid = "1.2.2"

View file

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

View file

@ -52,17 +52,17 @@ async fn main() -> anyhow::Result<()> {
} }
loop { loop {
let e = SwarmBuilder::new() // let e = SwarmBuilder::new()
.add_accounts(accounts.clone()) // .add_accounts(accounts.clone())
.set_handler(handle)
.set_swarm_handler(swarm_handle)
.join_delay(Duration::from_millis(1000))
.start("localhost")
.await;
// let e = azalea::ClientBuilder::new()
// .set_handler(handle) // .set_handler(handle)
// .start(Account::offline("bot"), "localhost") // .set_swarm_handler(swarm_handle)
// .join_delay(Duration::from_millis(1000))
// .start("localhost")
// .await; // .await;
let e = azalea::ClientBuilder::new()
.set_handler(handle)
.start(Account::offline("bot"), "localhost")
.await;
eprintln!("{e:?}"); eprintln!("{e:?}");
} }
} }

View file

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

View file

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

View file

@ -4,18 +4,18 @@ mod mtdstarlite;
use crate::bot::{JumpEvent, LookAtEvent}; use crate::bot::{JumpEvent, LookAtEvent};
use crate::{SprintDirection, WalkDirection}; use crate::{SprintDirection, WalkDirection};
use azalea_client::{StartSprintEvent, StartWalkEvent}; use crate::app::{App, CoreSchedule, IntoSystemAppConfig, Plugin};
use azalea_core::{BlockPos, CardinalDirection}; use crate::ecs::{
use azalea_ecs::{
app::{App, Plugin},
component::Component, component::Component,
entity::Entity, entity::Entity,
event::{EventReader, EventWriter}, event::{EventReader, EventWriter},
query::{With, Without}, query::{With, Without},
schedule::IntoSystemDescriptor, schedule::IntoSystemConfig,
system::{Commands, Query, Res}, 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::metadata::Player;
use azalea_world::entity::Local; use azalea_world::entity::Local;
use azalea_world::{ use azalea_world::{
@ -36,7 +36,13 @@ impl Plugin for PathfinderPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<GotoEvent>() app.add_event::<GotoEvent>()
.add_event::<PathFoundEvent>() .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(goto_listener)
.add_system(add_default_pathfinder) .add_system(add_default_pathfinder)
.add_system(handle_tasks.before(path_found_listener)) .add_system(handle_tasks.before(path_found_listener))

View file

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

View file

@ -3,4 +3,6 @@
pub use crate::{bot::BotClientExt, pathfinder::PathfinderClientExt, ClientBuilder}; pub use crate::{bot::BotClientExt, pathfinder::PathfinderClientExt, ClientBuilder};
pub use azalea_client::{Account, Client, Event}; 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 // 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. // messages from the queue that are before that index.
use azalea_client::chat::{ChatPacket, ChatReceivedEvent}; use crate::ecs::{
use azalea_ecs::{
app::{App, Plugin},
component::Component, component::Component,
event::{EventReader, EventWriter}, event::{EventReader, EventWriter},
schedule::IntoSystemDescriptor, schedule::IntoSystemConfigs,
system::{Commands, Query, Res, ResMut, Resource}, system::{Commands, Query, Res, ResMut, Resource},
}; };
use azalea_client::chat::{ChatPacket, ChatReceivedEvent};
use bevy_app::{App, Plugin};
use std::collections::VecDeque; use std::collections::VecDeque;
use super::{Swarm, SwarmEvent}; use super::{Swarm, SwarmEvent};
@ -30,8 +30,7 @@ pub struct SwarmChatPlugin;
impl Plugin for SwarmChatPlugin { impl Plugin for SwarmChatPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<NewChatMessageEvent>() app.add_event::<NewChatMessageEvent>()
.add_system(chat_listener.label("chat_listener")) .add_systems((chat_listener, update_min_index_and_shrink_queue).chain())
.add_system(update_min_index_and_shrink_queue.after("chat_listener"))
.insert_resource(GlobalChatState { .insert_resource(GlobalChatState {
chat_queue: VecDeque::new(), chat_queue: VecDeque::new(),
chat_min_index: 0, chat_min_index: 0,
@ -151,7 +150,7 @@ fn update_min_index_and_shrink_queue(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use azalea_ecs::{ecs::Ecs, event::Events, system::SystemState}; use bevy_ecs::{event::Events, prelude::World, system::SystemState};
use super::*; use super::*;
@ -161,8 +160,7 @@ mod tests {
// event mangement in drain_events // event mangement in drain_events
app.init_resource::<Events<ChatReceivedEvent>>() app.init_resource::<Events<ChatReceivedEvent>>()
.init_resource::<Events<NewChatMessageEvent>>() .init_resource::<Events<NewChatMessageEvent>>()
.add_system(chat_listener.label("chat_listener")) .add_systems((chat_listener, update_min_index_and_shrink_queue).chain())
.add_system(update_min_index_and_shrink_queue.after("chat_listener"))
.insert_resource(GlobalChatState { .insert_resource(GlobalChatState {
chat_queue: VecDeque::new(), chat_queue: VecDeque::new(),
chat_min_index: 0, chat_min_index: 0,
@ -170,7 +168,7 @@ mod tests {
app 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>>> = let mut system_state: SystemState<ResMut<Events<NewChatMessageEvent>>> =
SystemState::new(ecs); SystemState::new(ecs);
let mut events = system_state.get_mut(ecs); let mut events = system_state.get_mut(ecs);

View file

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

View file

@ -6,19 +6,14 @@ pub mod prelude;
use crate::{bot::DefaultBotPlugins, HandleFn}; use crate::{bot::DefaultBotPlugins, HandleFn};
use azalea_client::{chat::ChatPacket, init_ecs_app, start_ecs, Account, Client, Event, JoinError}; 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::{ use azalea_protocol::{
connect::ConnectionError, connect::ConnectionError,
resolver::{self, ResolverError}, resolver::{self, ResolverError},
ServerAddress, ServerAddress,
}; };
use azalea_world::WorldContainer; 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 futures::future::join_all;
use log::error; use log::error;
use parking_lot::{Mutex, RwLock}; use parking_lot::{Mutex, RwLock};
@ -35,7 +30,7 @@ use tokio::sync::mpsc;
/// It's used to make the [`Swarm::add`] function work. /// It's used to make the [`Swarm::add`] function work.
#[derive(Clone, Resource)] #[derive(Clone, Resource)]
pub struct Swarm { pub struct Swarm {
pub ecs_lock: Arc<Mutex<Ecs>>, pub ecs_lock: Arc<Mutex<World>>,
bots: Arc<Mutex<HashMap<Entity, Client>>>, 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_block::BlockState;
use azalea_chat::FormattedText; use azalea_chat::FormattedText;
use azalea_core::{BlockPos, Direction, Particle, Slot}; 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 derive_more::{Deref, DerefMut};
use thiserror::Error; use thiserror::Error;
use uuid::Uuid; use uuid::Uuid;
@ -183,7 +183,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# impl Allay { # impl Allay {
# pub fn apply_metadata( # pub fn apply_metadata(
# entity: &mut azalea_ecs::system::EntityCommands, # entity: &mut bevy_ecs::system::EntityCommands,
# d: EntityDataItem, # d: EntityDataItem,
# ) -> Result<(), UpdateMetadataError> { # ) -> Result<(), UpdateMetadataError> {
# match d.index { # match d.index {
@ -196,7 +196,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# } # }
code.append(f'impl {struct_name} {{') code.append(f'impl {struct_name} {{')
code.append( 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 {{') code.append(f' match d.index {{')
parent_last_index = -1 parent_last_index = -1
@ -400,7 +400,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# and now make the main apply_metadata # and now make the main apply_metadata
# pub fn apply_metadata( # pub fn apply_metadata(
# entity: &mut azalea_ecs::system::EntityCommands, # entity: &mut bevy_ecs::system::EntityCommands,
# items: Vec<EntityDataItem>, # items: Vec<EntityDataItem>,
# ) -> Result<(), UpdateMetadataError> { # ) -> Result<(), UpdateMetadataError> {
# if entity.contains::<Allay>() { # if entity.contains::<Allay>() {
@ -414,7 +414,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
# } # }
code.append( code.append(
f'''pub fn apply_metadata( f'''pub fn apply_metadata(
entity: &mut azalea_ecs::system::EntityCommands, entity: &mut bevy_ecs::system::EntityCommands,
entity_kind: azalea_registry::EntityKind, entity_kind: azalea_registry::EntityKind,
items: Vec<EntityDataItem>, items: Vec<EntityDataItem>,
) -> Result<(), UpdateMetadataError> {{ ) -> Result<(), UpdateMetadataError> {{
@ -436,7 +436,7 @@ impl From<EntityDataValue> for UpdateMetadataError {
code.append('}') code.append('}')
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 { # match kind {
# azalea_registry::EntityKind::AreaEffectCloud => { # azalea_registry::EntityKind::AreaEffectCloud => {
# entity.insert(AreaEffectCloudMetadataBundle::default()); # entity.insert(AreaEffectCloudMetadataBundle::default());
@ -444,7 +444,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) {')
code.append(' match kind {') code.append(' match kind {')
for entity_id in burger_entity_data: for entity_id in burger_entity_data:
if entity_id.startswith('~'): if entity_id.startswith('~'):