From 6814d9dd62d662d75d70c97802116ebf38d2dc46 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 12 Nov 2019 18:06:28 -0700 Subject: [PATCH 01/37] Use stable Rust: async/await is now stabilized --- .azure-pipelines.yml | 28 ++++++++++++++-------------- rust-toolchain | 1 - 2 files changed, 14 insertions(+), 15 deletions(-) delete mode 100644 rust-toolchain diff --git a/.azure-pipelines.yml b/.azure-pipelines.yml index 19524a591..2c5b0b5fe 100644 --- a/.azure-pipelines.yml +++ b/.azure-pipelines.yml @@ -8,27 +8,27 @@ jobs: - job: 'CI' strategy: matrix: - #linux-stable: - # rustup_toolchain: stable - # image_name: 'ubuntu-16.04' + linux-stable: + rustup_toolchain: stable + image_name: 'ubuntu-16.04' linux-beta: rustup_toolchain: beta image_name: 'ubuntu-16.04' linux-nightly: rustup_toolchain: nightly-2019-09-28 image_name: 'ubuntu-16.04' - #windows-stable: - # rustup_toolchain: stable - # image_name: 'windows-latest' - windows-beta: - rustup_toolchain: beta-gnu + windows-stable: + rustup_toolchain: stable image_name: 'windows-latest' - windows-nightly: - rustup_toolchain: nightly-2019-09-28 - image_name: 'ubuntu-16.04' - #apple-stable: - # rustup_toolchain: stable - # image_name: 'macOS-10.13' + #windows-beta: + # rustup_toolchain: beta-gnu + # image_name: 'windows-latest' + #windows-nightly: + # rustup_toolchain: nightly-2019-09-28 + # image_name: 'ubuntu-16.04' + apple-stable: + rustup_toolchain: stable + image_name: 'macOS-10.13' #apple-beta: # rustup_toolchain: beta # image_name: 'macos-latest' diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 65b2df87f..000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -beta From 53b389e9ce74cd2e7d00b2965dab47e4c39a8883 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Tue, 12 Nov 2019 21:06:14 -0700 Subject: [PATCH 02/37] Ow. --- Cargo.lock | 170 ++-- server/Cargo.toml | 6 +- server/src/blocks/falling.rs | 70 -- server/src/blocks/mod.rs | 128 --- server/src/entity/broadcast.rs | 201 ----- server/src/entity/chunk.rs | 446 ----------- server/src/entity/component.rs | 117 --- server/src/entity/destroy.rs | 82 -- server/src/entity/impls/animal/chicken.rs | 53 -- server/src/entity/impls/animal/cow.rs | 53 -- server/src/entity/impls/animal/donkey.rs | 57 -- server/src/entity/impls/animal/horse.rs | 57 -- server/src/entity/impls/animal/llama.rs | 53 -- server/src/entity/impls/animal/mod.rs | 12 - server/src/entity/impls/animal/mooshroom.rs | 53 -- server/src/entity/impls/animal/pig.rs | 53 -- server/src/entity/impls/animal/rabbit.rs | 53 -- server/src/entity/impls/animal/sheep.rs | 53 -- server/src/entity/impls/animal/squid.rs | 53 -- server/src/entity/impls/arrow.rs | 168 ---- server/src/entity/impls/falling_block.rs | 142 ---- server/src/entity/impls/item.rs | 582 -------------- server/src/entity/impls/mod.rs | 95 --- server/src/entity/impls/test.rs | 23 - server/src/entity/metadata.rs | 182 ----- server/src/entity/mod.rs | 103 --- server/src/entity/movement.rs | 264 ------- server/src/entity/save.rs | 198 ----- server/src/lazy.rs | 36 - server/src/lib.rs | 323 +------- server/src/player/animation.rs | 140 ---- server/src/player/broadcast.rs | 203 ----- server/src/player/chat.rs | 154 ---- server/src/player/digging.rs | 817 -------------------- server/src/player/init.rs | 177 ----- server/src/player/inventory.rs | 759 ------------------ server/src/player/mod.rs | 113 --- server/src/player/movement.rs | 469 ----------- server/src/player/placement.rs | 215 ------ server/src/player/resource_pack.rs | 86 --- server/src/player/save.rs | 101 --- server/src/player/view.rs | 221 ------ server/src/prelude.rs | 5 - server/src/systems.rs | 68 -- server/src/testframework.rs | 481 ------------ server/src/util/broadcaster.rs | 136 ---- server/src/util/macros.rs | 70 -- server/src/util/mod.rs | 169 ---- 48 files changed, 119 insertions(+), 8151 deletions(-) delete mode 100644 server/src/blocks/falling.rs delete mode 100644 server/src/blocks/mod.rs delete mode 100644 server/src/entity/broadcast.rs delete mode 100644 server/src/entity/chunk.rs delete mode 100644 server/src/entity/component.rs delete mode 100644 server/src/entity/destroy.rs delete mode 100644 server/src/entity/impls/animal/chicken.rs delete mode 100644 server/src/entity/impls/animal/cow.rs delete mode 100644 server/src/entity/impls/animal/donkey.rs delete mode 100644 server/src/entity/impls/animal/horse.rs delete mode 100644 server/src/entity/impls/animal/llama.rs delete mode 100644 server/src/entity/impls/animal/mod.rs delete mode 100644 server/src/entity/impls/animal/mooshroom.rs delete mode 100644 server/src/entity/impls/animal/pig.rs delete mode 100644 server/src/entity/impls/animal/rabbit.rs delete mode 100644 server/src/entity/impls/animal/sheep.rs delete mode 100644 server/src/entity/impls/animal/squid.rs delete mode 100644 server/src/entity/impls/arrow.rs delete mode 100644 server/src/entity/impls/falling_block.rs delete mode 100644 server/src/entity/impls/item.rs delete mode 100644 server/src/entity/impls/mod.rs delete mode 100644 server/src/entity/impls/test.rs delete mode 100644 server/src/entity/metadata.rs delete mode 100644 server/src/entity/mod.rs delete mode 100644 server/src/entity/movement.rs delete mode 100644 server/src/entity/save.rs delete mode 100644 server/src/lazy.rs delete mode 100644 server/src/player/animation.rs delete mode 100644 server/src/player/broadcast.rs delete mode 100644 server/src/player/chat.rs delete mode 100644 server/src/player/digging.rs delete mode 100644 server/src/player/init.rs delete mode 100644 server/src/player/inventory.rs delete mode 100644 server/src/player/mod.rs delete mode 100644 server/src/player/movement.rs delete mode 100644 server/src/player/placement.rs delete mode 100644 server/src/player/resource_pack.rs delete mode 100644 server/src/player/save.rs delete mode 100644 server/src/player/view.rs delete mode 100644 server/src/prelude.rs delete mode 100644 server/src/systems.rs delete mode 100644 server/src/testframework.rs delete mode 100644 server/src/util/broadcaster.rs delete mode 100644 server/src/util/macros.rs delete mode 100644 server/src/util/mod.rs diff --git a/Cargo.lock b/Cargo.lock index fe183b46b..05ac9fe2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -90,6 +90,11 @@ dependencies = [ "nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "as-slice" version = "0.1.0" @@ -99,11 +104,6 @@ dependencies = [ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "atom" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "atty" version = "0.2.13" @@ -146,6 +146,19 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "bit-set" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bit-vec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "1.2.0" @@ -654,6 +667,7 @@ dependencies = [ "hematite-nbt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "humantime-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "legion 0.1.1 (git+https://github.com/TomGillen/legion?rev=2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mojang-api 0.3.0 (git+https://github.com/caelunshun/mojang-api-rs?rev=6525e910ad53953fa16028f0fce74b1a19855733)", "multimap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -672,17 +686,16 @@ dependencies = [ "rsa-der 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", - "shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "simdeez 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "simdnoise 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "simple_logger 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -948,15 +961,6 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "hibitset" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "atom 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "http" version = "0.1.18" @@ -1065,6 +1069,14 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "itertools" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.8.0" @@ -1103,6 +1115,24 @@ dependencies = [ "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "legion" +version = "0.1.1" +source = "git+https://github.com/TomGillen/legion?rev=2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b#2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b" +dependencies = [ + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "downcast-rs 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "shrinkwraprs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "libc" version = "0.2.62" @@ -1596,6 +1626,14 @@ name = "proc-macro-nested" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro2" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "0.3.8" @@ -1625,6 +1663,14 @@ name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "quote" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "quote" version = "0.5.2" @@ -2079,22 +2125,16 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "shred" -version = "0.9.3" +name = "shrinkwraprs" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "shrev" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "simdeez" version = "0.6.4" @@ -2151,22 +2191,6 @@ name = "sourcefile" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "specs" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", - "hibitset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "shred 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)", - "shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "tuple_utils 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "spin" version = "0.5.2" @@ -2177,6 +2201,11 @@ name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "stream-cipher" version = "0.3.2" @@ -2219,6 +2248,16 @@ name = "subtle" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "syn" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "syn" version = "0.13.11" @@ -2467,6 +2506,27 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "tonks" +version = "0.1.0" +source = "git+https://github.com/feather-rs/tonks#5151057974bfd22317ef64b0e1eb455a0aa80442" +dependencies = [ + "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "legion 0.1.1 (git+https://github.com/TomGillen/legion?rev=2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b)", + "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "tower-make" version = "0.3.0-alpha.2a" @@ -2516,11 +2576,6 @@ name = "try-lock" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "tuple_utils" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "typenum" version = "1.11.2" @@ -2832,13 +2887,15 @@ dependencies = [ "checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" "checksum approx 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" +"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" "checksum as-slice 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "293dac66b274fab06f95e7efb05ec439a6b70136081ea522d270bc351ae5bb27" -"checksum atom 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3c86699c3f02778ec07158376991c8f783dd1f2f95c579ffaf0738dc984b2fe2" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)" = "690a62be8920ccf773ee00ef0968649b0e724cda8bd5b12286302b4ae955fdf5" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" "checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e84c238982c4b1e1ee668d136c510c67a13465279c0cb367ea6baf6310620a80" +"checksum bit-vec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f59bbe95d4e52a6398ec21238d31577f2b28a9d86807f06ca59d191d8440d0bb" "checksum bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8a606a02debe2813760609f57a64a2ffd27d9fdf5b2f133eaca0b248dd92cdd2" "checksum bitvec 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a993f74b4c99c1908d156b8d2e0fb6277736b0ecbd833982fd1241d39b2766a6" "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" @@ -2912,7 +2969,6 @@ dependencies = [ "checksum heapless 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f339aa7d51777fc0af6aa7cbeb277dfc6e6c029cbdeda48d0fbb92c2337f0e69" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hematite-nbt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "98b407a33bb1715a4cf0276edfe8df52352c55b2a3703c5079adedf398b92932" -"checksum hibitset 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "47e7292fd9f7fe89fa35c98048f2d0a69b79ed243604234d18f6f8a1aa6f408d" "checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" "checksum http-body 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f3aef6f3de2bd8585f5b366f3f550b5774500b4764d00cf00f903c95749eec3" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" @@ -2923,11 +2979,13 @@ dependencies = [ "checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" "checksum indexmap 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a61202fbe46c4a951e9404a720a0180bcf3212c750d735cb5c4ba4dc551299f3" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" +"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +"checksum legion 0.1.1 (git+https://github.com/TomGillen/legion?rev=2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b)" = "" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" @@ -2982,10 +3040,12 @@ dependencies = [ "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro-hack 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "114cdf1f426eb7f550f01af5f53a33c0946156f6814aec939b3bd77e844f9a9d" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" +"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" +"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" @@ -3035,8 +3095,7 @@ dependencies = [ "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" -"checksum shred 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d15d46c92f8c0aed110a132f3c68a8cdd390048f51fa547c89dc571ba1e01191" -"checksum shrev 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5752e017e03af9d735b4b069f53b7a7fd90fefafa04d8bd0c25581b0bff437f" +"checksum shrinkwraprs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7d5f047b90b2ca2d1526ff73d67cba61f86f4cf9a8afddc99dd96702ded8e684" "checksum simdeez 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4204ae48b2a871f428dc20426f005be250413fa6263e6f3d93094a36b29504dc" "checksum simdnoise 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86a9e4c1c3369eab7105ac7e1582a601942fed0a63877cb2e1afcf57f34ed7b3" "checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" @@ -3045,15 +3104,16 @@ dependencies = [ "checksum slotmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "759fd553261805f128e2900bf69ab3d034260bc338caf7f0ee54dbf035c85acd" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" -"checksum specs 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4943fde8c5d3d14c3d19d2a4c7abbd7b626c270a19e6cd35252294a48feb698c" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" "checksum stream-cipher 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" "checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" "checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" "checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" +"checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5" "checksum syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)" = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" @@ -3075,13 +3135,13 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" "checksum tracing-attributes 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3ff978fd9c9afe2cc9c671e247713421c6406b3422305cbdce5de695d3ab4c3c" "checksum tracing-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "528c8ebaaa16cdac34795180b046c031775b0d56402704d98c096788f33d646a" "checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" -"checksum tuple_utils 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44834418e2c5b16f47bedf35c28e148db099187dd5feee6367fb2525863af4f1" "checksum typenum 1.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" "checksum unicase 2.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2e2e6bd1e59e56598518beb94fd6db628ded570326f0a98c679a304bd9f00150" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" diff --git a/server/Cargo.toml b/server/Cargo.toml index 1071f1790..1f2d0ee63 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,6 +16,8 @@ path = "src/main.rs" feather-blocks = { path = "../blocks" } feather-core = { path = "../core" } feather-item-block = { path = "../item_block" } +legion = { git = "https://github.com/TomGillen/legion", rev = "2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b" } +tonks = { git = "https://github.com/feather-rs/tonks" } crossbeam = "0.7" log = "0.4" simple_logger = "1.3" @@ -36,9 +38,7 @@ hashbrown = { version = "0.6", features = ["rayon"] } mojang-api = { git = "https://github.com/caelunshun/mojang-api-rs", rev = "6525e910ad53953fa16028f0fce74b1a19855733" } multimap = "0.6" hematite-nbt = "0.4" -specs = { version = "0.15", features = ["storage-event-control"] } rayon = "1.2" -shrev = "1.1" failure = "0.1" num-derive = "0.3" num-traits = "0.2" @@ -74,4 +74,4 @@ name = "worldgen" harness = false [features] -nightly = ["specs/nightly", "parking_lot/nightly"] +nightly = ["hashbrown/nightly", "parking_lot/nightly"] diff --git a/server/src/blocks/falling.rs b/server/src/blocks/falling.rs deleted file mode 100644 index d048f9b13..000000000 --- a/server/src/blocks/falling.rs +++ /dev/null @@ -1,70 +0,0 @@ -use shrev::ReaderId; -use specs::shrev::EventChannel; -use specs::{Builder, Entities, LazyUpdate, Read, System, Write}; - -use feather_core::world::ChunkMap; - -use feather_blocks::{Block, BlockExt}; - -use crate::blocks::{BlockNotifyEvent, BlockUpdateCause, BlockUpdateEvent}; -use crate::entity::{falling_block, PositionComponent, VelocityComponent}; -use feather_core::Position; - -/// This system listens to `BlockNotifyEvent`s. -#[derive(Default)] -pub struct FallingBlockCreationSystem { - reader: Option>, -} - -impl<'a> System<'a> for FallingBlockCreationSystem { - type SystemData = ( - Read<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, ChunkMap>, - Read<'a, LazyUpdate>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (events, mut block_update, mut chunk_map, lazy, entities) = data; - - // Process events - for event in events.read(&mut self.reader.as_mut().unwrap()) { - match event.block { - Block::Sand | Block::RedSand | Block::Gravel => { - let mut below = event.pos; - below.y -= 1; - - if !chunk_map.block_at(below).unwrap_or(Block::Air).is_solid() { - chunk_map.set_block_at(event.pos, Block::Air).unwrap(); - - let update_event = BlockUpdateEvent { - cause: BlockUpdateCause::FallingBlock, - pos: event.pos, - old_block: event.block, - new_block: Block::Air, - }; - - block_update.single_write(update_event); - - let mut entity_pos: Position = event.pos.world_pos(); - // Center position on block - entity_pos.x += 0.5; - entity_pos.z += 0.5; - - falling_block::create(&lazy, &entities, event.block, entity_pos) - .with(PositionComponent { - current: entity_pos, - previous: entity_pos, - }) - .with(VelocityComponent::default()) - .build(); - } - } - _ => (), - } - } - } - - setup_impl!(reader); -} diff --git a/server/src/blocks/mod.rs b/server/src/blocks/mod.rs deleted file mode 100644 index 67d43a4f0..000000000 --- a/server/src/blocks/mod.rs +++ /dev/null @@ -1,128 +0,0 @@ -mod falling; - -pub use falling::FallingBlockCreationSystem; - -use shrev::{EventChannel, ReaderId}; -use specs::{DispatcherBuilder, Entity, Read, System, Write}; - -use feather_blocks::Block; -use feather_core::world::{BlockPosition, ChunkMap}; - -use crate::systems::{BLOCK_FALLING_CREATION, BLOCK_UPDATE_PROPAGATE}; -use hashbrown::HashSet; - -lazy_static! { - /// List of block types that need to be notified - /// of adjacent block updates. - static ref BLOCKS_TO_NOTIFY: HashSet = { - let mut set = HashSet::new(); - // Falling blocks - set.insert(Block::Sand); - set.insert(Block::RedSand); - set.insert(Block::Gravel); - set - }; -} - -/// Event triggered when a block is updated. -/// -/// This event is triggered *after* the block is updated -/// in the chunk map. -#[derive(Debug, Clone)] -pub struct BlockUpdateEvent { - /// The cause of this block update event. - pub cause: BlockUpdateCause, - /// The location of the block which was updated. - pub pos: BlockPosition, - /// The block which was previously at the position. - pub old_block: Block, - /// The new block at the position. - pub new_block: Block, -} - -/// The possible causes of a block update event. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum BlockUpdateCause { - /// Indicates that a player updated the block. - Player(Entity), - /// Indicates that a falling block updated the block. - FallingBlock, - /// A test block update caused, used for unit testing. - Test, -} - -#[derive(Debug, Clone)] -pub struct BlockNotifyEvent { - pub block: Block, - pub pos: BlockPosition, - pub notified_by: BlockPosition, -} - -/// System for propagating block update -/// events to surrounding blocks. -/// -/// This system listens to `BlockUpdateEvent`s. -#[derive(Default)] -pub struct BlockUpdatePropagateSystem { - reader: Option>, -} - -impl<'a> System<'a> for BlockUpdatePropagateSystem { - type SystemData = ( - Read<'a, EventChannel>, - Read<'a, ChunkMap>, - Write<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (events, chunk_map, mut notify) = data; - - // Process events - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let mut notify_events: Vec = Vec::new(); - - for x in -1..=1 { - for y in -1..=1 { - for z in -1..=1 { - let mut adjacent = event.pos; - adjacent.x += x; - adjacent.y += y; - adjacent.z += z; - let block = chunk_map.block_at(adjacent); - if let Some(block) = block { - if BLOCKS_TO_NOTIFY.contains(&block) { - notify_events.push(BlockNotifyEvent { - block, - pos: adjacent, - notified_by: event.pos, - }); - } - } - } - } - } - - // Notify all adjacent blocks at once - notify.drain_vec_write(&mut notify_events); - } - } - - setup_impl!(reader); -} - -pub fn init_logic(_dispatcher: &mut DispatcherBuilder) { - // TODO -} - -pub fn init_handlers(dispatcher: &mut DispatcherBuilder) { - dispatcher.add( - BlockUpdatePropagateSystem::default(), - BLOCK_UPDATE_PROPAGATE, - &[], - ); - dispatcher.add( - FallingBlockCreationSystem::default(), - BLOCK_FALLING_CREATION, - &[BLOCK_UPDATE_PROPAGATE], - ); -} diff --git a/server/src/entity/broadcast.rs b/server/src/entity/broadcast.rs deleted file mode 100644 index b2444f92e..000000000 --- a/server/src/entity/broadcast.rs +++ /dev/null @@ -1,201 +0,0 @@ -//! Module for broadcasting when an entity comes within -//! range of a player. Also handles sending the correct -//! packet to spawn entities on the client. -//! -//! Sending entities to a client is handled lazily -//! through `LazyUpdate`, because arbitrary components -//! may need to be accessed. - -use crate::chunk_logic::ChunkHolders; -use crate::entity::{LastKnownPositionComponent, PacketCreatorComponent}; -use crate::entity::{Metadata, PositionComponent}; -use crate::lazy::LazyUpdateExt; -use crate::network::{send_packet_boxed_to_player, send_packet_to_player, NetworkComponent}; -use feather_core::network::packet::implementation::PacketEntityMetadata; -use shrev::EventChannel; -use specs::{Entity, LazyUpdate, Read, ReadStorage, ReaderId, System, WorldExt}; - -/// An entity send request, containing -/// the player to send to and the entity -/// to send. -#[derive(Debug)] -struct SendRequest { - player: Entity, - entity: Entity, -} - -/// Event which is triggered when an entity -/// is sent to a client. This can be used to send -/// associated information, such as entity equipment. -#[derive(Debug, Clone)] -pub struct EntitySendEvent { - /// The player for which this event was triggered. - pub player: Entity, - /// The entity which was sent to the player. - pub entity: Entity, -} - -/// Event triggered when an entity of any -/// type is spawned. -#[derive(Debug, Clone)] -pub struct EntitySpawnEvent { - /// The spawned entity. - pub entity: Entity, -} - -/// System for broadcasting when an entity is spawned. -/// -/// Broadcasts are lazily queued for sending -/// and are sent by `EntitySendSystem`. -/// -/// This system listens to `EntitySpawnEvent`s. -#[derive(Default)] -pub struct EntityBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for EntityBroadcastSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, ChunkHolders>, - Read<'a, EventChannel>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, networks, chunk_holders, spawn_events, lazy) = data; - - for event in spawn_events.read(self.reader.as_mut().unwrap()) { - // Broadcast entity to players who can see it. - let position = match positions.get(event.entity) { - Some(position) => position, - None => continue, - }; - let chunk = position.current.chunk_pos(); - - if let Some(holders) = chunk_holders.holders_for(chunk) { - for holder in holders { - if networks.get(*holder).is_none() { - // Not a player. - continue; - } - - // Don't send player to themself. - if *holder == event.entity { - continue; - } - - lazy.send_entity_to_player(*holder, event.entity); - } - } - } - } - - setup_impl!(reader); -} - -/// Lazily sends an entity to a player. -pub fn send_entity_to_player(lazy: &LazyUpdate, player: Entity, entity: Entity) { - lazy.exec(move |world| { - // Attempt to get the `PacketCreator` for the entity. - // If it doesn't exist, skip sending. - let packet_creators = world.read_component::(); - let packet_creator = match packet_creators.get(entity) { - Some(packet_creator) => packet_creator, - None => return, - }; - - let create_packet = packet_creator.0; - let packet = create_packet(world, entity); - - if let Some(network) = world.read_component::().get(player) { - send_packet_boxed_to_player(network, packet); - - // If the entity has metadata, send it. - let metas = world.read_component::(); - if let Some(meta) = metas.get(entity) { - let packet = PacketEntityMetadata { - entity_id: entity.id() as i32, - metadata: meta.to_full_raw_metadata(), - }; - send_packet_to_player(network, packet); - } - } - - // Insert last known position - let positions = world.read_component::(); - let mut last_positions = world.write_component::(); - if let Some(last_positions) = last_positions.get_mut(player) { - if let Some(pos) = positions.get(entity) { - last_positions.0.insert(entity, pos.current); - } - } - - // Trigger event - let event = EntitySendEvent { entity, player }; - world.fetch_mut::>().single_write(event); - }); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::{item, VelocityComponent}; - use crate::player::ChunkCrossSystem; - use crate::testframework as t; - use feather_core::network::cast_packet; - use feather_core::network::packet::implementation::{SpawnObject, SpawnPlayer}; - use feather_core::network::packet::PacketType; - use feather_core::{Item, ItemStack}; - use specs::{Builder, WorldExt}; - - #[test] - fn test_spawn_player() { - let (mut w, mut d) = t::init_world(); - - let player1 = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - let event = EntitySpawnEvent { - entity: player1.entity, - }; - - w.fetch_mut::>().single_write(event); - - d.dispatch(&w); - w.maintain(); - - t::assert_packet_not_received(&player1, PacketType::SpawnPlayer); // Player shouldn't have received packet for themselves - - let packet = t::assert_packet_received(&player2, PacketType::SpawnPlayer); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.entity_id, player1.entity.id() as i32); - } - - #[test] - fn test_spawn_item() { - let (mut w, mut d) = t::builder() - .with(EntityBroadcastSystem::default(), "broadcast") - .with(ChunkCrossSystem::default(), "chunk_cross") - .build(); - - let player = t::add_player(&mut w); - - let item = item::create(&w.fetch(), &w.fetch(), ItemStack::new(Item::Stone, 1), 0) - .with(PositionComponent::default()) - .with(VelocityComponent::default()) - .build(); - - w.maintain(); - d.dispatch(&w); - w.maintain(); - - let spawn_entity = t::assert_packet_received(&player, PacketType::SpawnObject); - let spawn_entity = cast_packet::(&*spawn_entity); - - assert_eq!(spawn_entity.entity_id, item.id() as i32); - assert_eq!(spawn_entity.velocity_x, 0); - } -} diff --git a/server/src/entity/chunk.rs b/server/src/entity/chunk.rs deleted file mode 100644 index 22490f8c9..000000000 --- a/server/src/entity/chunk.rs +++ /dev/null @@ -1,446 +0,0 @@ -//! Maintains a list of entities which are in each -//! chunk, which allows for more efficient nearby -//! entity queries and packet broadcasting. - -use crate::chunk_logic::ChunkLoadEvent; -use crate::entity::{ - arrow, chicken, cow, donkey, horse, item, llama, mooshroom, pig, rabbit, sheep, squid, - EntityDestroyEvent, EntitySpawnEvent, PositionComponent, -}; -use crate::TickCount; -use feather_core::entity::EntityData; -use feather_core::world::ChunkPosition; -use hashbrown::{HashMap, HashSet}; -use shrev::EventChannel; -use specs::storage::ComponentEvent; -use specs::{ - BitSet, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, ReaderId, System, World, - WorldExt, Write, -}; -use std::sync::atomic::{AtomicBool, Ordering}; - -/// Keeps track of which entities are in which chunk. -/// Also has a boolean for each chunk which indicates -/// whether its entities have been updated recently. -#[derive(Debug, Deref, DerefMut, Default)] -pub struct ChunkEntities(HashMap)>); - -lazy_static! { - static ref EMPTY_VEC: Vec = Vec::with_capacity(0); -} - -impl ChunkEntities { - /// Returns all entities in a given chunk. - pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &Vec { - if let Some((_, entities)) = self.0.get(&chunk) { - entities - } else { - &EMPTY_VEC - } - } - - /// Returns all entities in the chunk, in addition to - /// a boolean indicating whether the entities have been - /// updated since the last call to this function. - pub fn entities_in_chunk_and_modified(&self, chunk: ChunkPosition) -> (bool, &[Entity]) { - if let Some((dirty, entities)) = self.0.get(&chunk) { - let d = dirty.load(Ordering::SeqCst); - dirty.store(false, Ordering::SeqCst); - (d, entities) - } else { - (false, &[]) - } - } - - /// Adds an entity to a chunk. - pub fn add_to_chunk(&mut self, chunk: ChunkPosition, entity: Entity) { - self.0 - .entry(chunk) - .and_modify(|(dirty, vec)| { - dirty.store(true, Ordering::SeqCst); - vec.push(entity) - }) - .or_insert_with(|| (AtomicBool::new(true), vec![entity])); - } - - /// Removes an entity from a chunk. - /// - /// # Panics - /// May panic in some cases if the entity is not contained - /// within the given chunk. - pub fn remove_from_chunk(&mut self, chunk: ChunkPosition, entity: Entity) { - let (dirty, vec) = match self.0.get_mut(&chunk) { - Some(vec) => vec, - _ => return, - }; - - let (index, _) = match vec.iter().enumerate().find(|x| *x.1 == entity) { - Some(index) => index, - None => return, - }; - vec.swap_remove(index); - - dirty.store(true, Ordering::SeqCst); - - if vec.is_empty() { - self.0.remove(&chunk); - } - } - - /// Returns a vector of all entities in all chunks - /// within the given view distance of another chunk. - pub fn entites_within_view_distance( - &self, - chunk: ChunkPosition, - view_distance: u8, - ) -> HashSet { - let mut result = HashSet::new(); - - // 1 is subtracted from the view distance because of some odd - // client-side glitch (or maybe it's our fault?) where the last chunk within the view distance - // is not loaded correctly. - let view_distance = i32::from(view_distance) - 1; - - for x_offset in -view_distance..=view_distance { - for z_offset in -view_distance..=view_distance { - let chunk = ChunkPosition::new(chunk.x + x_offset, chunk.z + z_offset); - - result.extend(self.entities_in_chunk(chunk)); - } - } - - result - } -} - -/// System for updating the `ChunkEntities`. -/// -/// This system listens to `EntityMoveEvent`s, `EntitySpawnEvent`s, -/// and `EntityDestroyEvent`s. -#[derive(Default)] -pub struct ChunkEntityUpdateSystem { - dirty: BitSet, - move_reader: Option>, - spawn_reader: Option>, - destroy_reader: Option>, -} - -impl<'a> System<'a> for ChunkEntityUpdateSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - Write<'a, ChunkEntities>, - Read<'a, EventChannel>, - Read<'a, EventChannel>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, mut entity_chunks, spawn_events, destroy_events, entities) = data; - - self.dirty.clear(); - for event in positions.channel().read(self.move_reader.as_mut().unwrap()) { - if let ComponentEvent::Modified(id) = event { - self.dirty.add(*id); - } - } - - for (position, entity, _) in (&positions, &entities, &self.dirty).join() { - let new_pos = position.current.chunk_pos(); - let old_pos = position.previous.chunk_pos(); - - if new_pos != old_pos { - entity_chunks.remove_from_chunk(old_pos, entity); - entity_chunks.add_to_chunk(new_pos, entity); - } - } - - for event in spawn_events.read(self.spawn_reader.as_mut().unwrap()) { - if let Some(pos) = positions.get(event.entity) { - entity_chunks.add_to_chunk(pos.current.chunk_pos(), event.entity); - } - } - - for event in destroy_events.read(self.destroy_reader.as_mut().unwrap()) { - if let Some(pos) = positions.get(event.entity) { - entity_chunks.remove_from_chunk(pos.current.chunk_pos(), event.entity); - } - } - } - - fn setup(&mut self, world: &mut World) { - use specs::SystemData; - - Self::SystemData::setup(world); - - self.move_reader = Some( - world - .write_component::() - .register_reader(), - ); - self.destroy_reader = Some(world.fetch_mut::>().register_reader()); - self.spawn_reader = Some(world.fetch_mut::>().register_reader()); - } -} - -/// System for spawning entities inside newly-loaded chunks. -/// -/// This system listens to `ChunkLoadEvent`s. -#[derive(Default)] -pub struct EntityChunkLoadSystem { - reader: Option>, -} - -impl<'a> System<'a> for EntityChunkLoadSystem { - type SystemData = ( - Read<'a, EventChannel>, - Read<'a, LazyUpdate>, - Entities<'a>, - Read<'a, TickCount>, - ); - - #[allow(clippy::cognitive_complexity)] // Big match statement. Necessary - fn run(&mut self, data: Self::SystemData) { - let (load_events, lazy, entities, tick) = data; - - for event in load_events.read(self.reader.as_mut().unwrap()) { - for entity in &event.entities { - match entity { - EntityData::Item(item_data) => { - if item::create_from_data(&lazy, &entities, item_data, &tick).is_none() { - debug!("Error while loading item entity"); - } - } - EntityData::Arrow(arrow_data) => { - if arrow::create_from_data(&lazy, &entities, arrow_data).is_none() { - debug!("Error while loading arrow entity"); - } - } - EntityData::Cow(data) => { - if cow::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading cow entity") - } - } - EntityData::Pig(data) => { - if pig::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading pig entity") - } - } - EntityData::Chicken(data) => { - if chicken::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading chicken entity") - } - } - EntityData::Sheep(data) => { - if sheep::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading sheep entity") - } - } - EntityData::Horse(data) => { - if horse::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading horse entity") - } - } - EntityData::Llama(data) => { - if llama::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading llama entity") - } - } - EntityData::Mooshroom(data) => { - if mooshroom::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading mooshroom entity") - } - } - EntityData::Rabbit(data) => { - if rabbit::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading rabbit entity") - } - } - EntityData::Squid(data) => { - if squid::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading squid entity") - } - } - EntityData::Donkey(data) => { - if donkey::create_from_data(&lazy, &entities, data).is_none() { - debug!("Error while loading donkey entity") - } - } - // TODO: Spawn remaining entity types here. - EntityData::Unknown => { - trace!("Chunk {:?} contains an unknown entity type", event.pos); - } - } - } - } - } - - setup_impl!(reader); -} - -// Tests here cannot use the `testframework::add_entity` function -// because it automatically adds a ChunkEntities entry for the entity. -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::{test, ArrowComponent, ItemComponent}; - use crate::testframework as t; - use feather_core::entity::{ArrowEntityData, ItemEntityData}; - use specs::{Builder, World, WorldExt}; - - #[test] - fn test_chunk_entities() { - let mut chunks = ChunkEntities::default(); - - let mut world = World::new(); - let entity = world.create_entity().build(); - - let pos = ChunkPosition::new(0, 0); - - chunks.add_to_chunk(pos, entity); - assert_eq!(chunks.entities_in_chunk(pos).as_slice(), &[entity]); - assert!(chunks.entities_in_chunk_and_modified(pos).0); - assert!(!chunks.entities_in_chunk_and_modified(pos).0); - } - - #[test] - fn test_new_entity() { - let (mut w, mut d) = t::builder() - .with(ChunkEntityUpdateSystem::default(), "") - .build(); - - let pos = position!(1.0, 64.0, 1003.5); - let entity = w - .create_entity() - .with(PositionComponent { - current: pos, - previous: pos, - }) - .build(); - - let event = EntitySpawnEvent { entity }; - t::trigger_event(&w, event); - - d.dispatch(&w); - w.maintain(); - - let chunk_entities = w.fetch::(); - assert_eq!( - chunk_entities.entities_in_chunk(pos.chunk_pos()).as_slice(), - &[entity] - ); - } - - #[test] - fn test_moved_entity() { - let (mut w, mut d) = t::builder() - .with(ChunkEntityUpdateSystem::default(), "") - .build(); - - let pos = position!(1.0, 64.0, -14.0); - let old_pos = position!(1.0, 64.0, -18.0); - - let entity = w - .create_entity() - .with(PositionComponent { - current: old_pos, - previous: old_pos, - }) - .build(); - - // Trigger flagged storage event. - w.write_component::() - .get_mut(entity) - .unwrap() - .current = pos; - - d.dispatch(&w); - w.maintain(); - - let chunk_entities = w.fetch::(); - assert!(chunk_entities - .entities_in_chunk(old_pos.chunk_pos()) - .is_empty()); - assert_eq!( - chunk_entities.entities_in_chunk(pos.chunk_pos()).as_slice(), - &[entity] - ); - } - - #[test] - fn test_destroyed_entity() { - let (mut w, mut d) = t::builder() - .with(ChunkEntityUpdateSystem::default(), "") - .build(); - - let pos = position!(100.0, -100.0, -100.0); - let entity = test::create(&mut w, pos).build(); - - let event = EntityDestroyEvent { entity }; - t::trigger_event(&w, event); - - d.dispatch(&w); - w.maintain(); - - let chunk_entities = w.fetch::(); - assert!(chunk_entities.entities_in_chunk(pos.chunk_pos()).is_empty()); - } - - #[test] - fn test_entities_within_view_distance() { - let mut chunk_entities = ChunkEntities::default(); - - let mut world = World::new(); - let entity1 = world.create_entity().build(); - let entity2 = world.create_entity().build(); - let entity3 = world.create_entity().build(); - let entity4 = world.create_entity().build(); - - let chunk1 = ChunkPosition::new(0, 0); - let chunk2 = ChunkPosition::new(0, 3); - let chunk3 = ChunkPosition::new(0, 4); - let chunk4 = ChunkPosition::new(-3, -3); - - chunk_entities.add_to_chunk(chunk1, entity1); - chunk_entities.add_to_chunk(chunk2, entity2); - chunk_entities.add_to_chunk(chunk3, entity3); - chunk_entities.add_to_chunk(chunk4, entity4); - - let view_distance = 4; - let entities = chunk_entities.entites_within_view_distance(chunk1, view_distance); - - assert!(entities.contains(&entity1)); - assert!(entities.contains(&entity2)); - assert!(!entities.contains(&entity3)); - assert!(entities.contains(&entity4)); - } - - #[test] - fn test_entities_loaded_in_chunk() { - let (mut w, mut d) = t::builder() - .with(EntityChunkLoadSystem::default(), "") - .build(); - - let entities = vec![ - EntityData::Item(ItemEntityData::default()), - EntityData::Arrow(ArrowEntityData::default()), - ]; - let pos = ChunkPosition::new(1, 2); - - let mut entity_spawn_reader = t::reader(&w); - let load_event = ChunkLoadEvent { pos, entities }; - t::trigger_event(&w, load_event); - - d.dispatch(&w); - w.maintain(); - d.dispatch(&w); - w.maintain(); - - // Confirm two entities were created: one arrow, one item - let mut events = t::triggered_events::(&w, &mut entity_spawn_reader); - - let first = events.remove(0).entity; - let second = events.remove(0).entity; - assert!(w.read_component::().contains(first)); - assert!(w.read_component::().contains(second)); - } -} diff --git a/server/src/entity/component.rs b/server/src/entity/component.rs deleted file mode 100644 index 35e2cf1b1..000000000 --- a/server/src/entity/component.rs +++ /dev/null @@ -1,117 +0,0 @@ -//! Various Specs components. - -use feather_core::entity::EntityData; -use feather_core::world::Position; -use feather_core::{Gamemode, Packet}; -use glm::DVec3; -use specs::storage::BTreeStorage; -use specs::{Component, Entity, FlaggedStorage, Join, System, VecStorage, World, WriteStorage}; -use uuid::Uuid; - -pub struct PlayerComponent { - pub profile_properties: Vec, - pub gamemode: Gamemode, -} - -impl Component for PlayerComponent { - type Storage = BTreeStorage; -} - -#[derive(Default, Debug, PartialEq, Clone, Copy)] -pub struct PositionComponent { - /// The current position of this entity. - pub current: Position, - /// The position of this entity on the previous - /// tick. At the end of each tick, `reset` should - /// be called. - pub previous: Position, -} - -impl PositionComponent { - /// Resets the current and previous position. - /// Should be called at the end of every tick. - pub fn reset(&mut self) { - self.previous = self.current; - } -} - -impl Component for PositionComponent { - type Storage = FlaggedStorage>; -} - -/// An entity's velocity, in blocks per tick. -/// -/// Entities without this component are assumed -/// to have a velocity of 0. -#[derive(Deref, DerefMut, Debug, PartialEq, Clone, Copy)] -pub struct VelocityComponent(pub DVec3); - -impl Component for VelocityComponent { - type Storage = FlaggedStorage>; -} - -impl Default for VelocityComponent { - fn default() -> Self { - Self(glm::vec3(0.0, 0.0, 0.0)) - } -} - -#[derive(Clone, Debug)] -pub struct NamedComponent { - pub display_name: String, - pub uuid: Uuid, -} - -impl Component for NamedComponent { - type Storage = BTreeStorage; -} - -pub trait PacketCreator: Fn(&World, Entity) -> Box + Send + Sync {} - -impl Box + Send + Sync> PacketCreator for F {} - -/// Component containing a closure which returns the packet -/// needed to spawn an entity. -/// -/// The closure requires world access because it may need to access -/// arbitrary components. -pub struct PacketCreatorComponent(pub &'static dyn PacketCreator); - -impl Component for PacketCreatorComponent { - type Storage = VecStorage; -} - -pub trait EntitySerializer: Fn(&World, Entity) -> EntityData + Send + Sync {} - -impl EntityData + Send + Sync> EntitySerializer for F {} - -/// Component containing a closure which returns the `EntityData` -/// for an entity. -/// -/// The closure requires world access because it may need to access -/// arbitrary components. -pub struct SerializerComponent(pub &'static dyn EntitySerializer); - -impl Component for SerializerComponent { - type Storage = VecStorage; -} - -/// System for resetting an entity's components -/// at the end of the tick. -pub struct ComponentResetSystem; - -impl<'a> System<'a> for ComponentResetSystem { - type SystemData = WriteStorage<'a, PositionComponent>; - - fn run(&mut self, mut positions: Self::SystemData) { - // Ensure that position update events are not triggered - // for this. See #81 - positions.set_event_emission(false); - - for position in (&mut positions).join() { - position.reset(); - } - - positions.set_event_emission(true); - } -} diff --git a/server/src/entity/destroy.rs b/server/src/entity/destroy.rs deleted file mode 100644 index 28aea7e9c..000000000 --- a/server/src/entity/destroy.rs +++ /dev/null @@ -1,82 +0,0 @@ -//! Module for broadcasting and handling entity destroy -//! events. - -use crate::util::Util; -use feather_core::network::packet::implementation::DestroyEntities; -use shrev::{EventChannel, ReaderId}; -use specs::SystemData; -use specs::{Entities, Entity, Read, System, World}; - -/// Event triggered when an entity -/// of any type is destroyed. -#[derive(Debug, Clone)] -pub struct EntityDestroyEvent { - /// Note that when this event is triggered, - /// the entity isn't actually removed from the world - /// yet. This allows systems to access the entity's - /// data before it is destroyed. - /// - /// `EntityDestroySystem` is responsible for removing - /// entities once the `EntityDestroyEvent` has been - /// handled by all readers. - pub entity: Entity, -} - -/// System for removing entities from the world when they -/// are destroyed. -#[derive(Default)] -pub struct EntityDestroySystem { - reader: Option>, -} - -impl<'a> System<'a> for EntityDestroySystem { - type SystemData = (Read<'a, EventChannel>, Entities<'a>); - - fn run(&mut self, data: Self::SystemData) { - let (events, entities) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let _ = entities.delete(event.entity); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -/// System for broadcasting when an entity is destroyed. -#[derive(Default)] -pub struct EntityDestroyBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for EntityDestroyBroadcastSystem { - type SystemData = (Read<'a, Util>, Read<'a, EventChannel>); - - fn run(&mut self, data: Self::SystemData) { - let (util, events) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let destroy_entities = DestroyEntities::new(vec![event.entity.id() as i32]); - - util.broadcast_entity_update(event.entity, destroy_entities, None); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} diff --git a/server/src/entity/impls/animal/chicken.rs b/server/src/entity/impls/animal/chicken.rs deleted file mode 100644 index a92e4b3d3..000000000 --- a/server/src/entity/impls/animal/chicken.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct ChickenComponent; - -impl Component for ChickenComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(ChickenComponent) - .with(PhysicsBuilder::for_living().bbox(0.4, 0.7, 0.4).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 7) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Chicken(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/cow.rs b/server/src/entity/impls/animal/cow.rs deleted file mode 100644 index e962c8daa..000000000 --- a/server/src/entity/impls/animal/cow.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct CowComponent; - -impl Component for CowComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(CowComponent) - .with(PhysicsBuilder::for_living().bbox(0.9, 1.4, 0.9).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 9) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Cow(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/donkey.rs b/server/src/entity/impls/animal/donkey.rs deleted file mode 100644 index c7918ffcb..000000000 --- a/server/src/entity/impls/animal/donkey.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct DonkeyComponent; - -impl Component for DonkeyComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(DonkeyComponent) - .with( - PhysicsBuilder::for_living() - .bbox(1.396_484_4, 1.6, 1.396_484_4) - .build(), - ) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 11) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Donkey(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/horse.rs b/server/src/entity/impls/animal/horse.rs deleted file mode 100644 index 7b5d6f82f..000000000 --- a/server/src/entity/impls/animal/horse.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct HorseComponent; - -impl Component for HorseComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(HorseComponent) - .with( - PhysicsBuilder::for_living() - .bbox(1.396_484_4, 1.6, 1.396_484_4) - .build(), - ) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 29) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Horse(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/llama.rs b/server/src/entity/impls/animal/llama.rs deleted file mode 100644 index 29f06e68a..000000000 --- a/server/src/entity/impls/animal/llama.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct LlamaComponent; - -impl Component for LlamaComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(LlamaComponent) - .with(PhysicsBuilder::for_living().bbox(0.9, 1.87, 0.9).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 36) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Llama(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/mod.rs b/server/src/entity/impls/animal/mod.rs deleted file mode 100644 index 89ede779b..000000000 --- a/server/src/entity/impls/animal/mod.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Implementations for animals: cows, pigs, chickens, etc. - -pub mod chicken; -pub mod cow; -pub mod donkey; -pub mod horse; -pub mod llama; -pub mod mooshroom; -pub mod pig; -pub mod rabbit; -pub mod sheep; -pub mod squid; diff --git a/server/src/entity/impls/animal/mooshroom.rs b/server/src/entity/impls/animal/mooshroom.rs deleted file mode 100644 index 830a147bf..000000000 --- a/server/src/entity/impls/animal/mooshroom.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct MooshroomComponent; - -impl Component for MooshroomComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(MooshroomComponent) - .with(PhysicsBuilder::for_living().bbox(0.9, 1.4, 0.9).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 47) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Mooshroom(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/pig.rs b/server/src/entity/impls/animal/pig.rs deleted file mode 100644 index cfa88d29d..000000000 --- a/server/src/entity/impls/animal/pig.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct PigComponent; - -impl Component for PigComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(PigComponent) - .with(PhysicsBuilder::for_living().bbox(0.9, 0.9, 0.9).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 51) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Pig(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/rabbit.rs b/server/src/entity/impls/animal/rabbit.rs deleted file mode 100644 index b247c2f83..000000000 --- a/server/src/entity/impls/animal/rabbit.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct RabbitComponent; - -impl Component for RabbitComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(RabbitComponent) - .with(PhysicsBuilder::for_living().bbox(0.4, 0.5, 0.4).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 56) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Rabbit(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/sheep.rs b/server/src/entity/impls/animal/sheep.rs deleted file mode 100644 index 934456885..000000000 --- a/server/src/entity/impls/animal/sheep.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct SheepComponent; - -impl Component for SheepComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(SheepComponent) - .with(PhysicsBuilder::for_living().bbox(0.9, 1.3, 0.9).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 58) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Sheep(AnimalData { base }) -} diff --git a/server/src/entity/impls/animal/squid.rs b/server/src/entity/impls/animal/squid.rs deleted file mode 100644 index 76bd309d7..000000000 --- a/server/src/entity/impls/animal/squid.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::entity::{ - base_data, create_mob_packet, PacketCreatorComponent, PositionComponent, SerializerComponent, - VelocityComponent, -}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use feather_core::entity::{AnimalData, EntityData}; -use feather_core::Packet; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Builder, Component, Entity, LazyUpdate, NullStorage, World}; - -#[derive(Default)] -pub struct SquidComponent; - -impl Component for SquidComponent { - type Storage = NullStorage; -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &'a EntitiesRes) -> LazyBuilder<'a> { - lazy.spawn_entity(entities) - .with(SquidComponent) - .with(PhysicsBuilder::for_living().bbox(0.8, 0.8, 0.8).build()) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &AnimalData, -) -> Option { - let position = data.base.read_position()?; - let velocity = data.base.read_velocity()?; - - Some( - create(lazy, entities) - .with(PositionComponent { - current: position, - previous: position, - }) - .with(VelocityComponent(velocity)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - create_mob_packet(world, entity, 70) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let base = base_data(world, entity); - EntityData::Squid(AnimalData { base }) -} diff --git a/server/src/entity/impls/arrow.rs b/server/src/entity/impls/arrow.rs deleted file mode 100644 index a9754583f..000000000 --- a/server/src/entity/impls/arrow.rs +++ /dev/null @@ -1,168 +0,0 @@ -use shrev::EventChannel; -use specs::{ - Builder, Component, Entities, Entity, LazyUpdate, NullStorage, Read, ReaderId, System, World, - WorldExt, -}; - -use feather_core::packet::SpawnObject; -use feather_core::{Item, Packet, Position}; - -use crate::entity::component::{PacketCreatorComponent, SerializerComponent}; -use crate::entity::metadata::Metadata; -use crate::entity::movement::degrees_to_stops; -use crate::entity::{PositionComponent, VelocityComponent}; -use crate::lazy::LazyUpdateExt; -use crate::physics::PhysicsBuilder; -use crate::player::PLAYER_EYE_HEIGHT; -use crate::util::protocol_velocity; -use feather_core::entity::{ArrowEntityData, BaseEntityData, EntityData}; -use specs::world::{EntitiesRes, LazyBuilder}; -use uuid::Uuid; - -/// Component for arrow entities. -#[derive(Default)] -pub struct ArrowComponent; - -impl Component for ArrowComponent { - type Storage = NullStorage; -} - -/// Event triggered when arrow is shot. -#[derive(Debug, Clone)] -pub struct ShootArrowEvent { - pub arrow_type: Item, - pub shooter: Option, - pub position: Position, - pub critical: bool, -} - -#[derive(Default)] -pub struct ShootArrowSystem { - reader: Option>, -} - -impl<'a> System<'a> for ShootArrowSystem { - type SystemData = ( - Read<'a, LazyUpdate>, - Read<'a, EventChannel>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (lazy, shoot_arrow_events, entities) = data; - - for event in shoot_arrow_events.read(self.reader.as_mut().unwrap()) { - let mut pos = event.position - + glm::vec3(0.0, PLAYER_EYE_HEIGHT, 0.0) - + event.position.direction() * 1.5; - pos.on_ground = false; - - // TODO: Scale velocity based on power - let velocity = pos.direction(); - - // TODO: shooter - - create(&lazy, &entities, false) - .with(PositionComponent { - current: pos, - previous: pos, - }) - .with(VelocityComponent(velocity)) - .build(); - } - } - - setup_impl!(reader); -} - -pub fn create<'a>(lazy: &'a LazyUpdate, entities: &EntitiesRes, critical: bool) -> LazyBuilder<'a> { - let meta = { - let mut meta_arrow = crate::entity::metadata::Arrow::default(); - let mask = if critical { - crate::entity::metadata::ArrowBitMask::CRITICAL - } else { - crate::entity::metadata::ArrowBitMask::default() - }; - meta_arrow.set_arrow_bit_mask(mask.bits()); - // meta_arrow.set_shooter(shooter); TODO - Metadata::Arrow(meta_arrow) - }; - - lazy.spawn_entity(entities) - .with(ArrowComponent) - .with( - PhysicsBuilder::new() - .bbox(0.5, 0.5, 0.5) - .gravity(-0.05) - .drag(0.99) - .slip_multiplier(0.0) - .build(), - ) - .with(meta) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &ArrowEntityData, -) -> Option { - let pos = data.entity.read_position()?; - let vel = data.entity.read_velocity()?; - - let critical = match data.critical { - 0 => false, - _ => true, - }; - - // TODO: load other attributes - - Some( - create(lazy, entities, critical) - .with(PositionComponent { - current: pos, - previous: pos, - }) - .with(VelocityComponent(vel)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - let positions = world.read_component::(); - let velocities = world.read_component::(); - - let position = positions.get(entity).unwrap().current; - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocities.get(entity).unwrap().0); - - let packet = SpawnObject { - entity_id: entity.id() as i32, - object_uuid: Uuid::new_v4(), // TODO - ty: 60, - x: position.x, - y: position.y, - z: position.z, - pitch: degrees_to_stops(position.pitch), - yaw: degrees_to_stops(position.yaw), - data: 1, // TODO: Shooter entity ID - velocity_x, - velocity_y, - velocity_z, - }; - - Box::new(packet) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let positions = world.read_component::(); - let velocities = world.read_component::(); - - EntityData::Arrow(ArrowEntityData { - entity: BaseEntityData::new( - positions.get(entity).unwrap().current, - velocities.get(entity).unwrap().0, - ), - critical: 0, // TODO - }) -} diff --git a/server/src/entity/impls/falling_block.rs b/server/src/entity/impls/falling_block.rs deleted file mode 100644 index 18c59a2cf..000000000 --- a/server/src/entity/impls/falling_block.rs +++ /dev/null @@ -1,142 +0,0 @@ -use shrev::ReaderId; -use specs::shrev::EventChannel; -use specs::{ - Builder, Component, DenseVecStorage, Entity, LazyUpdate, Read, ReadStorage, System, World, - WorldExt, Write, -}; - -use feather_blocks::{Block, BlockExt}; -use feather_core::packet::SpawnObject; -use feather_core::world::ChunkMap; - -use crate::blocks::{BlockUpdateCause, BlockUpdateEvent}; -use crate::entity::component::PacketCreatorComponent; -use crate::entity::metadata::Metadata; -use crate::entity::movement::degrees_to_stops; -use crate::entity::{EntityDestroyEvent, PositionComponent, VelocityComponent}; -use crate::lazy::LazyUpdateExt; -use crate::physics::{EntityPhysicsLandEvent, PhysicsBuilder}; -use crate::util::protocol_velocity; -use feather_core::{Packet, Position}; -use specs::world::{EntitiesRes, LazyBuilder}; -use uuid::Uuid; - -/// Component for falling block entities. -pub struct FallingBlockComponent { - pub block: Block, -} - -impl Default for FallingBlockComponent { - fn default() -> Self { - FallingBlockComponent { - block: Block::Stone, - } - } -} - -impl Component for FallingBlockComponent { - type Storage = DenseVecStorage; -} - -/// This system listens to `EntityPhysicsLandEvent`s. -#[derive(Default)] -pub struct FallingBlockLandSystem { - reader: Option>, -} - -/// System for handling when a falling block lands -/// on the ground, destroying the entity and setting the block. -impl<'a> System<'a> for FallingBlockLandSystem { - type SystemData = ( - Read<'a, EventChannel>, - ReadStorage<'a, FallingBlockComponent>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, ChunkMap>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (events, falling_blocks, mut destroy_events, mut block_updates, mut chunk_map) = data; - - // Process events - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let entity = event.entity; - - let falling_block = match falling_blocks.get(entity) { - Some(block) => block, - None => continue, // Not a falling block - }; - - let destroy_event = EntityDestroyEvent { entity }; - destroy_events.single_write(destroy_event); - - let pos = event.pos.block_pos(); - let old_block = chunk_map.block_at(pos).unwrap(); - chunk_map.set_block_at(pos, falling_block.block).unwrap(); - - let update_event = BlockUpdateEvent { - cause: BlockUpdateCause::FallingBlock, - pos, - old_block, - new_block: falling_block.block, - }; - - block_updates.single_write(update_event); - } - } - - setup_impl!(reader); -} - -pub fn create<'a>( - lazy: &'a LazyUpdate, - entities: &EntitiesRes, - block: Block, - position: Position, -) -> LazyBuilder<'a> { - let meta = { - let mut meta_falling_block = crate::entity::metadata::FallingBlock::default(); - meta_falling_block.set_spawn_position(position.block_pos()); - Metadata::FallingBlock(meta_falling_block) - }; - - lazy.spawn_entity(entities) - .with(FallingBlockComponent { block }) - .with( - PhysicsBuilder::new() - .gravity(-0.04) - .drag(0.98) - .bbox(0.98, 0.98, 0.98) - .build(), - ) - .with(meta) - .with(PacketCreatorComponent(&create_packet)) - //.with(SerializerComponent(&serialize)) TODO -} - -fn create_packet(world: &World, entity: Entity) -> Box { - let blocks = world.read_component::(); - let positions = world.read_component::(); - let velocities = world.read_component::(); - - let block = blocks.get(entity).unwrap().block.native_state_id(); - let position = positions.get(entity).unwrap().current; - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocities.get(entity).unwrap().0); - - let packet = SpawnObject { - entity_id: entity.id() as i32, - object_uuid: Uuid::new_v4(), - ty: 70, - x: position.x, - y: position.y, - z: position.z, - pitch: degrees_to_stops(position.pitch), - yaw: degrees_to_stops(position.yaw), - data: i32::from(block), - velocity_x, - velocity_y, - velocity_z, - }; - - Box::new(packet) -} diff --git a/server/src/entity/impls/item.rs b/server/src/entity/impls/item.rs deleted file mode 100644 index edc54b881..000000000 --- a/server/src/entity/impls/item.rs +++ /dev/null @@ -1,582 +0,0 @@ -//! Logic for working with item entities. -use crate::entity::metadata::{self, Metadata}; -use crate::entity::{ - ChunkEntities, EntityDestroyEvent, PlayerComponent, PositionComponent, VelocityComponent, -}; -use crate::physics::{nearby_entities, PhysicsBuilder}; -use crate::player::{ - InventoryComponent, InventoryUpdateEvent, PlayerItemDropEvent, PLAYER_EYE_HEIGHT, -}; -use crate::util::{protocol_velocity, Util}; -use crate::{TickCount, TPS}; -use feather_core::network::packet::implementation::CollectItem; -use feather_core::{Item, ItemStack, Packet}; -use rand::Rng; -use shrev::EventChannel; -use smallvec::SmallVec; -use specs::storage::ComponentEvent; -use specs::{ - BitSet, Builder, Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, - ReadStorage, ReaderId, System, SystemData, World, WorldExt, Write, WriteStorage, -}; - -use crate::entity::component::{PacketCreatorComponent, SerializerComponent}; -use crate::entity::movement::degrees_to_stops; -use crate::lazy::LazyUpdateExt; -use feather_core::entity::{BaseEntityData, EntityData, ItemData, ItemEntityData}; -use feather_core::packet::SpawnObject; -use specs::world::{EntitiesRes, LazyBuilder}; -use uuid::Uuid; - -/// Component for item entities. -pub struct ItemComponent { - /// The tick at which this item is collectable - /// by a player. - pub collectable_at: u64, - /// This item's stack. - pub stack: ItemStack, -} - -impl Component for ItemComponent { - type Storage = DenseVecStorage; -} - -/// System for spawning an item entity when -/// an item is dropped. -/// -/// This system listens to `PlayerItemDropEvent`s. -#[derive(Default)] -pub struct ItemSpawnSystem { - reader: Option>, -} - -impl<'a> System<'a> for ItemSpawnSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - Read<'a, LazyUpdate>, - Entities<'a>, - Read<'a, EventChannel>, - Read<'a, TickCount>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, lazy, entities, item_drop_events, tick) = data; - - let mut rng = rand::thread_rng(); - - for event in item_drop_events.read(self.reader.as_mut().unwrap()) { - // Spawn item entity. - - // Position is player's eye height minus 0.3 - let mut pos = { - let player_pos = positions.get(event.player).unwrap().current - + glm::vec3(0.0, PLAYER_EYE_HEIGHT, 0.0); - player_pos - glm::vec3(0.0f64, 0.3, 0.0) - }; - - pos.on_ground = false; - - // This velocity calculation was sourced from Glowstone's - // work. See https://github.com/GlowstoneMC/Glowstone/blob/dev/src/main/java/net/glowstone/entity/GlowHumanEntity.java - // (method drop(ItemStack stack)) for their code. - let velocity = { - let mut vel = pos.direction() * 0.3; - let rand_offset = 0.02; - - let x = rng.gen_range(0.0, rand_offset) - rand_offset / 2.0; - let y = rng.gen_range(0.0, 0.12); - let z = rng.gen_range(0.0, rand_offset) - rand_offset / 2.0; - - vel += glm::vec3(x, y, z); - - vel - }; - - create(&lazy, &entities, event.stack.clone(), tick.0 + TPS) - .with(PositionComponent { - current: pos, - previous: pos, - }) - .with(VelocityComponent(velocity)) - .build(); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some(world.fetch_mut::>().register_reader()); - } -} - -/// System for merging item entities of the same -/// type. -#[derive(Default)] -pub struct ItemMergeSystem { - dirty: BitSet, - reader: Option>, -} - -impl<'a> System<'a> for ItemMergeSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, ItemComponent>, - WriteStorage<'a, Metadata>, - Write<'a, EventChannel>, - Read<'a, ChunkEntities>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, item_markers, mut metadatas, mut destroy_events, chunk_entities, entities) = - data; - - self.dirty.clear(); - - for event in positions.channel().read(self.reader.as_mut().unwrap()) { - match event { - ComponentEvent::Modified(id) | ComponentEvent::Inserted(id) => { - self.dirty.add(*id); - } - _ => (), - } - } - - let mut metadatas_to_update: SmallVec<[(Entity, Metadata); 2]> = smallvec![]; - // Used to not destroy both entities - let mut destroyed: SmallVec<[Entity; 2]> = smallvec![]; - - for (position, entity, _, _) in (&positions, &entities, &item_markers, &self.dirty).join() { - if !entities.is_alive(entity) { - continue; - } - - if destroyed.iter().any(|x| *x == entity) { - continue; - } - - let mut stack = item_stack_from_meta(metadatas.get(entity).unwrap()); - - // Find nearby entities and check if they are of the same item - // type. If so, merge the two item stacks. - let nearby = nearby_entities( - &chunk_entities, - &positions, - position.current, - glm::vec3(1.0, 0.5, 1.0), - ); - - for other in nearby { - // Skip entity if it's dead. - if !entities.is_alive(other) { - continue; - } - - if other == entity { - continue; - } - - // Skip if it's not an item. - if item_markers.get(other).is_none() { - continue; - } - - let other_stack = item_stack_from_meta(metadatas.get(other).unwrap()); - - if other_stack.ty != stack.ty { - continue; - } - - // Merge two stacks. - // This works by deleting `other` and adding - // together the amounts of the two item stacks. - entities.delete(other).unwrap(); - - let event = EntityDestroyEvent { entity: other }; - destroy_events.single_write(event); - - // TODO this could overflow... - stack.amount += other_stack.amount; - - metadatas_to_update.push((entity, item_meta(stack.clone()))); - destroyed.push(other); - } - } - - metadatas_to_update.into_iter().for_each(|(entity, meta)| { - metadatas.insert(entity, meta).unwrap(); - }); - } - - flagged_setup_impl!(PositionComponent, reader); -} - -/// System for collecting items when a player comes -/// near them. -#[derive(Default)] -pub struct ItemCollectSystem { - dirty: BitSet, - reader: Option>, -} - -impl<'a> System<'a> for ItemCollectSystem { - type SystemData = ( - WriteStorage<'a, InventoryComponent>, - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, PlayerComponent>, - ReadStorage<'a, ItemComponent>, - WriteStorage<'a, Metadata>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Read<'a, ChunkEntities>, - Read<'a, Util>, - Read<'a, TickCount>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut inventories, - positions, - players, - items, - mut metadatas, - mut inventory_events, - mut destroy_events, - chunk_entities, - util, - tick, - entities, - ) = data; - - self.dirty.clear(); - - read_flagged_events!(positions, self.reader, self.dirty); - - // For each player who has moved this tick, - // look for nearby items. - // We need to keep track of which items - // have already been collected to avoid - // having the same item being collected - // by two players at once; this would - // cause dupe exploits. - let mut collected_items: SmallVec<[Entity; 4]> = smallvec![]; - - for (position, inventory, player, _, _) in ( - &positions, - &mut inventories, - &entities, - &players, - &self.dirty, - ) - .join() - { - let nearby = nearby_entities( - &chunk_entities, - &positions, - position.current, - glm::vec3(1.0, 0.5, 1.0), - ); - - for other in nearby { - // If it's not an item, skip. - let item = continue_if_none!(items.get(other)); - - // Check if the item can be picked up yet. - if item.collectable_at > tick.0 { - continue; - } - - // If the item has already been collected, don't try it. - if collected_items.iter().any(|x| *x == other) { - continue; - } - - // Attempt to collect the item. - let mut stack = item_stack_from_meta(metadatas.get(other).unwrap()); - let (affected_slots, amount_left) = inventory.collect_item(stack.clone()); - - // Broadcast Collect Item packet, which gives an animation. - let packet = CollectItem { - collected: other.id() as i32, - collector: player.id() as i32, - count: i32::from(stack.amount - amount_left), - }; - util.broadcast_entity_update(player, packet, None); - - if amount_left == 0 { - entities.delete(other).unwrap(); - collected_items.push(other); - - let event = EntityDestroyEvent { entity: other }; - destroy_events.single_write(event); - } else { - stack.amount = amount_left; - let meta = item_meta(stack); - metadatas.insert(other, meta).unwrap(); - } - - // Trigger inventory update event. - let event = InventoryUpdateEvent { - slots: affected_slots, - player, - }; - inventory_events.single_write(event); - } - } - } - - flagged_setup_impl!(PositionComponent, reader); -} - -pub fn create<'a>( - lazy: &'a LazyUpdate, - entities: &EntitiesRes, - stack: ItemStack, - collectable_at: u64, -) -> LazyBuilder<'a> { - let meta = { - let mut meta_item = crate::entity::metadata::Item::default(); - meta_item.set_item(Some(stack.clone())); - Metadata::Item(meta_item) - }; - - lazy.spawn_entity(entities) - .with(ItemComponent { - stack, - collectable_at, - }) - .with( - PhysicsBuilder::new() - .bbox(0.25, 0.25, 0.25) - .gravity(-0.04) - .drag(0.98) - .build(), - ) - .with(VelocityComponent::default()) - .with(meta) - .with(PacketCreatorComponent(&create_packet)) - .with(SerializerComponent(&serialize)) -} - -pub fn create_from_data( - lazy: &LazyUpdate, - entities: &EntitiesRes, - data: &ItemEntityData, - tick: &TickCount, -) -> Option { - let pos = data.entity.read_position()?; - let vel = data.entity.read_velocity()?; - - let stack = ItemStack::new(Item::from_identifier(&data.item.item)?, data.item.count); - - let collectable_at = data.pickup_delay as u64 + tick.0; - - Some( - create(lazy, entities, stack, collectable_at) - .with(PositionComponent { - current: pos, - previous: pos, - }) - .with(VelocityComponent(vel)) - .build(), - ) -} - -fn create_packet(world: &World, entity: Entity) -> Box { - let positions = world.read_component::(); - let velocities = world.read_component::(); - - let position = positions.get(entity).unwrap().current; - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocities.get(entity).unwrap().0); - - let packet = SpawnObject { - entity_id: entity.id() as i32, - object_uuid: Uuid::new_v4(), - ty: 2, // Type 2 for item stack - x: position.x, - y: position.y, - z: position.z, - pitch: degrees_to_stops(position.pitch), - yaw: degrees_to_stops(position.yaw), - data: 1, // Has velocity - velocity_x, - velocity_y, - velocity_z, - }; - - Box::new(packet) -} - -fn serialize(world: &World, entity: Entity) -> EntityData { - let positions = world.read_component::(); - let velocities = world.read_component::(); - let items = world.read_component::(); - - let item = items.get(entity).unwrap(); - let position = positions.get(entity).unwrap(); - let velocity = velocities.get(entity).unwrap(); - - EntityData::Item(ItemEntityData { - entity: BaseEntityData::new(position.current, velocity.0), - age: 0, // TODO - pickup_delay: 0, // TODO - item: ItemData { - item: item.stack.ty.identifier().to_string(), - count: item.stack.amount, - }, - }) -} - -pub fn item_stack_from_meta(meta: &Metadata) -> ItemStack { - match meta { - Metadata::Item(item) => item.item().unwrap(), - _ => panic!(), - } -} - -pub fn item_meta(stack: ItemStack) -> Metadata { - let mut item = metadata::Item::default(); - item.set_item(Some(stack)); - Metadata::Item(item) -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::{ChunkEntityUpdateSystem, EntitySpawnEvent}; - use crate::testframework as t; - use feather_core::inventory::SLOT_HOTBAR_OFFSET; - use feather_core::network::cast_packet; - use feather_core::{Item, ItemStack, PacketType}; - use specs::WorldExt; - - #[test] - fn test_item_spawn_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - t::set_entity_pos(&w, player.entity, position!(0.0, 1.0, 0.0)); - - let stack = ItemStack::new(Item::AcaciaBoat, 4); - - let mut entity_spawn_reader = t::reader(&w); - - let event = PlayerItemDropEvent { - slot: None, - stack, - player: player.entity, - }; - t::trigger_event(&w, event); - - d.dispatch(&w); - w.maintain(); - - // Confirm event was triggered - let events = t::triggered_events::(&w, &mut entity_spawn_reader); - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - let entity = first.entity; - - // Check position - let pos = t::entity_pos(&w, entity); - assert_float_eq!(pos.x, 0.0); - assert_float_eq!(pos.z, 0.0); - - // Confirm that velocity was created - let _vel = t::entity_vel(&w, entity).unwrap(); - } - - #[test] - fn test_item_merge_system() { - let (mut w, mut d) = t::builder() - .with_dep(ItemMergeSystem::default(), "item_merge", &[]) - .build(); - - let item1 = create( - &w.fetch(), - &w.fetch(), - ItemStack::new(Item::EnderPearl, 4), - 0, - ) - .with(PositionComponent::default()) - .build(); - let item2 = create( - &w.fetch(), - &w.fetch(), - ItemStack::new(Item::EnderPearl, 7), - 0, - ) - .with(PositionComponent::default()) - .build(); - - let mut updater = ChunkEntityUpdateSystem::default(); - updater.setup(&mut w); - - w.maintain(); - - // Update chunk entities so `nearby_entities` works - specs::RunNow::run_now(&mut updater, &w); - - d.dispatch(&w); - w.maintain(); - - assert!(!w.is_alive(item2)); - assert!(w.is_alive(item1)); - - let metadatas = w.read_component::(); - let metadata = metadatas.get(item1).unwrap(); - - let stack = item_stack_from_meta(&metadata); - assert_eq!(stack.ty, Item::EnderPearl); - assert_eq!(stack.amount, 11); - } - - #[test] - fn test_item_collect_system() { - let (mut w, mut d) = t::builder() - .with_dep(ItemCollectSystem::default(), "", &[]) - .build(); - - let player = t::add_player(&mut w); - let stack = ItemStack::new(Item::String, 4); - let item = create(&w.fetch(), &w.fetch(), stack.clone(), 0) - .with(PositionComponent::default()) - .build(); - - let mut destroy_reader = t::reader(&w); - - // Allow item to be collected - w.fetch_mut::().0 = 0; - - let mut updater = ChunkEntityUpdateSystem::default(); - updater.setup(&mut w); - - w.maintain(); - - // Update chunk entities so `nearby_entities` works - - specs::RunNow::run_now(&mut updater, &w); - - d.dispatch(&w); - w.maintain(); - - let destroy_events = t::triggered_events::(&w, &mut destroy_reader); - let first = destroy_events.first().unwrap(); - assert_eq!(first.entity, item); - - assert!(!w.is_alive(item)); - - let inventories = w.read_component::(); - let inventory = inventories.get(player.entity).unwrap(); - - assert_eq!(inventory.item_at(SLOT_HOTBAR_OFFSET), Some(&stack)); - - let packet = t::assert_packet_received(&player, PacketType::CollectItem); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.collector, player.entity.id() as i32); - assert_eq!(packet.collected, item.id() as i32); - assert_eq!(packet.count, 4); - } -} diff --git a/server/src/entity/impls/mod.rs b/server/src/entity/impls/mod.rs deleted file mode 100644 index f46a2d94d..000000000 --- a/server/src/entity/impls/mod.rs +++ /dev/null @@ -1,95 +0,0 @@ -//! Entity implementations. -//! -//! Every entity implementation is expected to define -//! the following functions: -//! -//! * `create(&LazyUpdate, &EntitiesRes) -> LazyBuilder`. When a system spawns an entity -//! of a known type, it should call this function on the `LazyBuilder` -//! returned by `LazyUpdate::spawn_entity` to apply components, such as markers, metadata, -//! `SerializerComponent`, and `SpawnPacketComponent`. This function may -//! take parameters. This function should not apply generic components, -//! such as position and velocity; the callee is responsible for this. -//! * `create_from_data(&LazyUpdate, &EntitiesRes, &{Entity}Data) -> Option`. Spawns an -//! entity loaded from the given entity data. If the entity creation failed, `None` is returned. -//! -//! These functions should be invoked in the form `name::function`, e.g. -//! `arrow::create` or `item::create_from_data`. -//! -//! Entity implementations should also define systems related to the entity: for -//! example, most entities will have an update system which updates an entity -//! on each tick. - -pub mod arrow; -pub mod falling_block; -pub mod item; - -mod animal; -pub use animal::*; - -use crate::entity::{ - degrees_to_stops, metadata::EMPTY_METADATA, Metadata, NamedComponent, PositionComponent, - VelocityComponent, -}; -use crate::util::protocol_velocity; -use feather_core::entity::BaseEntityData; -use feather_core::network::packet::implementation::SpawnMob; -use feather_core::Packet; -use specs::{Entity, World, WorldExt}; -use uuid::Uuid; - -#[cfg(test)] -pub mod test; - -/// Returns a `Spawn Mob` packet with the given entity type ID. -pub fn create_mob_packet(world: &World, entity: Entity, type_id: i32) -> Box { - let entity_id = entity.id() as i32; - let entity_uuid = world - .read_component::() - .get(entity) - .map(|named| named.uuid) - .unwrap_or_else(Uuid::new_v4); - - let positions = world.read_component::(); - let position = positions.get(entity).copied().unwrap_or_default(); - let velocities = world.read_component::(); - let velocity = velocities.get(entity).copied().unwrap_or_default(); - - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocity.0); - - let metadatas = world.read_component::(); - let metadata = metadatas.get(entity).unwrap_or(&EMPTY_METADATA); - - let packet = SpawnMob { - entity_id, - entity_uuid, - ty: type_id, - x: position.current.x, - y: position.current.y, - z: position.current.z, - yaw: degrees_to_stops(position.current.yaw), - pitch: degrees_to_stops(position.current.pitch), - head_pitch: degrees_to_stops(position.current.pitch), // FIXME: is this correct? - velocity_x, - velocity_y, - velocity_z, - meta: metadata.to_full_raw_metadata(), - }; - - Box::new(packet) -} - -/// Creates a `BaseEntityData` for the given entity. -pub fn base_data(world: &World, entity: Entity) -> BaseEntityData { - let position = world - .read_component::() - .get(entity) - .copied() - .unwrap_or_default(); - let velocity = world - .read_component::() - .get(entity) - .copied() - .unwrap_or_default(); - - BaseEntityData::new(position.current, velocity.0) -} diff --git a/server/src/entity/impls/test.rs b/server/src/entity/impls/test.rs deleted file mode 100644 index 21ef64b9f..000000000 --- a/server/src/entity/impls/test.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! A fake entity implementation for unit tests. - -use crate::entity::{EntitySpawnEvent, PositionComponent, VelocityComponent}; -use feather_core::Position; -use shrev::EventChannel; -use specs::{Builder, EntityBuilder, World, WorldExt}; - -pub fn create(world: &mut World, pos: Position) -> EntityBuilder { - let builder = world - .create_entity() - .with(PositionComponent { - current: pos, - previous: pos, - }) - .with(VelocityComponent(glm::vec3(0.0, 0.0, 0.0))); - builder - .world - .fetch_mut::>() - .single_write(EntitySpawnEvent { - entity: builder.entity, - }); - builder -} diff --git a/server/src/entity/metadata.rs b/server/src/entity/metadata.rs deleted file mode 100644 index 82f3d1c3f..000000000 --- a/server/src/entity/metadata.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Definition for entity metadata enum. - -#![allow(clippy::too_many_arguments)] // TODO: builder patterm - -use crate::util::Util; -use feather_core::packet::PacketEntityMetadata; -use feather_core::{BlockPosition, EntityMetadata, Slot}; -use specs::storage::ComponentEvent; -use specs::{ - BitSet, Component, Entities, FlaggedStorage, Join, Read, ReaderId, System, VecStorage, - WriteStorage, -}; -use uuid::Uuid; - -type OptUuid = Option; - -bitflags! { - pub struct EntityBitMask: u8 { - const ON_FIRE = 0x01; - const CROUCHED = 0x02; - const SPRITING = 0x08; - const SWIMMING = 0x10; - const INVISIBLE = 0x20; - const GLOWING_EFFECT = 0x40; - const FLYING_WITH_ELYTRA = 0x80; - } -} - -bitflags! { - #[derive(Default)] - pub struct ArrowBitMask: u8 { - const CRITICAL = 0x01; - const NO_CLIP = 0x02; - } -} - -lazy_static! { - pub static ref EMPTY_METADATA: Metadata = { Metadata::Entity(Entity::default()) }; -} - -entity_metadata! { - Metadata, - Entity { - bit_mask: u8() = 0, - air: VarInt() = 1, - silent: bool() = 4, - no_gravity: bool() = 5, - }, - Item: Entity { - item: Slot() = 6, - }, - Living: Entity { - hand_states: u8() = 6, - health: f32(1.0) = 7, - potion_effect_color: VarInt() = 8, - potion_effect_ambient: bool() = 9, - arrows: VarInt() = 10, - }, - Player: Living { - additional_hearts: f32() = 11, - score: VarInt() = 12, - displayed_skin_parts: u8() = 13, - main_hand: u8(1) = 14, - }, - Arrow: Entity { - arrow_bit_mask: u8() = 6, - shooter: OptUuid() = 7, - }, - TippedArrow: Arrow { - color: VarInt() = 8, - }, - FallingBlock: Entity { - spawn_position: BlockPosition() = 6, - }, -} - -impl Component for Metadata { - type Storage = FlaggedStorage>; -} - -/// System for broadcasting entity metadata updates. -#[derive(Default)] -pub struct MetadataBroadcastSystem { - dirty: BitSet, - reader: Option>, -} - -impl<'a> System<'a> for MetadataBroadcastSystem { - type SystemData = (WriteStorage<'a, Metadata>, Read<'a, Util>, Entities<'a>); - - fn run(&mut self, data: Self::SystemData) { - let (mut metadatas, util, entities) = data; - - self.dirty.clear(); - - read_flagged_events!(metadatas, self.reader, self.dirty); - - // Ensure that metadata update events are not - // triggered for this mutation of the storage. - metadatas.set_event_emission(false); - - // Go through updated metadata and broadcast changes. - for (metadata, entity, _) in (&mut metadatas, &entities, &self.dirty).join() { - let packet = PacketEntityMetadata { - entity_id: entity.id() as i32, - metadata: metadata.to_raw_metadata(), - }; - - util.broadcast_entity_update(entity, packet, None); - } - - metadatas.set_event_emission(true); - } - - flagged_setup_impl!(Metadata, reader); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::test; - use crate::testframework as t; - use feather_core::entitymeta::MetaEntry; - use feather_core::network::cast_packet; - use feather_core::PacketType; - use specs::{Builder, WorldExt}; - - #[test] - fn test_basic() { - let mut meta = Metadata::Entity(Entity::new( - (EntityBitMask::ON_FIRE | EntityBitMask::CROUCHED).bits(), - 0, - false, - false, - )); - - let raw = meta.to_raw_metadata(); - - assert_eq!(raw.get(0), Some(MetaEntry::Byte(0b0000_0011))); - assert_eq!(raw.get(5), Some(MetaEntry::Boolean(false))); - assert_eq!(raw.get(6), None); - } - - #[test] - fn test_inheritance() { - let _meta = Metadata::Item(Item::new( - (EntityBitMask::ON_FIRE).bits(), - 0, - false, - false, - None, - )); - } - - #[test] - fn test_metadata_update_system() { - let (mut w, mut d) = t::builder() - .with(MetadataBroadcastSystem::default(), "") - .build(); - - let entity = test::create(&mut w, position!(0.0, 0.0, 0.0)).build(); - - // Insert metadata - { - let mut metadatas = w.write_component::(); - metadatas - .insert(entity, Metadata::Entity(Entity::default())) - .unwrap(); - } - - let player = t::add_player(&mut w); - - d.dispatch(&w); - w.maintain(); - - // Ensure that packet was sent - let packet = t::assert_packet_received(&player, PacketType::EntityMetadata); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.entity_id, entity.id() as i32); - } -} diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs deleted file mode 100644 index 9aa2ee75c..000000000 --- a/server/src/entity/mod.rs +++ /dev/null @@ -1,103 +0,0 @@ -//! Provides several useful components, including `EntityComponent` -//! and `PlayerComponent`. In the future, will also -//! provide entity-specific components and systems. - -mod broadcast; -mod chunk; -mod component; -mod destroy; -mod impls; -pub mod metadata; -mod movement; -mod save; - -pub use impls::*; - -use crate::systems::{ - BLOCK_FALLING_LANDING, CHUNK_CROSS, CHUNK_ENTITIES_LOAD, CHUNK_ENTITIES_UPDATE, CHUNK_SAVE, - ENTITY_DESTROY, ENTITY_DESTROY_BROADCAST, ENTITY_METADATA_BROADCAST, ENTITY_MOVE_BROADCAST, - ENTITY_PHYSICS, ENTITY_SPAWN_BROADCAST, ENTITY_VELOCITY_BROADCAST, ITEM_COLLECT, ITEM_MERGE, - ITEM_SPAWN, JOIN_BROADCAST, SHOOT_ARROW, -}; -pub use arrow::{ArrowComponent, ShootArrowEvent}; -pub use broadcast::send_entity_to_player; -pub use broadcast::{EntitySendEvent, EntitySpawnEvent}; -pub use chunk::ChunkEntities; -pub use chunk::ChunkEntityUpdateSystem; -pub use component::{ - NamedComponent, PacketCreatorComponent, PlayerComponent, PositionComponent, - SerializerComponent, VelocityComponent, -}; -pub use destroy::EntityDestroyEvent; -pub use falling_block::FallingBlockComponent; -pub use item::ItemComponent; -pub use metadata::{EntityBitMask, Metadata}; -pub use movement::{degrees_to_stops, LastKnownPositionComponent}; - -pub use save::save_chunks; - -use crate::entity::arrow::ShootArrowSystem; -use crate::entity::chunk::EntityChunkLoadSystem; -use crate::entity::destroy::EntityDestroyBroadcastSystem; -use crate::entity::falling_block::FallingBlockLandSystem; -use crate::entity::item::ItemCollectSystem; -use crate::entity::metadata::MetadataBroadcastSystem; -use crate::entity::save::ChunkSaveSystem; -use broadcast::EntityBroadcastSystem; -use component::ComponentResetSystem; -use destroy::EntityDestroySystem; -use item::{ItemMergeSystem, ItemSpawnSystem}; -use movement::{EntityMoveBroadcastSystem, EntityVelocityBroadcastSystem}; -use specs::DispatcherBuilder; - -pub fn init_logic(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(ItemCollectSystem::default(), ITEM_COLLECT, &[]); -} - -pub fn init_handlers(dispatcher: &mut DispatcherBuilder) { - dispatcher.add( - ChunkEntityUpdateSystem::default(), - CHUNK_ENTITIES_UPDATE, - &[], - ); - dispatcher.add(EntityChunkLoadSystem::default(), CHUNK_ENTITIES_LOAD, &[]); - dispatcher.add(EntityDestroySystem::default(), ENTITY_DESTROY, &[]); - dispatcher.add(ItemSpawnSystem::default(), ITEM_SPAWN, &[]); - dispatcher.add(ItemMergeSystem::default(), ITEM_MERGE, &[]); - dispatcher.add( - MetadataBroadcastSystem::default(), - ENTITY_METADATA_BROADCAST, - &[], - ); - dispatcher.add(ShootArrowSystem::default(), SHOOT_ARROW, &[]); - dispatcher.add(ChunkSaveSystem::default(), CHUNK_SAVE, &[]); -} - -pub fn init_broadcast(dispatcher: &mut DispatcherBuilder) { - dispatcher.add( - EntityMoveBroadcastSystem::default(), - ENTITY_MOVE_BROADCAST, - &[], - ); - dispatcher.add( - EntityBroadcastSystem::default(), - ENTITY_SPAWN_BROADCAST, - &[JOIN_BROADCAST, CHUNK_CROSS], - ); - dispatcher.add( - EntityVelocityBroadcastSystem::default(), - ENTITY_VELOCITY_BROADCAST, - &[], - ); - dispatcher.add( - EntityDestroyBroadcastSystem::default(), - ENTITY_DESTROY_BROADCAST, - &[], - ); - dispatcher.add( - FallingBlockLandSystem::default(), - BLOCK_FALLING_LANDING, - &[ENTITY_PHYSICS], - ); - dispatcher.add_thread_local(ComponentResetSystem); -} diff --git a/server/src/entity/movement.rs b/server/src/entity/movement.rs deleted file mode 100644 index 34a4d25b9..000000000 --- a/server/src/entity/movement.rs +++ /dev/null @@ -1,264 +0,0 @@ -use specs::storage::ComponentEvent; -use specs::{ - BitSet, Component, DenseVecStorage, Entities, Entity, Join, Read, ReadStorage, ReaderId, - System, WriteStorage, -}; - -use feather_core::network::packet::implementation::{ - EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, EntityVelocity, -}; -use feather_core::world::Position; - -use crate::chunk_logic::ChunkHolders; -use crate::entity::{PositionComponent, VelocityComponent}; -use crate::network::{send_packet_boxed_to_player, NetworkComponent}; -use crate::util::{protocol_velocity, Util}; -use feather_core::Packet; -use hashbrown::HashMap; -use smallvec::SmallVec; - -/// Component which stores the last known position for any given entity -/// for a player. -/// -/// This is used to ensure position remains synced across clients, since -/// relative movement packets are used. -#[derive(Default, Debug)] -pub struct LastKnownPositionComponent(pub HashMap); - -impl Component for LastKnownPositionComponent { - type Storage = DenseVecStorage; -} - -/// System for broadcasting when an entity moves. -#[derive(Default)] -pub struct EntityMoveBroadcastSystem { - dirty: BitSet, - reader: Option>, - held: BitSet, -} - -impl<'a> System<'a> for EntityMoveBroadcastSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - WriteStorage<'a, LastKnownPositionComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, ChunkHolders>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, mut last_positions, networks, chunk_holders, entities) = data; - - self.dirty.clear(); - - for event in positions.channel().read(&mut self.reader.as_mut().unwrap()) { - match event { - ComponentEvent::Modified(index) | ComponentEvent::Inserted(index) => { - self.dirty.add(*index); - } - _ => (), - } - } - - for (position, entity, _) in (&positions, &entities, &self.dirty).join() { - // Populate `self.held` with chunk holders for this entity's chunk - for entity in chunk_holders - .holders_for(position.current.chunk_pos()) - .unwrap_or(&[]) - { - self.held.add(entity.id()); - } - - // For each player which can see this entity's chunk, send a movement update packet. - for (network, last_positions, _) in (&networks, &mut last_positions, &self.held).join() - { - let last_known_position = match last_positions.0.get(&entity) { - Some(pos) => pos, - None => continue, // Player hasn't yet known this entity - }; - - if let Some(packets) = - packet_for_movement_update(entity, *last_known_position, position.current) - { - packets - .into_iter() - .for_each(|packet| send_packet_boxed_to_player(network, packet)); - } - - last_positions.0.insert(entity, position.current); - } - - self.held.clear(); - } - } - - flagged_setup_impl!(PositionComponent, reader); -} - -/// Returns the packet needed to notify a client -/// of a position update, from the old position to the new one. -#[allow(clippy::float_cmp)] -pub fn packet_for_movement_update( - entity: Entity, - old_pos: Position, - new_pos: Position, -) -> Option; 2]>> { - if old_pos == new_pos { - return None; - } - - let mut packets = smallvec![]; - - let has_moved = old_pos.x != new_pos.x || old_pos.y != new_pos.y || old_pos.z != new_pos.z; - let has_looked = old_pos.pitch != new_pos.pitch || old_pos.yaw != new_pos.yaw; - - if has_moved { - let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); - - if (rx == 0 && ry == 0 && rz == 0) && !has_looked { - // Because of floating point errors, - // the physics system may trigger an - // event when the distance moved is minuscule, - // which causes jittering on the client. - // Don't send the packet if it has no effect. - return None; - } - - if has_looked { - let packet: Box = Box::new(EntityLookAndRelativeMove::new( - entity.id() as i32, - rx, - ry, - rz, - degrees_to_stops(new_pos.yaw), - degrees_to_stops(new_pos.pitch), - new_pos.on_ground, - )); - packets.push(packet); - } else { - let packet: Box = Box::new(EntityRelativeMove::new( - entity.id() as i32, - rx, - ry, - rz, - new_pos.on_ground, - )); - packets.push(packet); - } - } else { - let packet: Box = Box::new(EntityLook::new( - entity.id() as i32, - degrees_to_stops(new_pos.yaw), - degrees_to_stops(new_pos.pitch), - new_pos.on_ground, - )); - packets.push(packet); - } - - // Entity Head Look also needs to be sent if the entity turned its head - if has_looked { - let packet: Box = Box::new(EntityHeadLook::new( - entity.id() as i32, - degrees_to_stops(new_pos.yaw), - )); - packets.push(packet); - } - - Some(packets) -} - -/// System for broadcasting when an entity's velocity -/// is updated. -#[derive(Default)] -pub struct EntityVelocityBroadcastSystem { - dirty: BitSet, - reader: Option>, -} - -impl<'a> System<'a> for EntityVelocityBroadcastSystem { - type SystemData = ( - ReadStorage<'a, VelocityComponent>, - Read<'a, Util>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (velocities, util, entities) = data; - - self.dirty.clear(); - - for event in velocities.channel().read(self.reader.as_mut().unwrap()) { - match event { - ComponentEvent::Modified(index) | ComponentEvent::Inserted(index) => { - self.dirty.add(*index); - } - _ => (), - } - } - - for (velocity, entity, _) in (&velocities, &entities, &self.dirty).join() { - let (velocity_x, velocity_y, velocity_z) = protocol_velocity(velocity.0); - let packet = EntityVelocity { - entity_id: entity.id() as i32, - velocity_x, - velocity_y, - velocity_z, - }; - - util.broadcast_entity_update(entity, packet, Some(entity)); - } - } - - flagged_setup_impl!(VelocityComponent, reader); -} - -/// Calculates the relative move fields -/// as used in the Entity Relative Move packets. -pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { - let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; - let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; - let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; - (x, y, z) -} - -pub fn degrees_to_stops(degs: f32) -> u8 { - ((degs / 360.0) * 256.0) as u8 -} - -#[cfg(test)] -mod tests { - use specs::{Builder, WorldExt}; - - use feather_core::network::cast_packet; - use feather_core::network::packet::PacketType; - - use crate::entity::test; - use crate::testframework as t; - - use super::*; - - #[test] - fn test_velocity_broadcast_system() { - let (mut w, mut d) = t::builder() - .with(EntityVelocityBroadcastSystem::default(), "") - .build(); - - let player = t::add_player(&mut w); - - let entity = test::create(&mut w, position!(0.0, 0.0, 0.0)).build(); - - w.write_component::() - .insert(entity, VelocityComponent(glm::vec3(0.0, 0.0, 0.0))) - .unwrap(); - - d.dispatch(&w); - w.maintain(); - - let packet = t::assert_packet_received(&player, PacketType::EntityVelocity); - let packet = cast_packet::(&*packet); - assert_eq!(packet.entity_id, entity.id() as i32); - assert_eq!(packet.velocity_x, 0); - assert_eq!(packet.velocity_y, 0); - assert_eq!(packet.velocity_z, 0); - } -} diff --git a/server/src/entity/save.rs b/server/src/entity/save.rs deleted file mode 100644 index c1d5cde5a..000000000 --- a/server/src/entity/save.rs +++ /dev/null @@ -1,198 +0,0 @@ -//! Saving of entity data (and chunk data along with it). - -use crate::chunk_logic; -use crate::chunk_logic::{ChunkUnloadEvent, ChunkWorkerHandle}; -use crate::config::Config; -use crate::entity::{ChunkEntities, SerializerComponent}; -use feather_core::world::ChunkMap; -use rayon::prelude::*; -use shrev::{EventChannel, ReaderId}; -use specs::{Entity, LazyUpdate, Read, ReadExpect, System, WorldExt, Write}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; -use std::time::Instant; - -/// System to save chunk and entity data upon a chunk unload -/// and periodically. -/// -/// This system listens to `ChunkUnloadEvent`s. -#[derive(Default)] -pub struct ChunkSaveSystem { - reader: Option>, -} - -/// Previous time at which chunks were saved. -pub struct PreviousSaveTime(Instant); - -impl Default for PreviousSaveTime { - fn default() -> Self { - Self(Instant::now()) - } -} - -impl<'a> System<'a> for ChunkSaveSystem { - type SystemData = ( - Write<'a, PreviousSaveTime>, - Write<'a, ChunkMap>, - Read<'a, ChunkEntities>, - Read<'a, EventChannel>, - Read<'a, Arc>, - Read<'a, LazyUpdate>, - ReadExpect<'a, ChunkWorkerHandle>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut prev_save_time, - mut chunk_map, - chunk_entities, - unload_events, - config, - lazy, - worker_handle, - ) = data; - - for event in unload_events.read(self.reader.as_mut().unwrap()) { - let entities = vec![]; // TODO - chunk_logic::save_chunk(&worker_handle, Arc::clone(&event.chunk), entities); - } - - if prev_save_time.0.elapsed() >= config.world.save_interval { - // Save chunks - save_chunks(&mut chunk_map, &chunk_entities, &lazy); - prev_save_time.0 = Instant::now(); - } - } - - setup_impl!(reader); -} - -/// Saves all modified chunks. -/// -/// The saves themselves are performed lazily and asynchronously. -/// -/// Returns the number of chunks queued for saving. -pub fn save_chunks( - chunk_map: &mut ChunkMap, - chunk_entities: &ChunkEntities, - lazy: &LazyUpdate, -) -> u32 { - let count = AtomicUsize::new(0); - chunk_map - .chunks_mut() - .par_iter_mut() - .map(|(_, chunk)| { - let (dirty, entities) = chunk_entities.entities_in_chunk_and_modified(chunk.position()); - (chunk, entities, dirty) - }) - .for_each(|(chunk, entities, dirty)| { - // If all of the following are true, don't save the chunk: - // * The chunk has not been modified since the last save. - // * The entities in the chunk are empty (if they weren't, it is likely they were modified) - // * The entities in the chunk haven't changed. - if !chunk.check_modified() && (entities.is_empty() && !dirty) { - return; - } - - // World access is required for entity serialization, - // so we perform the saving itself asynchronously. - let chunk = Arc::new(chunk.clone()); - let entities: Vec = entities.to_vec(); - lazy.exec(move |world| { - // Compute entity data. - let entity_data = entities - .into_iter() - .filter_map(|entity| { - let serializers = world.read_component::(); - let serializer = match serializers.get(entity) { - Some(serializer) => serializer, - None => return None, // Entity not serialized - }; - - let serialize = serializer.0; - Some(serialize(world, entity)) - }) - .collect(); - - let handle = world.fetch::(); - chunk_logic::save_chunk(&handle, chunk, entity_data); - }); - - count.fetch_add(1, Ordering::Release); - }); - - let count = count.load(Ordering::Acquire); - debug!("Saving {} chunks", count); - count as u32 -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{chunkworker, testframework as t}; - use failure::_core::time::Duration; - use feather_core::{Chunk, ChunkPosition}; - - #[test] - fn test_chunk_unload() { - let (mut world, mut dispatcher) = t::builder().with(ChunkSaveSystem::default(), "").build(); - - let (tx, rx) = crossbeam::unbounded(); - let (_tx2, rx2) = crossbeam::unbounded(); - world.insert(ChunkWorkerHandle { - sender: tx, - receiver: rx2, - }); - - let event = ChunkUnloadEvent { - chunk: Arc::new(Chunk::default()), - }; - - t::trigger_event(&world, event); - - dispatcher.dispatch(&world); - - let msg = rx.try_recv().unwrap(); - - match msg { - chunkworker::Request::SaveChunk(chunk, entities) => { - assert_eq!(chunk.position(), ChunkPosition::new(0, 0)); - assert!(entities.is_empty()); // TODO - } - _ => panic!(), - } - } - - #[test] - fn test_periodic() { - let (mut world, mut dispatcher) = t::builder().with(ChunkSaveSystem::default(), "").build(); - - let (tx, rx) = crossbeam::unbounded(); - let (_tx2, rx2) = crossbeam::unbounded(); - world.insert(ChunkWorkerHandle { - sender: tx, - receiver: rx2, - }); - - let last_save_time = PreviousSaveTime(Instant::now() - Duration::from_secs(120)); - world.insert(last_save_time); - - let pos = ChunkPosition::new(0, 0); - world - .fetch_mut::() - .set_chunk_at(pos, Chunk::new(pos)); - - dispatcher.dispatch(&world); - world.maintain(); - - let msg = rx.try_recv().unwrap(); - - match msg { - chunkworker::Request::SaveChunk(chunk, entities) => { - assert_eq!(chunk.position(), pos); - assert!(entities.is_empty()); // TODO - } - _ => panic!(), - } - } -} diff --git a/server/src/lazy.rs b/server/src/lazy.rs deleted file mode 100644 index c446a5f8b..000000000 --- a/server/src/lazy.rs +++ /dev/null @@ -1,36 +0,0 @@ -//! Extension methods for `LazyUpdate`. - -use crate::entity::EntitySpawnEvent; -use shrev::EventChannel; -use specs::world::{EntitiesRes, LazyBuilder}; -use specs::{Entity, LazyUpdate}; - -pub trait LazyUpdateExt { - /// Creates an entity and lazily inserts components. - /// - /// This should be used instead of `LazyUpdate::create_entity` - /// because it automatically triggers an `EntitySpawnEvent`. - fn spawn_entity(&self, entities: &EntitiesRes) -> LazyBuilder; - - /// Lazily sends an entity to a player. This simply forwards - /// to `crate::entity::broadcast::send_entity_to_player`. - fn send_entity_to_player(&self, player: Entity, entity: Entity); -} - -impl LazyUpdateExt for LazyUpdate { - fn spawn_entity(&self, entities: &EntitiesRes) -> LazyBuilder { - let entity = entities.create(); - // Trigger event - self.exec(move |world| { - world - .fetch_mut::>() - .single_write(EntitySpawnEvent { entity }); - }); - - LazyBuilder { lazy: self, entity } - } - - fn send_entity_to_player(&self, player: Entity, entity: Entity) { - crate::entity::send_entity_to_player(self, player, entity); - } -} diff --git a/server/src/lib.rs b/server/src/lib.rs index f7942747f..acd131abe 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -37,61 +37,34 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use specs::{Builder, Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, WorldExt}; use feather_core::network::packet::implementation::DisconnectPlay; -use prelude::*; use crate::chunk_logic::{ChunkHolders, ChunkWorkerHandle}; -use crate::entity::chicken::ChickenComponent; -use crate::entity::cow::CowComponent; -use crate::entity::donkey::DonkeyComponent; -use crate::entity::horse::HorseComponent; -use crate::entity::llama::LlamaComponent; -use crate::entity::mooshroom::MooshroomComponent; -use crate::entity::pig::PigComponent; -use crate::entity::rabbit::RabbitComponent; -use crate::entity::sheep::SheepComponent; -use crate::entity::squid::SquidComponent; -use crate::entity::{ - EntityDestroyEvent, NamedComponent, PacketCreatorComponent, SerializerComponent, -}; -use crate::network::send_packet_to_player; -use crate::player::PlayerDisconnectEvent; -use crate::systems::{BROADCASTER, JOIN_HANDLER, NETWORK, PLAYER_INIT}; -use crate::util::Util; use crate::worldgen::{ ComposableGenerator, EmptyWorldGenerator, SuperflatWorldGenerator, WorldGenerator, }; use feather_core::level; use feather_core::level::{deserialize_level_file, save_level_file, LevelData, LevelGeneratorType}; use rand::Rng; -use shrev::EventChannel; use std::collections::hash_map::DefaultHasher; use std::fs::File; use std::hash::{Hash, Hasher}; use std::io::{Read, Write}; use std::path::Path; use std::process::exit; +use crate::config::Config; #[global_allocator] static ALLOC: System = System; -#[macro_use] -pub mod util; -pub mod blocks; pub mod chunk_logic; pub mod chunkworker; pub mod config; -pub mod entity; pub mod io; pub mod joinhandler; -pub mod lazy; pub mod network; pub mod physics; -pub mod player; -pub mod prelude; pub mod shutdown; -pub mod systems; #[cfg(test)] -pub mod testframework; pub mod time; pub mod worldgen; @@ -256,297 +229,3 @@ fn hash_seed(seed_raw: &str) -> i64 { seed_raw.hash(&mut hasher); hasher.finish() as i64 } - -/// Loads the level.dat file for the world. -fn load_level(path: &Path) -> Result { - let file = File::open(path)?; - let data = deserialize_level_file(file)?; - Ok(data) -} - -/// Loads the chunks around the spawn area and creates -/// a chunk hold on those chunks to prevent them from -/// being unloaded. -/// -/// Note that these chunks are loaded asynchronously, -/// and this function will return before loading is complete. -fn load_spawn_chunks(world: &mut World) { - let view_distance = i32::from(world.fetch::>().server.view_distance); - - // Create an entity for the server and - // add chunk holders using it. - let server_entity = world.create_entity().build(); - - let mut chunk_holders = world.fetch_mut::(); - let chunk_worker_handle = world.fetch::(); - - let level = world.fetch::(); - let offset_x = level.spawn_x / 16; - let offset_z = level.spawn_z / 16; - for x in -view_distance..=view_distance { - for z in -view_distance..=view_distance { - let chunk = ChunkPosition::new(x + offset_x, z + offset_z); - - chunk_logic::load_chunk(&chunk_worker_handle, chunk); - chunk_holders.insert_holder(chunk, server_entity); - } - } -} - -/// Runs the server loop, blocking until the server -/// is shut down. -fn run_loop(world: &mut World, dispatcher: &mut Dispatcher, shutdown_rx: Receiver<()>) { - loop { - if shutdown_rx.try_recv().is_ok() { - // Shut down - return; - } - - let start_time = current_time_in_millis(); - - dispatcher.dispatch(&world); - world.maintain(); - - world.fetch_mut::().reset(); - - // Increment tick count - let mut tick_count = world.write_resource::(); - tick_count.0 += 1; - - // Sleep correct amount - let end_time = current_time_in_millis(); - let elapsed = end_time - start_time; - if elapsed > TICK_TIME { - debug!("Running behind! Starting next tick immediately"); - continue; // Behind - start next tick immediately - } - - // Sleep in 1ms increments until we've slept enough - let mut sleep_time = (TICK_TIME - elapsed) as i64; - let mut last_sleep_time = current_time_in_millis(); - while sleep_time > 0 { - std::thread::sleep(Duration::from_millis(1)); - sleep_time -= (current_time_in_millis() - last_sleep_time) as i64; - last_sleep_time = current_time_in_millis(); - } - } -} - -/// Starts the IO threads. -fn init_io_manager( - config: Arc, - player_count: Arc, - server_icon: Arc>, -) -> io::NetworkIoManager { - io::NetworkIoManager::start( - format!("{}:{}", config.server.address, config.server.port) - .parse() - .unwrap(), - config, - player_count, - server_icon, - ) -} - -/// Initializes the Specs world and dispatchers. -fn init_world<'a, 'b>( - config: Arc, - player_count: Arc, - ioman: io::NetworkIoManager, - level: LevelData, -) -> (World, Dispatcher<'a, 'b>) { - let mut world = World::new(); - time::init_time(&mut world, &level); - world.insert(config); - world.insert(player_count); - world.insert(ioman); - world.insert(TickCount::default()); - - world.register::(); - world.register::(); - - let generator: Arc = match level.generator_type() { - LevelGeneratorType::Flat => Arc::new(SuperflatWorldGenerator { - options: level.clone().generator_options.unwrap_or_default(), - }), - LevelGeneratorType::Default => { - Arc::new(ComposableGenerator::default_with_seed(level.seed as u64)) - } - _ => Arc::new(EmptyWorldGenerator {}), - }; - world.insert(level); - world.insert(generator); - - let mut dispatcher = DispatcherBuilder::new(); - - dispatcher.add(network::NetworkSystem, NETWORK, &[]); - - blocks::init_logic(&mut dispatcher); - physics::init_logic(&mut dispatcher); - entity::init_logic(&mut dispatcher); - player::init_logic(&mut dispatcher); - chunk_logic::init_logic(&mut dispatcher); - time::init_logic(&mut dispatcher); - - dispatcher.add_barrier(); - - blocks::init_handlers(&mut dispatcher); - physics::init_handlers(&mut dispatcher); - entity::init_handlers(&mut dispatcher); - player::init_handlers(&mut dispatcher); - chunk_logic::init_handlers(&mut dispatcher); - - // Player init dependency is so that player position is loaded - // before the join handle runs. - dispatcher.add( - joinhandler::JoinHandlerSystem, - JOIN_HANDLER, - &[NETWORK, PLAYER_INIT], - ); - - dispatcher.add_barrier(); - - player::init_broadcast(&mut dispatcher); - entity::init_broadcast(&mut dispatcher); - - // Broadcast system needs to run last. - dispatcher.add_barrier(); - dispatcher.add(util::BroadcasterSystem, BROADCASTER, &[]); - - let mut dispatcher = dispatcher.build(); - dispatcher.setup(&mut world); - - register_components(&mut world); - - (world, dispatcher) -} - -fn register_components(world: &mut World) { - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); -} - -fn init_log(config: &Config) { - let level = match config.log.level.as_str() { - "trace" => log::Level::Trace, - "debug" => log::Level::Debug, - "info" => log::Level::Info, - "warn" => log::Level::Warn, - "error" => log::Level::Error, - _ => panic!("Unknown log level {}", config.log.level), - }; - - simple_logger::init_with_level(level).unwrap(); -} - -/// Tries to load a server icon from the current directory. -fn load_server_icon() -> Option { - let icon_file: Option = match File::open("server-icon.png") { - Ok(file) => Some(file), - Err(_) => None, - }; - - let mut icon_file = icon_file?; - - let mut data = Vec::new(); - if icon_file.read_to_end(&mut data).is_err() { - warn!("Failed to load server icon."); - return None; - } - - let b64_icon = base64::encode(&data); - Some(format!("data:image/png;base64,{}", b64_icon)) -} - -/// Retrieves the current time in seconds -/// since the UNIX epoch. -pub fn current_time_in_secs() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_secs() -} - -/// Retrieves the current time in milleseconds -/// since the UNIX epoch. -pub fn current_time_in_millis() -> u64 { - SystemTime::now() - .duration_since(UNIX_EPOCH) - .unwrap() - .as_millis() as u64 -} - -/// Disconnects the given player, removing them from the world. -/// This operation is performed lazily. -pub fn disconnect_player(player: Entity, reason: String, lazy: &LazyUpdate) { - lazy.exec_mut(move |world| { - let json = json!({ - "text": reason, - }); - - let packet = DisconnectPlay::new(json.to_string()); - send_packet_to_player(world.read_component().get(player).unwrap(), packet); - - disconnect_player_without_packet(player, world, reason); - }) -} - -/// Disconnects a player without sending Disconnect Play. -/// This should be used when the client disconnects. -pub fn disconnect_player_without_packet(player: Entity, world: &mut World, reason: String) { - let nameds = world.write_component::(); - let named = nameds.get(player).unwrap(); - - info!("Disconnecting player {}: {}", named.display_name, reason); - - // Decrement player count - let player_count = world.fetch_mut::>(); - player_count.0.fetch_sub(1, Ordering::SeqCst); - - // Trigger disconnect event - let event = PlayerDisconnectEvent { - player, - uuid: named.uuid, - reason, - }; - world - .fetch_mut::>() - .single_write(event); - - // Trigger entity destroy event - let event = EntityDestroyEvent { entity: player }; - world - .fetch_mut::>() - .single_write(event); - - // The entity is removed from the world by `entity::EntityDestroySystem`. -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_init_world() { - let config = Arc::new(Config::default()); - let player_count = Arc::new(PlayerCount(AtomicUsize::new(0))); - let server_icon = Arc::new(Some(String::from("server_icon"))); - let ioman = init_io_manager( - Arc::clone(&config), - Arc::clone(&player_count), - Arc::clone(&server_icon), - ); - let level = LevelData::default(); - - let (world, mut dispatcher) = init_world(config, player_count, ioman, level); - dispatcher.dispatch(&world); - } -} diff --git a/server/src/player/animation.rs b/server/src/player/animation.rs deleted file mode 100644 index acf600b75..000000000 --- a/server/src/player/animation.rs +++ /dev/null @@ -1,140 +0,0 @@ -use crate::network::PacketQueue; -use crate::util::Util; -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{AnimationClientbound, AnimationServerbound}; -use feather_core::network::packet::PacketType; -use feather_core::{ClientboundAnimation, Hand}; -use shrev::EventChannel; -use specs::SystemData; -use specs::{Entity, Read, ReaderId, System, World, Write}; - -/// Event which is triggered when a player causes -/// an animation. -#[derive(Debug, Clone)] -pub struct PlayerAnimationEvent { - pub player: Entity, - pub animation: ClientboundAnimation, -} - -/// System for handling Animation Serverbound packets -/// and then triggering a `PlayerAnimationEvent`. -pub struct PlayerAnimationSystem; - -impl<'a> System<'a> for PlayerAnimationSystem { - type SystemData = ( - Write<'a, EventChannel>, - Read<'a, PacketQueue>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut events, packet_queue) = data; - - // Handle Animation Serverbound packets. - let packets = packet_queue.for_packet(PacketType::AnimationServerbound); - - for (player, packet) in packets { - let packet = cast_packet::(&*packet); - - let animation = match packet.hand { - Hand::Main => ClientboundAnimation::SwingMainArm, - Hand::Off => ClientboundAnimation::SwingOffhand, - }; - - let event = PlayerAnimationEvent { player, animation }; - events.single_write(event); - } - } -} - -/// System for broadcasting when a player causes an animation. -/// This system listens to `PlayerAnimationEvent`s. -#[derive(Default)] -pub struct AnimationBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for AnimationBroadcastSystem { - type SystemData = (Read<'a, EventChannel>, Read<'a, Util>); - - fn run(&mut self, data: Self::SystemData) { - let (events, util) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - // Broadcast animation - let packet = AnimationClientbound::new(event.player.id() as i32, event.animation); - - util.broadcast_entity_update(event.player, packet, Some(event.player)) - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::network::packet::implementation::{ - AnimationClientbound, AnimationServerbound, - }; - use specs::WorldExt; - - #[test] - fn test_animation_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let packet = AnimationServerbound::new(Hand::Main); - t::receive_packet(&player, &w, packet); - - let mut event_reader = t::reader::(&w); - - d.dispatch(&w); - w.maintain(); - - let channel = w.fetch::>(); - - let events = channel.read(&mut event_reader).collect::>(); - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - - assert_eq!(first.player, player.entity); - assert_eq!(first.animation, ClientboundAnimation::SwingMainArm); - } - - #[test] - fn test_animation_broadcast_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - let event = PlayerAnimationEvent { - player: player.entity, - animation: ClientboundAnimation::SwingMainArm, - }; - - t::trigger_event(&w, event.clone()); - - d.dispatch(&w); - w.maintain(); - - // Make sure animation wasn't sent to the player itself - t::assert_packet_not_received(&player, PacketType::AnimationClientbound); - - let packet = t::assert_packet_received(&player2, PacketType::AnimationClientbound); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.entity_id, event.player.id() as i32); - assert_eq!(packet.animation, event.animation); - } -} diff --git a/server/src/player/broadcast.rs b/server/src/player/broadcast.rs deleted file mode 100644 index 40fe02c73..000000000 --- a/server/src/player/broadcast.rs +++ /dev/null @@ -1,203 +0,0 @@ -use crate::config::Config; -use crate::entity::{ChunkEntities, NamedComponent, PlayerComponent, PositionComponent}; -use crate::joinhandler::PlayerJoinEvent; -use crate::lazy::LazyUpdateExt; -use crate::network::{send_packet_to_all_players, send_packet_to_player, NetworkComponent}; -use crate::player::chat::ChatBroadcastEvent; -use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction}; -use feather_core::Gamemode; -use shrev::EventChannel; -use specs::{Entities, Entity, Join, Read, ReadStorage, ReaderId, System, World, Write}; -use specs::{LazyUpdate, SystemData}; -use std::sync::Arc; -use uuid::Uuid; - -/// System for broadcasting when a player joins -/// the game. -/// -/// This system only broadcasts the -/// Player Info packet necessary to view to player -/// in the tablist - the `EntityBroadcastSystem` handles -/// the Spawn Player packet. -#[derive(Default)] -pub struct JoinBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for JoinBroadcastSystem { - type SystemData = ( - Read<'a, EventChannel>, - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, NamedComponent>, - ReadStorage<'a, PlayerComponent>, - ReadStorage<'a, NetworkComponent>, - Write<'a, EventChannel>, - Read<'a, ChunkEntities>, - Read<'a, LazyUpdate>, - Read<'a, Arc>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - join_events, - positions, - nameds, - player_comps, - net_comps, - mut chat, - chunk_entities, - lazy, - config, - entities, - ) = data; - - for event in join_events.read(&mut self.reader.as_mut().unwrap()) { - // Broadcast join - let position = positions.get(event.player).unwrap(); - let named = nameds.get(event.player).unwrap(); - let player_comp = player_comps.get(event.player).unwrap(); - - let player_info = get_player_initialization_packet(position, named, player_comp); - - send_packet_to_all_players(&net_comps, &entities, player_info, None); - - let net_comp = net_comps.get(event.player).unwrap(); - - // Send existing players to new player - for (position, named, player_comp, entity) in - (&positions, &nameds, &player_comps, &entities).join() - { - if entity != event.player { - let player_info = - get_player_initialization_packet(position, named, player_comp); - send_packet_to_player(net_comp, player_info); - } - } - - // Send entities within view distance to new player - for entity in chunk_entities.entites_within_view_distance( - position.current.chunk_pos(), - config.server.view_distance, - ) { - if entity != event.player { - lazy.send_entity_to_player(event.player, entity); - } - } - - // Broadcast join message in chat - let message = json!({ - "translate": "multiplayer.player.joined", - "color": "yellow", - "with": [ - {"text": named.display_name}, - ], - }) - .to_string(); - chat.single_write(ChatBroadcastEvent { message }); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -/// Returns the player info packet -/// for the given player. -fn get_player_initialization_packet( - _position: &PositionComponent, - named: &NamedComponent, - pcomp: &PlayerComponent, -) -> PlayerInfo { - let display_name = json!({ - "text": named.display_name - }) - .to_string(); - - let mut props = vec![]; - for prop in pcomp.profile_properties.iter() { - props.push(( - prop.name.clone(), - prop.value.clone(), - prop.signature.clone(), - )); - } - - let action = PlayerInfoAction::AddPlayer( - named.display_name.clone(), - props, - Gamemode::Creative, - 50, - display_name, - ); - PlayerInfo::new(action, named.uuid) -} - -/// Event which is called when a player disconnected. -pub struct PlayerDisconnectEvent { - pub player: Entity, - pub reason: String, - pub uuid: Uuid, -} - -/// System for broadcasting when a player disconnects. -#[derive(Default)] -pub struct DisconnectBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for DisconnectBroadcastSystem { - type SystemData = ( - ReadStorage<'a, NamedComponent>, - ReadStorage<'a, NetworkComponent>, - Write<'a, EventChannel>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (nameds, networks, mut chat, disconnect_events) = data; - - for event in disconnect_events.read(&mut self.reader.as_mut().unwrap()) { - // Broadcast disconnect. - // Note that the Destroy Entity packet is sent - // in a separate system (crate::entity::EntityDestroyBroadcastSystem). - // This system only updates the tablist for all clients. - let player_info = PlayerInfo::new(PlayerInfoAction::RemovePlayer, event.uuid); - - for net in (&networks).join() { - send_packet_to_player(net, player_info.clone()); - } - - let named = nameds.get(event.player).unwrap(); - - // Broadcast chat message. - let message = json!({ - "translate": "multiplayer.player.left", - "color": "yellow", - "with": [ - {"text": named.display_name}, - ], - }) - .to_string(); - let event = ChatBroadcastEvent { message }; - chat.single_write(event); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} diff --git a/server/src/player/chat.rs b/server/src/player/chat.rs deleted file mode 100644 index 60e7db460..000000000 --- a/server/src/player/chat.rs +++ /dev/null @@ -1,154 +0,0 @@ -use shrev::EventChannel; -use specs::SystemData; -use specs::{Entities, Read, ReadStorage, ReaderId, System, World, Write}; - -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{ - ChatMessageClientbound, ChatMessageServerbound, -}; -use feather_core::network::packet::PacketType; - -use crate::entity::NamedComponent; -use crate::network::{send_packet_to_all_players, NetworkComponent, PacketQueue}; - -/// Event which is triggered when a new chat message is to be broadcasted to the whole server. -#[derive(Debug, Clone)] -pub struct ChatBroadcastEvent { - pub message: String, -} - -/// System for handling Chat Message Serverbound packets -/// and then triggering a `ChatBroadcastEvent`. -pub struct PlayerChatSystem; - -impl<'a> System<'a> for PlayerChatSystem { - type SystemData = ( - Write<'a, EventChannel>, - ReadStorage<'a, NamedComponent>, - Read<'a, PacketQueue>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut events, nameds, packet_queue) = data; - - // Handle Chat Message Serverbound packets. - let packets = packet_queue.for_packet(PacketType::ChatMessageServerbound); - - for (player, packet) in packets { - let packet = cast_packet::(&*packet); - let message = packet.message.clone(); - let player_name = &nameds.get(player).unwrap().display_name; - - // TODO: could use a more robust chat-component library. - let message_json = json!({ - "translate": "chat.type.text", - "with": [ - {"text": player_name}, - {"text": message}, - ], - }) - .to_string(); - - let event = ChatBroadcastEvent { - message: message_json, - }; - events.single_write(event); - - // Log in the console - info!("<{}> {}", player_name, message); - } - } -} - -/// System for broadcasting chat messages. -/// This system listens to `ChatBroadcastEvent`s. -#[derive(Default)] -pub struct ChatBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for ChatBroadcastSystem { - type SystemData = ( - Read<'a, EventChannel>, - ReadStorage<'a, NetworkComponent>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (events, networks, entities) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let message = event.message.clone(); - - // Broadcast chat message - let packet = ChatMessageClientbound { - json_data: message, - position: 0, - }; - - send_packet_to_all_players(&networks, &entities, packet, None); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::network::packet::implementation::ChatMessageServerbound; - use specs::WorldExt; - - #[test] - fn test_chat_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let packet = ChatMessageServerbound { - message: String::from("test"), - }; - t::receive_packet(&player, &w, packet); - - let mut event_reader = t::reader::(&w); - - d.dispatch(&w); - w.maintain(); - - let channel = w.fetch::>(); - - let events = channel.read(&mut event_reader).collect::>(); - assert_eq!(events.len(), 1); - } - - #[test] - fn test_chat_broadcast_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - let event = ChatBroadcastEvent { - message: String::from("test"), - }; - - t::trigger_event(&w, event); - - d.dispatch(&w); - w.maintain(); - - t::assert_packet_received(&player, PacketType::ChatMessageClientbound); - let packet = t::assert_packet_received(&player2, PacketType::ChatMessageClientbound); - let packet = cast_packet::(&*packet); - assert_eq!(packet.json_data, String::from("test")); - } -} diff --git a/server/src/player/digging.rs b/server/src/player/digging.rs deleted file mode 100644 index 6b1d51466..000000000 --- a/server/src/player/digging.rs +++ /dev/null @@ -1,817 +0,0 @@ -//! This module handles the monolithic Player Digging packet. -//! -//! The packet's name is rather misleading, as it is also sent -//! for completely unrelated actions, including eating, shooting bows, -//! swapping items out the the offhand, and dropping items. - -use specs::{Entity, LazyUpdate, Read, ReadStorage, ReaderId, System, World, Write, WriteStorage}; - -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{ - BlockChange, PlayerDigging, PlayerDiggingStatus, -}; -use feather_core::network::packet::PacketType; -use feather_core::world::block::{Block, BlockExt}; -use feather_core::world::ChunkMap; -use feather_core::{Gamemode, Item, Position}; - -use crate::blocks::{BlockUpdateCause, BlockUpdateEvent}; -use crate::disconnect_player; -use crate::entity::{PlayerComponent, PositionComponent, ShootArrowEvent}; -use crate::network::PacketQueue; -use crate::player::{InventoryComponent, InventoryUpdateEvent}; -use crate::util::Util; -use feather_core::inventory::{ItemStack, SlotIndex, SLOT_HOTBAR_OFFSET, SLOT_OFFHAND}; -use shrev::EventChannel; -use specs::SystemData; - -/// Event triggered when a player drops an item. -/// -/// Before this event is triggered, the item -/// is removed from the player's inventory. -#[derive(Debug, Clone)] -pub struct PlayerItemDropEvent { - /// The slot from which the item was dropped, - /// if known. - pub slot: Option, - /// The item stack which was dropped. - pub stack: ItemStack, - /// The player who dropped the item. - pub player: Entity, -} - -/// System responsible for polling for PlayerDigging -/// packets and writing the corresponding events. -pub struct PlayerDiggingSystem; - -impl<'a> System<'a> for PlayerDiggingSystem { - type SystemData = ( - WriteStorage<'a, InventoryComponent>, - ReadStorage<'a, PlayerComponent>, // For gamemodes - ReadStorage<'a, PositionComponent>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, ChunkMap>, - Read<'a, PacketQueue>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - use PlayerDiggingStatus::*; - - let ( - mut inventories, - players, - positions, - mut block_breaks, - mut item_drops, - mut inventory_updates, - mut shoot_arrow_events, - mut chunk_map, - packet_queue, - lazy, - ) = data; - - let packets = packet_queue.for_packet(PacketType::PlayerDigging); - - for (player, packet) in packets { - let packet = cast_packet::(&*packet); - - match packet.status { - StartedDigging | FinishedDigging | CancelledDigging => handle_digging( - packet, - players.get(player).unwrap(), - inventories.get(player).unwrap().item_in_main_hand(), - player, - &mut block_breaks, - &mut chunk_map, - &lazy, - ), - DropItem | DropItemStack => handle_drop_item_stack( - packet, - player, - &mut inventory_updates, - &mut item_drops, - inventories.get_mut(player).unwrap(), - ), - ConsumeItem => handle_consume_item( - packet, - players.get(player).unwrap(), - player, - inventories.get_mut(player).unwrap(), - &mut inventory_updates, - positions.get(player).unwrap().current, - &mut shoot_arrow_events, - ), - status => warn!("Unhandled Player Digging status {:?}", status), - } - } - } -} - -fn handle_digging( - packet: &PlayerDigging, - player: &PlayerComponent, - item_in_main_hand: Option<&ItemStack>, - entity: Entity, - events: &mut EventChannel, - chunk_map: &mut ChunkMap, - lazy: &LazyUpdate, -) { - // Return early if needed - match packet.status { - PlayerDiggingStatus::StartedDigging => { - if player.gamemode != Gamemode::Creative { - return; - } - } - PlayerDiggingStatus::CancelledDigging => return, - _ => (), - } - - // Don't break block if player is holding a sword in creative mode. - if player.gamemode == Gamemode::Creative { - if let Some(item_in_main_hand) = item_in_main_hand { - match item_in_main_hand.ty { - Item::WoodenSword - | Item::StoneSword - | Item::GoldenSword - | Item::IronSword - | Item::DiamondSword => return, - _ => (), - } - } - } - - let old = chunk_map.block_at(packet.location); - - if chunk_map.set_block_at(packet.location, Block::Air).is_err() { - disconnect_player( - entity, - "Attempted to break block in unloaded chunk".to_string(), - lazy, - ); - return; - } - - let event = BlockUpdateEvent { - cause: BlockUpdateCause::Player(entity), - pos: packet.location, - old_block: old.unwrap(), // We checked that the location was valid above - new_block: Block::Air, - }; - - events.single_write(event); -} - -fn handle_drop_item_stack( - packet: &PlayerDigging, - entity: Entity, - inventory_updates: &mut EventChannel, - item_drops: &mut EventChannel, - inventory: &mut InventoryComponent, -) { - assert!( - packet.status == PlayerDiggingStatus::DropItem - || packet.status == PlayerDiggingStatus::DropItemStack - ); - - let slot = inventory.held_item + SLOT_HOTBAR_OFFSET; - - let stack = { - if let Some(item) = inventory.item_at(slot) { - item.clone() - } else { - // Silently fail - no item stack to drop - return; - } - }; - - let amnt = match packet.status { - PlayerDiggingStatus::DropItem => { - if stack.amount == 0 { - inventory.clear_item_at(slot); - 0 - } else if stack.amount == 1 { - inventory.clear_item_at(slot); - 1 - } else { - inventory.set_item_at(slot, ItemStack::new(stack.ty, stack.amount - 1)); - 1 - } - } - PlayerDiggingStatus::DropItemStack => { - inventory.clear_item_at(slot); - stack.amount - } - _ => unreachable!(), // Assertion above - }; - - let inv_update = InventoryUpdateEvent { - slots: smallvec![slot], - player: entity, - }; - inventory_updates.single_write(inv_update); - - if amnt != 0 { - let item_drop = PlayerItemDropEvent { - slot: Some(slot), - stack: ItemStack::new(stack.ty, amnt), - player: entity, - }; - item_drops.single_write(item_drop); - } -} - -/// Handles food consumption and shooting arrows. -fn handle_consume_item( - packet: &PlayerDigging, - player: &PlayerComponent, - entity: Entity, - inventory: &mut InventoryComponent, - inventory_updates: &mut EventChannel, - position: Position, - shoot_arrow_events: &mut EventChannel, -) { - assert_eq!(packet.status, PlayerDiggingStatus::ConsumeItem); - - // TODO: Fallback to off-hand if main-hand is not a consumable - let used_item = inventory.item_in_main_hand(); - - if let Some(item) = used_item { - if item.ty == Item::Bow { - handle_shoot_bow( - player, - entity, - inventory, - inventory_updates, - position, - shoot_arrow_events, - ); - } - // TODO: Food, potions - } -} - -fn handle_shoot_bow( - player: &PlayerComponent, - entity: Entity, - inventory: &mut InventoryComponent, - inventory_updates: &mut EventChannel, - position: Position, - shoot_arrow_events: &mut EventChannel, -) { - let arrow_to_consume: Option<(SlotIndex, ItemStack)> = find_arrow(&inventory); - if player.gamemode == Gamemode::Survival || player.gamemode == Gamemode::Adventure { - // If no arrow was found, don't shoot - let arrow_to_consume = arrow_to_consume.clone(); - if arrow_to_consume.is_none() { - debug!("Tried to shoot bow with no arrows."); - return; - } - - // Consume arrow - let (arrow_slot, arrow_stack) = arrow_to_consume.unwrap(); - let mut arrow_stack: ItemStack = arrow_stack; - arrow_stack.amount -= 1; - - inventory.set_item_at(arrow_slot, arrow_stack); - inventory_updates.single_write(InventoryUpdateEvent { - slots: smallvec![arrow_slot], - player: entity, - }); - } - - let arrow_type: Item = match arrow_to_consume { - None => Item::Arrow, // Default to generic arrow in creative mode with none in inventory - Some((_, arrow_stack)) => arrow_stack.ty, - }; - - shoot_arrow_events.single_write(ShootArrowEvent { - shooter: Some(entity), - position, - arrow_type, - critical: false, // TODO: Determine critical based on how long bow was pulled back - }); -} - -fn find_arrow(inventory: &InventoryComponent) -> Option<(SlotIndex, ItemStack)> { - // Order of priority is: off-hand, hotbar (0 to 8), rest of inventory - - if let Some(offhand) = inventory.item_at(SLOT_OFFHAND) { - if is_arrow_item(offhand.ty) { - return Some((SLOT_OFFHAND, offhand.clone())); - } - } - - for hotbar_slot in 0..9 { - if let Some(hotbar_stack) = inventory.item_at(SLOT_HOTBAR_OFFSET + hotbar_slot) { - if is_arrow_item(hotbar_stack.ty) { - return Some((SLOT_HOTBAR_OFFSET + hotbar_slot, hotbar_stack.clone())); - } - } - } - - for inv_slot in 9..=35 { - if let Some(inv_stack) = inventory.item_at(inv_slot) { - if is_arrow_item(inv_stack.ty) { - return Some((inv_slot, inv_stack.clone())); - } - } - } - None -} - -fn is_arrow_item(item: Item) -> bool { - match item { - Item::Arrow | Item::SpectralArrow | Item::TippedArrow => true, - _ => false, - } -} - -/// System for broadcasting block update -/// events to all clients. -/// -/// This system listens to `BlockUpdateEvent`s. -#[derive(Default)] -pub struct BlockUpdateBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for BlockUpdateBroadcastSystem { - type SystemData = (Read<'a, EventChannel>, Read<'a, Util>); - - fn run(&mut self, data: Self::SystemData) { - let (events, util) = data; - - // Process events - for event in events.read(&mut self.reader.as_mut().unwrap()) { - // Send Block Change packet to every player, - // except for the one that performed the update - // (if any) - let neq = if let BlockUpdateCause::Player(player) = event.cause { - Some(player) - } else { - None - }; - - let packet = BlockChange::new(event.pos, i32::from(event.new_block.native_state_id())); - util.broadcast_chunk_update(event.pos.chunk_pos(), packet, neq); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some(world.fetch_mut::>().register_reader()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::blocks::BlockUpdateEvent; - use crate::testframework as t; - use feather_core::item::Item; - use feather_core::world::chunk::Chunk; - use feather_core::world::{BlockPosition, ChunkPosition}; - use specs::WorldExt; - - #[test] - fn test_started_digging() { - let (mut w, mut d) = t::init_world(); - - let cpos = ChunkPosition::new(0, 0); - let bpos = BlockPosition::new(0, 0, 0); - let mut chunk = Chunk::new(cpos); - chunk.set_block_at(0, 0, 0, Block::Stone); - w.fetch_mut::().set_chunk_at(cpos, chunk); - - let mut event_reader = t::reader(&w); - - // Creative mode - - let player = t::add_player(&mut w); - - let packet = PlayerDigging::new(PlayerDiggingStatus::StartedDigging, bpos, 0); - - t::receive_packet(&player, &w, packet.clone()); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - { - let mut chunk_map = w.fetch_mut::(); - - assert_eq!(chunk_map.block_at(bpos).unwrap(), Block::Air); - - chunk_map.set_block_at(bpos, Block::Stone).unwrap(); - - let channel = w.fetch_mut::>(); - let events = channel.read(&mut event_reader).collect::>(); - assert_eq!(events.len(), 1); - - let first = events.first().unwrap(); - assert_eq!(first.old_block, Block::Stone); - assert_eq!(first.new_block, Block::Air); - assert_eq!(first.cause, BlockUpdateCause::Player(player.entity)); - assert_eq!(first.pos, bpos); - } - - // Survival mode - let player = t::add_player(&mut w); - w.write_component::() - .get_mut(player.entity) - .unwrap() - .gamemode = Gamemode::Survival; - - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let chunk_map = w.fetch::(); - assert_eq!(chunk_map.block_at(bpos).unwrap(), Block::Stone); - - let channel = w.fetch_mut::>(); - let events = channel.read(&mut event_reader).collect::>(); - assert_eq!(events.len(), 0); - } - - // This should be a no-op. - #[test] - fn test_cancelled_digging() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut event_reader = t::reader(&w); - - let packet = PlayerDigging::new( - PlayerDiggingStatus::CancelledDigging, - BlockPosition::default(), - 0, - ); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let channel = w.fetch::>(); - let events = channel.read(&mut event_reader).collect::>(); - assert!(events.is_empty()); - } - - #[test] - fn test_finished_digging() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - w.write_component::() - .get_mut(player.entity) - .unwrap() - .gamemode = Gamemode::Survival; - - let mut event_reader = t::reader(&w); - - let bpos = BlockPosition::new(0, 0, 0); - let cpos = bpos.chunk_pos(); - - let mut chunk = Chunk::new(cpos); - chunk.set_block_at(0, 0, 0, Block::Stone); - - w.fetch_mut::().set_chunk_at(cpos, chunk); - - let packet = PlayerDigging::new(PlayerDiggingStatus::FinishedDigging, bpos, 0); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let chunk_map = w.fetch::(); - - assert_eq!(chunk_map.block_at(bpos).unwrap(), Block::Air); - - let channel = w.fetch_mut::>(); - let events = channel.read(&mut event_reader).collect::>(); - assert_eq!(events.len(), 1); - - let first = events.first().unwrap(); - assert_eq!(first.old_block, Block::Stone); - assert_eq!(first.new_block, Block::Air); - assert_eq!(first.cause, BlockUpdateCause::Player(player.entity)); - assert_eq!(first.pos, bpos); - } - - #[test] - fn test_block_break_in_unloaded_chunk() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut event_reader = t::reader(&w); - - let bpos = BlockPosition::new(1000, 25, 1000); - - let packet = PlayerDigging::new(PlayerDiggingStatus::FinishedDigging, bpos, 0); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_disconnected(&player); - - let channel = w.fetch::>(); - let events = channel.read(&mut event_reader).collect::>(); - assert!(events.is_empty()); - } - - #[test] - fn test_drop_item() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let slot = SLOT_HOTBAR_OFFSET; - { - let mut invs = w.write_component::(); - let inv = invs.get_mut(player.entity).unwrap(); - inv.held_item = 0; - inv.set_item_at(slot, ItemStack::new(Item::CookedBeef, 4)); - } - - let mut drop_reader = t::reader(&w); - let mut update_reader = t::reader(&w); - - let packet = PlayerDigging::new(PlayerDiggingStatus::DropItem, BlockPosition::default(), 0); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - let drop_channel = w.fetch::>(); - let update_channel = w.fetch::>(); - - // Check that events are correct - let drop_events = drop_channel.read(&mut drop_reader).collect::>(); - assert_eq!(drop_events.len(), 1); - let first = drop_events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slot, Some(slot)); - assert_eq!(first.stack, ItemStack::new(Item::CookedBeef, 1)); // 1 beef was dropped - - let update_events = update_channel.read(&mut update_reader).collect::>(); - assert_eq!(update_events.len(), 1); - let first = update_events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slots.as_slice(), &[slot]); - - // Check that inventory was updated correctly - let invs = w.read_component::(); - let inv = invs.get(player.entity).unwrap(); - assert_eq!( - inv.item_at(slot).unwrap(), - &ItemStack::new(Item::CookedBeef, 3) - ); // 1 was removed - } - - #[test] - fn test_drop_item_no_stack() { - // This should be a no-op. - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut drop_reader = t::reader(&w); - let mut update_reader = t::reader(&w); - - let packet = PlayerDigging::new(PlayerDiggingStatus::DropItem, BlockPosition::default(), 0); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let drop_channel = w.fetch::>(); - let update_channel = w.fetch::>(); - - let drop_events = drop_channel.read(&mut drop_reader).collect::>(); - assert!(drop_events.is_empty()); - let update_events = update_channel.read(&mut update_reader).collect::>(); - assert!(update_events.is_empty()); - } - - #[test] - fn test_drop_item_stack() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut drop_reader = t::reader(&w); - let mut update_reader = t::reader(&w); - - let slot = SLOT_HOTBAR_OFFSET; - let amnt = 32; - { - let mut invs = w.write_component::(); - let inv = invs.get_mut(player.entity).unwrap(); - inv.set_item_at(slot, ItemStack::new(Item::CookedBeef, amnt)); - } - - let packet = PlayerDigging::new( - PlayerDiggingStatus::DropItemStack, - BlockPosition::default(), - 0, - ); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - let drop_channel = w.fetch::>(); - let update_channel = w.fetch::>(); - - let update_events = update_channel.read(&mut update_reader).collect::>(); - assert_eq!(update_events.len(), 1); - let first = update_events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slots.as_slice(), &[slot]); - - let drop_events = drop_channel.read(&mut drop_reader).collect::>(); - assert_eq!(drop_events.len(), 1); - let first = drop_events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slot, Some(slot)); - assert_eq!(first.stack, ItemStack::new(Item::CookedBeef, amnt)); - - let invs = w.read_component::(); - let inv = invs.get(player.entity).unwrap(); - assert_eq!(inv.item_at(slot), None); - } - - #[test] - fn test_block_update_broadcast_system() { - let (mut w, mut d) = t::init_world(); - - let player1 = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - let pos = BlockPosition::default(); - let block = Block::Sand; - - let event = BlockUpdateEvent { - cause: BlockUpdateCause::Player(player1.entity), - pos, - old_block: block, - new_block: Block::Air, - }; - w.fetch_mut::>().single_write(event); - - d.dispatch(&w); - w.maintain(); - - let block_change = t::assert_packet_received(&player2, PacketType::BlockChange); - let block_change = cast_packet::(&*block_change); - assert_eq!(block_change.location, pos); - assert_eq!( - block_change.block_id, - i32::from(Block::Air.native_state_id()) - ); - - t::assert_packet_not_received(&player1, PacketType::BlockChange); // Don't send update to own player - - // Now handle an event not caused by a player - let event = BlockUpdateEvent { - cause: BlockUpdateCause::Test, - pos, - old_block: block, - new_block: Block::Air, - }; - w.fetch_mut::>().single_write(event); - - d.dispatch(&w); - w.maintain(); - - // Packet should be sent to both players - t::assert_packet_received(&player1, PacketType::BlockChange); - t::assert_packet_received(&player2, PacketType::BlockChange); - } - - #[test] - pub fn test_find_arrow() { - let mut inv = InventoryComponent::new(); - inv.set_item_at( - SLOT_OFFHAND, - ItemStack { - ty: Item::Arrow, - amount: 1, - }, - ); - inv.set_item_at( - SLOT_HOTBAR_OFFSET, - ItemStack { - ty: Item::Arrow, - amount: 1, - }, - ); - inv.set_item_at( - 9, - ItemStack { - ty: Item::Arrow, - amount: 1, - }, - ); - - // 1. Off-hand - let (slot, stack) = find_arrow(&inv).unwrap(); - assert_eq!(slot, SLOT_OFFHAND); - assert_eq!(stack.ty, Item::Arrow); - inv.clear_item_at(SLOT_OFFHAND); - - // 2. Hot-bar - let (slot, stack) = find_arrow(&inv).unwrap(); - assert_eq!(slot, SLOT_HOTBAR_OFFSET); - assert_eq!(stack.ty, Item::Arrow); - inv.clear_item_at(SLOT_HOTBAR_OFFSET); - - // 3. Rest of inventory - let (slot, stack) = find_arrow(&inv).unwrap(); - assert_eq!(slot, 9); - assert_eq!(stack.ty, Item::Arrow); - inv.clear_item_at(9); - - // 4. No arrow found - assert!(find_arrow(&inv).is_none()); - } - - #[test] - pub fn test_shoot_arrow() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut shoot_reader = t::reader(&w); - let mut update_reader = t::reader(&w); - - let slot = SLOT_HOTBAR_OFFSET; - let amnt = 32; - { - let mut invs = w.write_component::(); - let inv = invs.get_mut(player.entity).unwrap(); - inv.set_item_at(slot, ItemStack::new(Item::Bow, 1)); - inv.set_item_at(slot + 1, ItemStack::new(Item::Arrow, amnt)); - } - - // Change to survival - w.write_component::() - .insert( - player.entity, - PlayerComponent { - gamemode: Gamemode::Survival, - profile_properties: vec![], - }, - ) - .unwrap(); - - let packet = PlayerDigging::new( - PlayerDiggingStatus::ConsumeItem, - BlockPosition::default(), - 0, - ); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - let shoot_channel = w.fetch::>(); - let update_channel = w.fetch::>(); - - let update_events = update_channel.read(&mut update_reader).collect::>(); - assert_eq!(update_events.len(), 1); - let first = update_events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slots.as_slice(), &[slot + 1]); - - let shoot_events = shoot_channel.read(&mut shoot_reader).collect::>(); - assert_eq!(shoot_events.len(), 1); - let first = shoot_events.first().unwrap(); - assert_eq!(first.shooter.unwrap(), player.entity); - assert_eq!(first.arrow_type, Item::Arrow); - - // In survival, check if amount of arrow stack decreased. - let invs = w.read_component::(); - let inv = invs.get(player.entity).unwrap(); - assert_eq!(inv.item_at(slot + 1).unwrap().amount, amnt - 1); - } -} diff --git a/server/src/player/init.rs b/server/src/player/init.rs deleted file mode 100644 index a0c4d83aa..000000000 --- a/server/src/player/init.rs +++ /dev/null @@ -1,177 +0,0 @@ -use crate::entity::{ - degrees_to_stops, LastKnownPositionComponent, PacketCreatorComponent, PlayerComponent, - VelocityComponent, -}; -use crate::entity::{Metadata, NamedComponent, PositionComponent}; -use crate::network::PlayerPreJoinEvent; -use crate::player::{ChunkPendingComponent, InventoryComponent, LoadedChunksComponent}; -use crate::prelude::*; -use feather_core::level::LevelData; -use feather_core::packet::SpawnPlayer; -use feather_core::{Gamemode, Packet}; -use hashbrown::HashSet; -use shrev::{EventChannel, ReaderId}; -use specs::{Entity, SystemData, WorldExt}; -use specs::{Read, System, World, WriteStorage}; -use std::path::Path; -use std::sync::Arc; - -/// System for initializing the necessary components -/// when a player joins. -#[derive(Default)] -pub struct PlayerInitSystem { - join_event_reader: Option>, -} - -impl<'a> System<'a> for PlayerInitSystem { - type SystemData = ( - Read<'a, EventChannel>, - WriteStorage<'a, PlayerComponent>, - WriteStorage<'a, PositionComponent>, - WriteStorage<'a, VelocityComponent>, - WriteStorage<'a, NamedComponent>, - WriteStorage<'a, ChunkPendingComponent>, - WriteStorage<'a, LoadedChunksComponent>, - WriteStorage<'a, InventoryComponent>, - WriteStorage<'a, Metadata>, - WriteStorage<'a, LastKnownPositionComponent>, - WriteStorage<'a, PacketCreatorComponent>, - Read<'a, LevelData>, - Read<'a, Arc>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - join_events, - mut player_comps, - mut positions, - mut velocities, - mut nameds, - mut chunk_pending_comps, - mut loaded_chunk_comps, - mut inventory_comps, - mut metadata, - mut last_positions, - mut packet_creators, - level, - config, - ) = data; - - // Run through events - for event in join_events.read(&mut self.join_event_reader.as_mut().unwrap()) { - // Load player data - let uuid = event.uuid; - // If this is a new player, set gamemode to server's default (config) - let default_gamemode = &config.server.default_gamemode.clone(); - let world_dir = Path::new(&config.world.name); - - debug!("Loading player data for UUID {}", uuid); - let (gamemode, pos, velocity, inventory_slots) = - match feather_core::player_data::load_player_data(world_dir, uuid) { - Ok(data) => ( - Gamemode::from_id(data.gamemode as u8), - data.entity.read_position(), - data.entity.read_velocity(), - data.inventory, - ), - Err(_) => ( - Gamemode::from_string(default_gamemode.as_str()), - None, // Invalid position will default to world spawn - None, - vec![], // Empty inventory - ), - }; - - let player_comp = PlayerComponent { - profile_properties: event.profile_properties.clone(), - gamemode, - }; - player_comps.insert(event.player, player_comp).unwrap(); - - let spawn_pos = pos.unwrap_or(position!( - f64::from(level.spawn_x), - f64::from(level.spawn_y), - f64::from(level.spawn_z) - )); - let position = PositionComponent { - current: spawn_pos, - previous: spawn_pos, - }; - positions.insert(event.player, position).unwrap(); - - let velocity = VelocityComponent(velocity.unwrap_or_else(|| glm::vec3(0.0, 0.0, 0.0))); - velocities.insert(event.player, velocity).unwrap(); - - let named = NamedComponent { - display_name: event.username.clone(), - uuid: event.uuid, - }; - nameds.insert(event.player, named).unwrap(); - - let chunk_pending_comp = ChunkPendingComponent { - pending: HashSet::new(), - }; - chunk_pending_comps - .insert(event.player, chunk_pending_comp) - .unwrap(); - - let loaded_chunk_comp = LoadedChunksComponent::default(); - loaded_chunk_comps - .insert(event.player, loaded_chunk_comp) - .unwrap(); - - let mut inventory_comp = InventoryComponent::new(); - for slot in inventory_slots { - let slot_index = slot.convert_index(); - if let Some(slot_index) = slot_index { - inventory_comp.set_item_at(slot_index, slot.to_stack()); - } - } - inventory_comps - .insert(event.player, inventory_comp) - .unwrap(); - - let last_position = LastKnownPositionComponent::default(); - last_positions.insert(event.player, last_position).unwrap(); - - let meta = Metadata::Player(crate::entity::metadata::Player::default()); - metadata.insert(event.player, meta).unwrap(); - - let packet_creator = PacketCreatorComponent(&create_packet); - packet_creators - .insert(event.player, packet_creator) - .unwrap(); - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.join_event_reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -pub fn create_packet(world: &World, entity: Entity) -> Box { - let positions = world.read_component::(); - let nameds = world.read_component::(); - let metas = world.read_component::(); - - let position = positions.get(entity).unwrap(); - - let packet = SpawnPlayer { - entity_id: entity.id() as i32, - player_uuid: nameds.get(entity).unwrap().uuid, - x: position.current.x, - y: position.current.y, - z: position.current.z, - yaw: degrees_to_stops(position.current.yaw), - pitch: degrees_to_stops(position.current.pitch), - metadata: metas.get(entity).unwrap().to_full_raw_metadata(), - }; - - Box::new(packet) -} diff --git a/server/src/player/inventory.rs b/server/src/player/inventory.rs deleted file mode 100644 index cfed37950..000000000 --- a/server/src/player/inventory.rs +++ /dev/null @@ -1,759 +0,0 @@ -use crate::disconnect_player; -use crate::entity::{EntitySendEvent, PlayerComponent}; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; -use crate::player::digging::PlayerItemDropEvent; -use crate::util::Util; -use feather_core::inventory::{ - Inventory, InventoryType, SlotIndex, HOTBAR_SIZE, SLOT_ARMOR_CHEST, SLOT_ARMOR_FEET, - SLOT_ARMOR_HEAD, SLOT_ARMOR_LEGS, SLOT_HOTBAR_OFFSET, SLOT_OFFHAND, -}; -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{ - CreativeInventoryAction, EntityEquipment, HeldItemChangeServerbound, SetSlot, -}; -use feather_core::network::packet::PacketType; -use feather_core::{Gamemode, ItemStack}; -use num_traits::ToPrimitive; -use shrev::EventChannel; -use smallvec::SmallVec; -use specs::{Component, LazyUpdate, Read, ReadStorage, ReaderId, World, WriteStorage}; -use specs::{DenseVecStorage, SystemData}; -use specs::{Entity, System, Write}; -use std::ops::{Deref, DerefMut}; - -/// Component for storing a player's inventory. -#[derive(Clone, Debug)] -pub struct InventoryComponent { - pub inventory: Inventory, - /// The player's held item. - /// This is stored as an index in the range 0..9. - pub held_item: SlotIndex, -} - -impl InventoryComponent { - pub fn new() -> Self { - Self { - inventory: Inventory::new(InventoryType::Player, 46), - held_item: 0, - } - } - - /// Returns the item in this inventory's - /// main hand. - pub fn item_in_main_hand(&self) -> Option<&ItemStack> { - self.inventory.item_at(SLOT_HOTBAR_OFFSET + self.held_item) - } - - /// Sets the item in this inventory's main hand. - pub fn set_item_in_main_hand(&mut self, item: ItemStack) { - self.inventory - .set_item_at(SLOT_HOTBAR_OFFSET + self.held_item, item); - } -} - -impl Default for InventoryComponent { - fn default() -> Self { - Self::new() - } -} - -impl Deref for InventoryComponent { - type Target = Inventory; - - fn deref(&self) -> &Self::Target { - &self.inventory - } -} - -impl DerefMut for InventoryComponent { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.inventory - } -} - -impl Component for InventoryComponent { - type Storage = DenseVecStorage; -} - -/// An equipment slot, with variants -/// listed in the order of the Entity Equipment -/// IDs to allow for easy conversion using `ToPrimitive`/`FromPrimitive`. -#[derive(Debug, Clone, Copy, ToPrimitive, FromPrimitive, PartialEq, Eq, Hash)] -pub enum Equipment { - MainHand, - OffHand, - Boots, - Leggings, - Chestplate, - Helmet, -} - -impl Equipment { - pub fn from_slot_index(index: SlotIndex) -> Option { - match index { - SLOT_OFFHAND => Some(Equipment::OffHand), - SLOT_ARMOR_FEET => Some(Equipment::Boots), - SLOT_ARMOR_LEGS => Some(Equipment::Leggings), - SLOT_ARMOR_CHEST => Some(Equipment::Chestplate), - SLOT_ARMOR_HEAD => Some(Equipment::Helmet), - _ => None, - } - } - - pub fn slot_index(self, held_item: SlotIndex) -> SlotIndex { - match self { - Equipment::MainHand => held_item + SLOT_HOTBAR_OFFSET, - Equipment::OffHand => SLOT_OFFHAND, - Equipment::Boots => SLOT_ARMOR_FEET, - Equipment::Leggings => SLOT_ARMOR_LEGS, - Equipment::Chestplate => SLOT_ARMOR_CHEST, - Equipment::Helmet => SLOT_ARMOR_HEAD, - } - } -} - -/// Event which is triggered when a player -/// updates their inventory. -/// -/// This event could also be triggered when the player -/// changes their held item. -#[derive(Debug, Clone)] -pub struct InventoryUpdateEvent { - /// The slot(s) affected by the update. - /// - /// Multiple slots could be affected when, for - /// example, a player uses the "drag" inventory interaction. - pub slots: SmallVec<[SlotIndex; 2]>, - /// The player owning the updated inventory. - pub player: Entity, -} - -/// System for handling Creative Inventory Action packets. -pub struct CreativeInventorySystem; - -impl<'a> System<'a> for CreativeInventorySystem { - type SystemData = ( - WriteStorage<'a, InventoryComponent>, - ReadStorage<'a, PlayerComponent>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Read<'a, PacketQueue>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut inventories, players, mut update_events, mut drop_events, packet_queue, lazy) = - data; - - let packets = packet_queue.for_packet(PacketType::CreativeInventoryAction); - - for (player, packet) in packets { - // Creative Inventory Action can only be used in creative - // mode. - let player_comp = players.get(player).unwrap(); - if player_comp.gamemode != Gamemode::Creative { - disconnect_player( - player, - "Attempted to use Creative Inventory Action while not in creative mode" - .to_string(), - &lazy, - ); - continue; - } - - let packet = cast_packet::(&*packet); - - let inventory = inventories.get_mut(player).unwrap(); - - // Slot -1 means that the user clicked outside the window, - // dropping the item. - if packet.slot == -1 { - match &packet.clicked_item { - Some(stack) => { - let event = PlayerItemDropEvent { - slot: None, - stack: stack.clone(), - player, - }; - drop_events.single_write(event); - - // No need to update inventory - continue; - } - None => (), - } - } - - if packet.slot >= inventory.slot_count() as i16 || packet.slot < -1 { - disconnect_player(player, "Slot index out of bounds".to_string(), &lazy); - continue; - } - - match packet.clicked_item.as_ref() { - Some(item) => { - inventory.set_item_at(packet.slot as usize, item.clone()); - } - None => { - inventory.clear_item_at(packet.slot as usize); - } - } - - // Trigger inventory update event - let event = InventoryUpdateEvent { - slots: smallvec![packet.slot as usize], - player, - }; - update_events.single_write(event); - } - } -} - -/// System for handling Held Item Change packets. -pub struct HeldItemChangeSystem; - -impl<'a> System<'a> for HeldItemChangeSystem { - type SystemData = ( - WriteStorage<'a, InventoryComponent>, - Write<'a, EventChannel>, - Read<'a, PacketQueue>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut inventories, mut events, packet_queue, lazy) = data; - - let packets = packet_queue.for_packet(PacketType::HeldItemChangeServerbound); - - for (player, packet) in packets { - let packet = cast_packet::(&*packet); - - if packet.slot as usize >= HOTBAR_SIZE { - disconnect_player(player, "Hotbar index out of bounds".to_string(), &lazy); - continue; - } - - let inventory = inventories.get_mut(player).unwrap(); - inventory.held_item = packet.slot as usize; - - // Trigger event - let event = InventoryUpdateEvent { - slots: smallvec![inventory.held_item as usize + SLOT_HOTBAR_OFFSET], - player, - }; - events.single_write(event); - } - } -} - -/// System for broadcasting equipment updates. -#[derive(Default)] -pub struct HeldItemBroadcastSystem { - reader: Option>, -} - -impl<'a> System<'a> for HeldItemBroadcastSystem { - type SystemData = ( - ReadStorage<'a, InventoryComponent>, - Read<'a, EventChannel>, - Read<'a, Util>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (inventories, events, util) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - let inv = inventories.get(event.player).unwrap(); - - for slot in &event.slots { - // Skip this slot if it is not an equipment update. - if let Ok(equipment) = is_equipment_update(&inv, *slot) { - let slot = equipment.slot_index(inv.held_item); - let item = inv.item_at(slot).cloned(); - - let packet = EntityEquipment::new( - event.player.id() as i32, - equipment.to_i32().unwrap(), - item, - ); - - util.broadcast_entity_update(event.player, packet, Some(event.player)); - } - } - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); - } -} - -/// System which listens to `EntitySendEvent`s and -/// sends entity equipment alongside. -#[derive(Default)] -pub struct EquipmentSendSystem { - reader: Option>, -} - -impl<'a> System<'a> for EquipmentSendSystem { - type SystemData = ( - ReadStorage<'a, InventoryComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (inventories, networks, send_events) = data; - - for event in send_events.read(&mut self.reader.as_mut().unwrap()) { - let network = networks.get(event.player).unwrap(); - let inventory = match inventories.get(event.entity) { - Some(inv) => inv, - None => continue, - }; - - let equipments = [ - Equipment::MainHand, - Equipment::Boots, - Equipment::Leggings, - Equipment::Chestplate, - Equipment::Helmet, - Equipment::OffHand, - ]; - - for equipment in equipments.iter() { - let item = { - let slot = equipment.slot_index(inventory.held_item); - inventory.item_at(slot).cloned() - }; - - let equipment_slot = equipment.to_i32().unwrap(); - - let packet = EntityEquipment::new(event.entity.id() as i32, equipment_slot, item); - send_packet_to_player(network, packet); - } - } - } - - fn setup(&mut self, world: &mut World) { - Self::SystemData::setup(world); - - self.reader = Some(world.fetch_mut::>().register_reader()); - } -} - -/// System for sending the Set Slot packet -/// when a player's inventory is updated. -#[derive(Default)] -pub struct SetSlotSystem { - reader: Option>, -} - -impl<'a> System<'a> for SetSlotSystem { - type SystemData = ( - ReadStorage<'a, InventoryComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (inventories, networks, events) = data; - - for event in events.read(self.reader.as_mut().unwrap()) { - let inv = inventories.get(event.player).unwrap(); - let network = networks.get(event.player).unwrap(); - - for slot in &event.slots { - let packet = SetSlot { - window_id: 0, - slot: *slot as i16, - slot_data: inv.item_at(*slot as usize).cloned(), - }; - - send_packet_to_player(&network, packet); - } - } - } - - setup_impl!(reader); -} - -/// Returns whether the given update to an inventory -/// is an equipment update. -fn is_equipment_update(inv: &InventoryComponent, slot: SlotIndex) -> Result { - if slot >= SLOT_HOTBAR_OFFSET && slot - SLOT_HOTBAR_OFFSET == inv.held_item { - Ok(Equipment::MainHand) - } else if let Some(equipment) = Equipment::from_slot_index(slot) { - Ok(equipment) - } else { - Err(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::inventory::{ItemStack, SLOT_ENTITY_EQUIPMENT_MAIN_HAND}; - use feather_core::item::Item; - use specs::WorldExt; - - #[test] - fn test_creative_inventory_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let packet = CreativeInventoryAction::new( - SLOT_HOTBAR_OFFSET as i16, - Some(ItemStack::new(Item::IronSword, 1)), - ); - - t::receive_packet(&player, &w, packet); - - let mut update_reader = t::reader(&w); - let mut drop_reader = t::reader(&w); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let inv_storage = w.read_component::(); - let inv = inv_storage.get(player.entity).unwrap(); - assert_eq!( - inv.item_at(SLOT_HOTBAR_OFFSET).unwrap(), - &ItemStack::new(Item::IronSword, 1) - ); - - // Confirm that event was triggered - { - let channel = w.fetch::>(); - let events = channel.read(&mut update_reader).collect::>(); - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!(first.slots.as_slice(), &[SLOT_HOTBAR_OFFSET]); - - assert!(w - .fetch::>() - .read(&mut drop_reader) - .next() - .is_none()); - } - - drop(inv_storage); - - let packet = CreativeInventoryAction::new(0, None); - - t::receive_packet(&player, &w, packet.clone()); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let inv_storage = w.read_component::(); - let inv = inv_storage.get(player.entity).unwrap(); - assert_eq!(inv.item_at(0), None); - - drop(inv_storage); - - // Now with a survival mode player... - w.write_component::() - .get_mut(player.entity) - .unwrap() - .gamemode = Gamemode::Survival; - - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_disconnected(&player); - } - - #[test] - fn test_creative_inventory_slot_out_of_bounds() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let packet = CreativeInventoryAction::new(46, Some(ItemStack::new(Item::IronSword, 1))); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_disconnected(&player); - } - - #[test] - fn test_creative_inventory_armor() { - let equipments = [ - Equipment::OffHand, - Equipment::Boots, - Equipment::Leggings, - Equipment::Chestplate, - Equipment::Helmet, - ]; - - for equipment in equipments.iter() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut event_reader = t::reader(&w); - - let packet = CreativeInventoryAction::new( - equipment.slot_index(0) as i16, - Some(ItemStack::new(Item::IronSword, 1)), - ); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - let ch = w.fetch::>(); - let events = ch.read(&mut event_reader).collect::>(); - - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - - assert_eq!(first.slots.as_slice(), &[equipment.slot_index(0)]); - assert_eq!(first.player, player.entity); - } - } - - #[test] - fn test_creative_inventory_system_drop_item() { - // Drop item - slot index -1 - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let stack = ItemStack::new(Item::CookedBeef, 1); - - let mut drop_reader = t::reader(&w); - let mut update_reader = t::reader(&w); - - let packet = CreativeInventoryAction { - slot: -1, - clicked_item: Some(stack.clone()), - }; - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_not_disconnected(&player); - - let channel = w.fetch::>(); - let events = channel.read(&mut update_reader).collect::>(); - assert!(events.is_empty()); - - let channel = w.fetch::>(); - let events = channel.read(&mut drop_reader).collect::>(); - - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - assert_eq!(first.stack, stack); - assert_eq!(first.player, player.entity); - assert_eq!(first.slot, None); - } - - #[test] - fn test_held_item_change_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let slot = 4; - - let mut event_reader = t::reader(&w); - - let packet = HeldItemChangeServerbound::new(slot); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - let channel = w.fetch::>(); - let events = channel.read(&mut event_reader).collect::>(); - - assert_eq!(events.len(), 1); - - let first = events.first().unwrap(); - assert_eq!(first.player, player.entity); - assert_eq!( - first.slots.as_slice(), - &[slot as usize + SLOT_HOTBAR_OFFSET] - ); - - let inventories = w.read_component::(); - let inv = inventories.get(player.entity).unwrap(); - - assert_eq!(inv.held_item, slot as usize); - } - - #[test] - fn test_held_item_change_system_out_of_bounds() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let slot = 9; - - let packet = HeldItemChangeServerbound::new(slot); - t::receive_packet(&player, &w, packet); - - d.dispatch(&w); - w.maintain(); - - t::assert_disconnected(&player); // Slot out of bounds - } - - #[test] - fn test_held_item_broadcast_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - { - let mut invs = w.write_component::(); - let inv = invs.get_mut(player.entity).unwrap(); - inv.held_item = 0; - inv.set_item_at(SLOT_HOTBAR_OFFSET, ItemStack::new(Item::IronSword, 1)); - } - - let event = InventoryUpdateEvent { - player: player.entity, - slots: smallvec![SLOT_HOTBAR_OFFSET], - }; - - w.fetch_mut::>() - .single_write(event); - - d.dispatch(&w); - w.maintain(); - - let packet = t::assert_packet_received(&player2, PacketType::EntityEquipment); - - let packet = cast_packet::(&*packet); - assert_eq!(packet.slot, SLOT_ENTITY_EQUIPMENT_MAIN_HAND as i32); - assert_eq!(packet.entity_id, player.entity.id() as i32); - assert_eq!(packet.item, Some(ItemStack::new(Item::IronSword, 1))); - - t::assert_packet_not_received(&player, PacketType::EntityEquipment); - } - - #[test] - fn test_equipment_send_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - let event = EntitySendEvent { - player: player.entity, - entity: player2.entity, - }; - - w.fetch_mut::>().single_write(event); - - { - let mut invs = w.write_component::(); - let inv = invs.get_mut(player2.entity).unwrap(); - - inv.held_item = 1; - inv.set_item_at(SLOT_HOTBAR_OFFSET + 1, ItemStack::new(Item::IronSword, 1)); - inv.set_item_at(SLOT_ARMOR_HEAD, ItemStack::new(Item::DiamondHelmet, 1)); - } - - d.dispatch(&w); - w.maintain(); - - let packets = t::received_packets(&player, None); - - let packets = packets - .into_iter() - .filter(|packet| packet.ty() == PacketType::EntityEquipment) - .collect::>(); - - assert_eq!(packets.len(), 6); - - for packet in packets { - let packet = cast_packet::(&*packet); - assert_eq!(packet.entity_id, player2.entity.id() as i32); - } - } - - #[test] - fn test_set_slot_system() { - let (mut w, mut d) = t::builder().with(SetSlotSystem::default(), "").build(); - - let player = t::add_player(&mut w); - let stack = ItemStack::new(Item::EnderPearl, 8); - { - let mut inventories = w.write_component::(); - inventories - .get_mut(player.entity) - .unwrap() - .set_item_at(0, stack.clone()); - - let event = InventoryUpdateEvent { - slots: smallvec![0], - player: player.entity, - }; - t::trigger_event(&w, event); - } - - d.dispatch(&w); - w.maintain(); - - let packet = t::assert_packet_received(&player, PacketType::SetSlot); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.window_id, 0); - assert_eq!(packet.slot_data, Some(stack)); - assert_eq!(packet.slot, 0); - } - - #[test] - fn test_is_equipment_update() { - let mut inv = InventoryComponent::default(); - inv.held_item = 0; - - assert!(is_equipment_update(&inv, 21).is_err()); - assert_eq!( - is_equipment_update(&inv, SLOT_HOTBAR_OFFSET), - Ok(Equipment::MainHand) - ); - assert_eq!( - is_equipment_update(&inv, SLOT_ARMOR_HEAD), - Ok(Equipment::Helmet) - ); - assert_eq!( - is_equipment_update(&inv, SLOT_ARMOR_CHEST), - Ok(Equipment::Chestplate) - ); - assert_eq!( - is_equipment_update(&inv, SLOT_ARMOR_LEGS), - Ok(Equipment::Leggings) - ); - assert_eq!( - is_equipment_update(&inv, SLOT_ARMOR_FEET), - Ok(Equipment::Boots) - ); - assert_eq!( - is_equipment_update(&inv, SLOT_OFFHAND), - Ok(Equipment::OffHand) - ); - } -} diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs deleted file mode 100644 index 2cdeeaee6..000000000 --- a/server/src/player/mod.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! This module provides systems and components -//! relating to players, including player movement -//! and inventory handling. - -/// Module for handling player animation broadcasting -/// (e.g. when a player swings their arm). -mod animation; -/// Module for broadcasting when a player joins and leaves. -mod broadcast; -/// Module for handling and broadcasting chat messages. -mod chat; -/// Module for handling the Player Digging packet. -mod digging; -/// Module for initializing the necessary components -/// when a player joins. -mod init; -/// Module for handling player inventory. -mod inventory; -/// Module for handling player movement packets. -/// Also handles loading/unloading chunks when necessary. -mod movement; -/// Module for handling player block placements. -mod placement; -mod resource_pack; -mod save; -mod view; - -pub use broadcast::PlayerDisconnectEvent; -pub use init::create_packet; - -pub use movement::{ - send_chunk_to_player, ChunkCrossSystem, ChunkPendingComponent, LoadedChunksComponent, -}; - -pub use animation::PlayerAnimationEvent; - -pub use digging::PlayerItemDropEvent; -pub use inventory::{InventoryComponent, InventoryUpdateEvent}; -pub use save::save_player_data; - -use crate::player::inventory::SetSlotSystem; -use crate::player::placement::BlockPlacementSystem; -use crate::player::save::PlayerDataSaveSystem; -use crate::player::view::ViewUpdateSystem; -use crate::systems::{ - ANIMATION_BROADCAST, BLOCK_BREAK_BROADCAST, BLOCK_PLACEMENT, CHAT_BROADCAST, CHUNK_CROSS, - CHUNK_SEND, CLIENT_CHUNK_UNLOAD, CREATIVE_INVENTORY, DISCONNECT_BROADCAST, EQUIPMENT_SEND, - HELD_ITEM_BROADCAST, HELD_ITEM_CHANGE, JOIN_BROADCAST, NETWORK, PLAYER_ANIMATION, PLAYER_CHAT, - PLAYER_DATA_SAVE, PLAYER_DIGGING, PLAYER_INIT, PLAYER_MOVEMENT, RESOURCE_PACK_SEND, SET_SLOT, - VIEW_UPDATE, -}; -use animation::{AnimationBroadcastSystem, PlayerAnimationSystem}; -use broadcast::{DisconnectBroadcastSystem, JoinBroadcastSystem}; -use chat::{ChatBroadcastSystem, PlayerChatSystem}; -use digging::BlockUpdateBroadcastSystem; -use digging::PlayerDiggingSystem; -use init::PlayerInitSystem; -use inventory::{ - CreativeInventorySystem, EquipmentSendSystem, HeldItemBroadcastSystem, HeldItemChangeSystem, -}; -use movement::{ChunkSendSystem, ClientChunkUnloadSystem, PlayerMovementSystem}; -use resource_pack::ResourcePackSendSystem; -use specs::DispatcherBuilder; - -pub const PLAYER_EYE_HEIGHT: f64 = 1.62; -pub const PLAYER_EYE_HEIGHT_WHILE_SNEAKING: f64 = 1.54; - -pub fn init_logic(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(PlayerDiggingSystem, PLAYER_DIGGING, &[NETWORK]); - dispatcher.add(PlayerAnimationSystem, PLAYER_ANIMATION, &[NETWORK]); - dispatcher.add(CreativeInventorySystem, CREATIVE_INVENTORY, &[NETWORK]); - dispatcher.add(HeldItemChangeSystem, HELD_ITEM_CHANGE, &[NETWORK]); - dispatcher.add(PlayerMovementSystem, PLAYER_MOVEMENT, &[NETWORK]); - dispatcher.add(PlayerChatSystem, PLAYER_CHAT, &[NETWORK]); - dispatcher.add(BlockPlacementSystem, BLOCK_PLACEMENT, &[NETWORK]); - dispatcher.add( - PlayerDataSaveSystem::default(), - PLAYER_DATA_SAVE, - &[NETWORK], - ); -} - -pub fn init_handlers(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(ViewUpdateSystem::default(), VIEW_UPDATE, &[]); - dispatcher.add(ChunkCrossSystem::default(), CHUNK_CROSS, &[]); - dispatcher.add(ClientChunkUnloadSystem, CLIENT_CHUNK_UNLOAD, &[]); - dispatcher.add(PlayerInitSystem::default(), PLAYER_INIT, &[]); -} - -pub fn init_broadcast(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(HeldItemBroadcastSystem::default(), HELD_ITEM_BROADCAST, &[]); - dispatcher.add(JoinBroadcastSystem::default(), JOIN_BROADCAST, &[]); - dispatcher.add( - DisconnectBroadcastSystem::default(), - DISCONNECT_BROADCAST, - &[], - ); - dispatcher.add( - AnimationBroadcastSystem::default(), - ANIMATION_BROADCAST, - &[], - ); - dispatcher.add(EquipmentSendSystem::default(), EQUIPMENT_SEND, &[]); - dispatcher.add(ResourcePackSendSystem::default(), RESOURCE_PACK_SEND, &[]); - dispatcher.add(ChunkSendSystem::default(), CHUNK_SEND, &[]); - dispatcher.add( - BlockUpdateBroadcastSystem::default(), - BLOCK_BREAK_BROADCAST, - &[], - ); - dispatcher.add(SetSlotSystem::default(), SET_SLOT, &[]); - dispatcher.add(ChatBroadcastSystem::default(), CHAT_BROADCAST, &[]); -} diff --git a/server/src/player/movement.rs b/server/src/player/movement.rs deleted file mode 100644 index 919847c85..000000000 --- a/server/src/player/movement.rs +++ /dev/null @@ -1,469 +0,0 @@ -use std::collections::VecDeque; -use std::ops::{Deref, DerefMut}; -use std::sync::Arc; - -use hashbrown::HashSet; -use rayon::prelude::*; -use shrev::{EventChannel, ReaderId}; -use specs::storage::{BTreeStorage, ComponentEvent}; -use specs::{ - BitSet, Component, Entities, Entity, Join, LazyUpdate, ParJoin, Read, ReadExpect, ReadStorage, - System, WorldExt, Write, WriteStorage, -}; - -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::{ - ChunkData, PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, UnloadChunk, -}; -use feather_core::network::packet::{Packet, PacketType}; -use feather_core::world::chunk::Chunk; -use feather_core::world::{ChunkMap, ChunkPosition, Position}; - -use crate::chunk_logic::{ - load_chunk, ChunkHolderComponent, ChunkHolderReleaseEvent, ChunkHolders, ChunkLoadEvent, - ChunkLoadFailEvent, ChunkWorkerHandle, -}; -use crate::config::Config; -use crate::entity::PositionComponent; -use crate::network::{send_packet_to_player, NetworkComponent, PacketQueue}; -use crate::{TickCount, TPS}; - -// MOVEMENT HANDLING - -/// System for handling player movement -/// packets. -pub struct PlayerMovementSystem; - -impl<'a> System<'a> for PlayerMovementSystem { - type SystemData = (WriteStorage<'a, PositionComponent>, Read<'a, PacketQueue>); - - fn run(&mut self, data: Self::SystemData) { - let (mut positions, packet_queue) = data; - - // Take movement packets - let mut packets = vec![]; - packets.append(&mut packet_queue.for_packet(PacketType::PlayerPosition)); - packets.append(&mut packet_queue.for_packet(PacketType::PlayerPositionAndLookServerbound)); - packets.append(&mut packet_queue.for_packet(PacketType::PlayerLook)); - - // Handle movement packets - for (player, packet) in packets { - let position = positions.get(player).unwrap(); - - // Get position using packet and old position - let new_pos = new_pos_from_packet(position.previous, packet); - - // Set new position - positions.get_mut(player).unwrap().current = new_pos; - } - } -} - -fn new_pos_from_packet(old_pos: Position, packet: Box) -> Position { - match packet.ty() { - PacketType::PlayerPosition => { - let packet = cast_packet::(&*packet); - - position!( - packet.x, - packet.feet_y, - packet.z, - old_pos.pitch, - old_pos.yaw, - packet.on_ground - ) - } - PacketType::PlayerLook => { - let packet = cast_packet::(&*packet); - - position!( - old_pos.x, - old_pos.y, - old_pos.z, - packet.pitch, - packet.yaw, - packet.on_ground - ) - } - PacketType::PlayerPositionAndLookServerbound => { - let packet = cast_packet::(&*packet); - - position!( - packet.x, - packet.feet_y, - packet.z, - packet.pitch, - packet.yaw, - packet.on_ground - ) - } - _ => panic!(), - } -} - -// CHUNK LOAD/UNLOAD HANDLING - -/// Component for storing which chunks a client -/// has loaded and which are queued to be unloaded -/// on the client. -#[derive(Clone, Default, Debug)] -pub struct LoadedChunksComponent { - /// All chunks which are loaded on the client, i.e. - /// which have had a Chunk Data packet sent. - loaded_chunks: HashSet, - /// Chunks queued for unloading on the client. - /// - /// Note that that these chunks will not be unloaded - /// on the server - all that will happen is that an Unload - /// Chunk packet will be sent to the client. This avoids client-side - /// memory leaks. - unload_queue: VecDeque<(ChunkPosition, u64)>, -} - -impl Component for LoadedChunksComponent { - type Storage = BTreeStorage; -} - -/// Component storing what chunks are pending -/// to send to a player. -#[derive(Clone, Debug)] -pub struct ChunkPendingComponent { - pub pending: HashSet, -} - -impl Deref for ChunkPendingComponent { - type Target = HashSet; - - fn deref(&self) -> &Self::Target { - &self.pending - } -} - -impl DerefMut for ChunkPendingComponent { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.pending - } -} - -impl Component for ChunkPendingComponent { - type Storage = BTreeStorage; -} - -/// Time after a player can no longer see a chunk -/// that it is unloaded. -const CHUNK_UNLOAD_TIME: u64 = TPS * 5; // 5 seconds - -/// Event which is triggered when a player crosses -/// chunk boundaries, causing their position's chunk -/// to change. -#[derive(Debug, Clone)] -pub struct ChunkCrossEvent { - /// The player affected by this event. - pub player: Entity, - /// The old chunk position. - pub old: ChunkPosition, - /// The new chunk position. - pub new: ChunkPosition, -} - -/// System that checks when a player crosses chunk boundaries. -/// When the player does so, the system sends Chunk Data packets -/// for chunks within the view distance and also unloads -/// chunks no longer within the player's view distance. -#[derive(Default)] -pub struct ChunkCrossSystem { - dirty: BitSet, - reader: Option>, -} - -impl<'a> System<'a> for ChunkCrossSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - Read<'a, ChunkMap>, - Read<'a, TickCount>, - Read<'a, Arc>, - WriteStorage<'a, LoadedChunksComponent>, - WriteStorage<'a, ChunkHolderComponent>, - ReadStorage<'a, NetworkComponent>, - Write<'a, ChunkHolders>, - Write<'a, EventChannel>, - ReadExpect<'a, ChunkWorkerHandle>, - Read<'a, LazyUpdate>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - positions, - chunk_map, - tick_count, - config, - mut loaded_chunks_comps, - mut chunk_holder_comps, - net_comps, - mut holders, - mut cross_events, - chunk_handle, - lazy, - entities, - ) = data; - - self.dirty.clear(); - - for event in positions.channel().read(self.reader.as_mut().unwrap()) { - match event { - ComponentEvent::Modified(id) | ComponentEvent::Inserted(id) => { - self.dirty.add(*id); - } - _ => (), - } - } - - // Go through events and handle them accordingly - for (position, net, chunk_holder, loaded_chunks, player, _) in ( - &positions, - &net_comps, - &mut chunk_holder_comps, - &mut loaded_chunks_comps, - &entities, - &self.dirty, - ) - .join() - { - let old_chunk_pos = position.previous.chunk_pos(); - let new_chunk_pos = position.current.chunk_pos(); - - if old_chunk_pos != new_chunk_pos { - // Player has moved across chunk boundaries. Handle accordingly. - let chunks = chunks_within_view_distance(&config, new_chunk_pos); - - for chunk in &chunks { - if loaded_chunks.loaded_chunks.contains(chunk) { - // Already sent - nothing to do. - continue; - } - - send_chunk_to_player( - *chunk, - net, - player, - &chunk_map, - &chunk_handle, - &mut holders, - chunk_holder, - loaded_chunks, - &lazy, - ); - } - - // Now, queue all chunks which need to be unloaded for unloading. - let old_chunks = chunks_within_view_distance(&config, old_chunk_pos); - - for chunk in old_chunks { - if chunks.contains(&chunk) { - // Chunk should remain loaded. Nothing to do - continue; - } - - // Queue chunk for unloading. - let time = tick_count.0 + CHUNK_UNLOAD_TIME; - loaded_chunks.unload_queue.push_back((chunk, time)); - } - - // Trigger chunk cross event. - let event = ChunkCrossEvent { - player, - old: old_chunk_pos, - new: new_chunk_pos, - }; - cross_events.single_write(event); - } - } - } - - flagged_setup_impl!(PositionComponent, reader); -} - -/// System for sending chunks to players once they're loaded. -/// -/// This system listens to `ChunkLoadEvent`s. -#[derive(Default)] -pub struct ChunkSendSystem { - load_event_reader: Option>, - fail_event_reader: Option>, -} - -impl<'a> System<'a> for ChunkSendSystem { - type SystemData = ( - WriteStorage<'a, ChunkPendingComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, ChunkMap>, - Read<'a, EventChannel>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut pendings, netcomps, chunk_map, load_events, fail_events) = data; - - for event in load_events.read(&mut self.load_event_reader.as_mut().unwrap()) { - // TODO perhaps this is slightly inefficient? - (&netcomps, &mut pendings) - .par_join() - .for_each(|(net, pending)| { - if pending.contains(&event.pos) { - // It's safe to unwrap the chunk value now, - // because we know it's been loaded. - let chunk = chunk_map.chunk_at(event.pos).unwrap(); - send_chunk_data(chunk, net); - - pending.remove(&event.pos); - } - }); - } - - for event in fail_events.read(self.fail_event_reader.as_mut().unwrap()) { - (&mut pendings).par_join().for_each(|pending| { - if pending.contains(&event.pos) { - // The chunk failed to load - skip sending it. - // See issue #71 - pending.remove(&event.pos); - } - }); - } - } - - setup_impl!(load_event_reader, fail_event_reader); -} - -/// System for sending the Unload Chunk packet when the time comes. -pub struct ClientChunkUnloadSystem; - -impl<'a> System<'a> for ClientChunkUnloadSystem { - type SystemData = ( - WriteStorage<'a, LoadedChunksComponent>, - ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, PositionComponent>, - WriteStorage<'a, ChunkHolderComponent>, - Entities<'a>, - Write<'a, ChunkHolders>, - Read<'a, TickCount>, - Read<'a, Arc>, - Write<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut loaded_chunks_comps, - net_comps, - positions, - mut chunk_holder_comps, - entities, - mut chunk_holders, - tick_count, - config, - mut holder_release_events, - ) = data; - - ( - &mut loaded_chunks_comps, - &net_comps, - &positions, - &mut chunk_holder_comps, - &entities, - ) - .join() - .for_each( - |(loaded_chunks_comp, net_comp, position, chunk_holder_comp, player)| { - // Go through queue and see if it's time to unload any chunks. - while let Some((chunk, time)) = loaded_chunks_comp.unload_queue.front() { - let chunk = *chunk; - if tick_count.0 >= *time { - // Unload if needed. - - let chunks_within_view_distance = - chunks_within_view_distance(&config, position.current.chunk_pos()); - - if chunks_within_view_distance.contains(&chunk) { - // Chunk is within view distance again - don't unload it. - loaded_chunks_comp.unload_queue.pop_front(); - continue; - } - - let unload_chunk = UnloadChunk::new(chunk.x, chunk.z); - send_packet_to_player(net_comp, unload_chunk); - - // Remove chunk from queue. - loaded_chunks_comp.unload_queue.pop_front(); - // Remove from loaded chunk list. - loaded_chunks_comp.loaded_chunks.remove(&chunk); - // Remove hold on chunk so it can be unloaded. - chunk_holders.remove_holder(chunk, player, &mut holder_release_events); - // Remove hold from chunk holder component. - chunk_holder_comp.holds.remove(&chunk); - } else { - // No more chunks in queue that should - // be unloaded - finished. - break; - } - } - }, - ); - } -} - -/// Returns the set of all chunk positions -/// within the server view distance of a given -/// chunk. -fn chunks_within_view_distance(config: &Config, chunk: ChunkPosition) -> HashSet { - let view_distance = i32::from(config.server.view_distance); - let mut results = HashSet::with_capacity((view_distance * view_distance) as usize); - - for x in -view_distance..=view_distance { - for z in -view_distance..=view_distance { - results.insert(ChunkPosition::new(chunk.x + x, chunk.z + z)); - } - } - - results -} - -/// Attempts to send the chunk at the given position to -/// the given player. If the chunk is not loaded, it will -/// be loaded and sent at a later time as soon as it is -/// loaded. -#[allow(clippy::too_many_arguments)] // TODO: get rid of LoadedChunksComponent -pub fn send_chunk_to_player( - chunk_pos: ChunkPosition, - net: &NetworkComponent, - player: Entity, - chunk_map: &ChunkMap, - chunk_handle: &ChunkWorkerHandle, - holders: &mut ChunkHolders, - holder: &mut ChunkHolderComponent, - loaded_chunks: &mut LoadedChunksComponent, - lazy: &LazyUpdate, -) { - holders.insert_holder(chunk_pos, player); - holder.holds.insert(chunk_pos); - loaded_chunks.loaded_chunks.insert(chunk_pos); - - if let Some(chunk) = chunk_map.chunk_at(chunk_pos) { - send_chunk_data(chunk, net); - } else { - // Queue for loading - load_chunk(chunk_handle, chunk_pos); - lazy.exec_mut(move |world| { - world - .write_component::() - .get_mut(player) - .unwrap() - .pending - .insert(chunk_pos); - }); - } -} - -fn send_chunk_data(chunk: &Chunk, net: &NetworkComponent) { - let packet = ChunkData::new(chunk.clone()); - send_packet_to_player(net, packet); -} diff --git a/server/src/player/placement.rs b/server/src/player/placement.rs deleted file mode 100644 index 4974554f2..000000000 --- a/server/src/player/placement.rs +++ /dev/null @@ -1,215 +0,0 @@ -use crate::blocks::{BlockUpdateCause, BlockUpdateEvent}; -use crate::disconnect_player; -use crate::entity::PlayerComponent; -use crate::network::PacketQueue; -use crate::player::{InventoryComponent, InventoryUpdateEvent}; -use crate::prelude::Gamemode; -use feather_core::inventory::SLOT_HOTBAR_OFFSET; -use feather_core::network::cast_packet; -use feather_core::network::packet::implementation::PlayerBlockPlacement; -use feather_core::world::ChunkMap; -use feather_core::{Block, ItemStack, PacketType}; -use feather_item_block::ItemToBlock; -use shrev::EventChannel; -use specs::{LazyUpdate, Read, ReadStorage, System, Write, WriteStorage}; - -/// System for handling Player Block Placement packets -/// and updating the world accordingly. -pub struct BlockPlacementSystem; - -impl<'a> System<'a> for BlockPlacementSystem { - type SystemData = ( - WriteStorage<'a, InventoryComponent>, - ReadStorage<'a, PlayerComponent>, - Write<'a, ChunkMap>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Read<'a, PacketQueue>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut inventories, - players, - mut chunk_map, - mut block_update_events, - mut inventory_update_events, - packet_queue, - lazy, - ) = data; - - let packets = packet_queue.for_packet(PacketType::PlayerBlockPlacement); - - for (player, packet) in packets { - let packet = cast_packet::(&*packet); - - // TODO: handle slabs, blocks with directions, etc. - let inventory = inventories.get_mut(player).unwrap(); - - let item = continue_if_none!(inventory.item_in_main_hand()); - - let block = continue_if_none!(item.ty.to_block()); - - let placed_on = match chunk_map.block_at(packet.location) { - Some(block) => block, - None => { - disconnect_player( - player, - String::from("Attempted to place block in unloaded chunk"), - &lazy, - ); - continue; - } - }; - - // TODO: waterlogged blocks, more - let pos = match placed_on { - Block::Grass | Block::TallGrass(_) | Block::Water(_) | Block::Lava(_) => { - packet.location - } - _ => packet.location + packet.face.placement_offset(), - }; - - let old = match chunk_map.block_at(pos) { - Some(block) => block, - None => { - disconnect_player( - player, - String::from("Attempted to place block in unloaded chunk"), - &lazy, - ); - continue; - } - }; - - chunk_map.set_block_at(pos, block).unwrap(); - - let event = BlockUpdateEvent { - cause: BlockUpdateCause::Player(player), - pos, - old_block: old, - new_block: block, - }; - - block_update_events.single_write(event); - - let gamemode = players.get(player).unwrap().gamemode; - - // Update player's inventory if in survival - if gamemode == Gamemode::Survival { - let item = ItemStack::new(item.ty, item.amount - 1); - inventory.set_item_in_main_hand(item); - - let event = InventoryUpdateEvent { - slots: smallvec![SLOT_HOTBAR_OFFSET + inventory.held_item], - player, - }; - inventory_update_events.single_write(event); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::inventory::SLOT_HOTBAR_OFFSET; - use feather_core::network::packet::implementation::Face; - use feather_core::{Block, BlockPosition, Item, ItemStack}; - use specs::WorldExt; - - #[test] - fn test_block_placement_system() { - let (mut w, mut d) = t::builder().with(BlockPlacementSystem, "").build(); - - t::populate_with_air(&mut w); - - let player = t::add_player(&mut w); - - { - let mut inventories = w.write_component::(); - inventories - .get_mut(player.entity) - .unwrap() - .set_item_at(SLOT_HOTBAR_OFFSET, ItemStack::new(Item::Cobblestone, 1)); - - let mut players = w.write_component::(); - players.get_mut(player.entity).unwrap().gamemode = Gamemode::Survival; - } - - let pos = BlockPosition::new(10, 20, 30); - - let packet = PlayerBlockPlacement { - location: pos, - face: Face::Top, - hand: 0, - cursor_position_x: 0.0, - cursor_position_y: 0.0, - cursor_position_z: 0.0, - }; - t::receive_packet(&player, &w, packet); - - let mut reader = t::reader(&w); - - d.dispatch(&w); - w.maintain(); - - let events = t::triggered_events::(&w, &mut reader); - let first = events.first().unwrap(); - - assert_eq!(first.cause, BlockUpdateCause::Player(player.entity)); - assert_eq!(first.old_block, Block::Air); - assert_eq!(first.new_block, Block::Cobblestone); - assert_eq!(first.pos, pos + BlockPosition::new(0, 1, 0)); - - let inventory = w - .read_component::() - .get(player.entity) - .unwrap() - .clone(); - assert_eq!(inventory.item_in_main_hand(), None); - } - - #[test] - fn test_block_placement_system_unloaded_chunk() { - let (mut w, mut d) = t::builder().with(BlockPlacementSystem, "").build(); - - t::populate_with_air(&mut w); - - let player = t::add_player(&mut w); - - { - let mut inventories = w.write_component::(); - inventories - .get_mut(player.entity) - .unwrap() - .set_item_at(SLOT_HOTBAR_OFFSET, ItemStack::new(Item::Cobblestone, 1)); - - let mut players = w.write_component::(); - players.get_mut(player.entity).unwrap().gamemode = Gamemode::Survival; - } - - let pos = BlockPosition::new(1000, 100, 2000); - - let packet = PlayerBlockPlacement { - location: pos, - face: Face::Top, - hand: 0, - cursor_position_x: 0.0, - cursor_position_y: 0.0, - cursor_position_z: 0.0, - }; - t::receive_packet(&player, &w, packet); - - let mut reader = t::reader(&w); - - d.dispatch(&w); - w.maintain(); - - t::assert_disconnected(&player); - - assert!(t::triggered_events::(&w, &mut reader).is_empty()); - } -} diff --git a/server/src/player/resource_pack.rs b/server/src/player/resource_pack.rs deleted file mode 100644 index 917536250..000000000 --- a/server/src/player/resource_pack.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! System for sending resource pack to new players. - -use crate::config::Config; -use crate::joinhandler::PlayerJoinEvent; -use crate::network::{send_packet_to_player, NetworkComponent}; -use feather_core::network::packet::implementation::ResourcePackSend; -use shrev::{EventChannel, ReaderId}; -use specs::{Read, ReadStorage, System}; -use std::sync::Arc; - -/// System for sending resource pack to new players, -/// if enabled. -/// -/// This system listens to `PlayerJoinEvent`s. -#[derive(Default)] -pub struct ResourcePackSendSystem { - reader: Option>, -} - -impl<'a> System<'a> for ResourcePackSendSystem { - type SystemData = ( - ReadStorage<'a, NetworkComponent>, - Read<'a, Arc>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (networks, config, join_events) = data; - if config.resource_pack.url.is_empty() { - return; // Resource pack not enabled - } - - for event in join_events.read(self.reader.as_mut().unwrap()) { - let network = networks.get(event.player).unwrap(); - - let packet = ResourcePackSend { - url: config.resource_pack.url.clone(), - hash: config.resource_pack.hash.to_lowercase(), - }; - - send_packet_to_player(&network, packet); - } - } - - setup_impl!(reader); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::network::cast_packet; - use feather_core::network::packet::PacketType; - use specs::WorldExt; - - #[test] - fn test_resource_pack_send_system() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let event = PlayerJoinEvent { - player: player.entity, - }; - t::trigger_event(&w, event); - - let url = "https://rust-lang.org/".to_string(); - let hash = "bLa".to_string(); - - { - let mut config = Config::clone(&w.fetch::>()); - config.resource_pack.url = url.clone(); - config.resource_pack.hash = hash.clone(); - w.insert(Arc::new(config)); - } - - d.dispatch(&w); - w.maintain(); - - let packet = t::assert_packet_received(&player, PacketType::ResourcePackSend); - let packet = cast_packet::(&*packet); - - assert_eq!(packet.url, url); - assert_eq!(packet.hash, hash.to_lowercase()); - } -} diff --git a/server/src/player/save.rs b/server/src/player/save.rs deleted file mode 100644 index df1ac1140..000000000 --- a/server/src/player/save.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Saving of player data files and a system to save -//! player data on disconnect. - -use crate::entity::{NamedComponent, PlayerComponent, PositionComponent}; -use crate::player::{InventoryComponent, PlayerDisconnectEvent}; -use crate::prelude::Config; -use crossbeam::Receiver; -use feather_core::entity::BaseEntityData; -use feather_core::inventory::Inventory; -use feather_core::player_data::{InventorySlot, PlayerData}; -use feather_core::{player_data, Gamemode, Position}; -use shrev::{EventChannel, ReaderId}; -use specs::{Read, ReadStorage, System}; -use std::path::Path; -use std::sync::Arc; -use uuid::Uuid; - -/// System to save player data upon disconnect. -/// -/// This system listens to `PlayerDisconnectEvent`s. -#[derive(Default)] -pub struct PlayerDataSaveSystem { - reader: Option>, -} - -impl<'a> System<'a> for PlayerDataSaveSystem { - type SystemData = ( - Read<'a, Arc>, - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, PlayerComponent>, - ReadStorage<'a, NamedComponent>, - ReadStorage<'a, InventoryComponent>, - Read<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (config, positions, players, nameds, inventories, disconnect_events) = data; - - for event in disconnect_events.read(self.reader.as_mut().unwrap()) { - let player = players.get(event.player).unwrap(); - save_player_data( - &config, - positions.get(event.player).unwrap().current, - player.gamemode, - &inventories.get(event.player).unwrap().inventory, - nameds.get(event.player).unwrap().uuid, - ); - } - } - - setup_impl!(reader); -} - -/// Saves a player's data. -/// -/// This operation is performed asynchronously, -/// and a channel is returned which will receive -/// a message upon completion. -pub fn save_player_data( - config: &Config, - position: Position, - gamemode: Gamemode, - inventory: &Inventory, - uuid: Uuid, -) -> Receiver<()> { - let data = PlayerData { - entity: BaseEntityData { - velocity: vec![0.0; 3], // Player velocity has no effect - position: vec![position.x, position.y, position.z], - rotation: vec![position.yaw, position.pitch], - }, - gamemode: gamemode.get_id() as i32, - inventory: inventory - .items() - .iter() - .enumerate() - .filter_map(|(index, item)| match item.clone() { - Some(item) => Some((index, item)), - None => None, - }) - .map(|(index, item)| InventorySlot::from_network_index(index, item)) - .collect(), - }; - - // Channel used to communicate with Tokio task - let (tx, rx) = crossbeam::bounded(1); - - let world_dir = Path::new(&config.world.name).to_owned(); - - tokio_executor::blocking::run(move || { - if let Err(e) = player_data::save_player_data(world_dir.as_path(), uuid, data) { - error!("Failed to save player data for UUID {}: {:?}", uuid, e); - } else { - debug!("Saved player data for UUID {}", uuid); - } - - let _ = tx.send(()); // Channel could have been dropped, so ignore result - }); - - rx -} diff --git a/server/src/player/view.rs b/server/src/player/view.rs deleted file mode 100644 index d0cf7504c..000000000 --- a/server/src/player/view.rs +++ /dev/null @@ -1,221 +0,0 @@ -//! This module implements creating and destroying -//! entities on the client when a player moves. -//! -//! When a player crosses chunk boundaries, the following -//! takes place: -//! * We send a `Destroy Entities` packet containing all -//! entities which are no longer within the view distance. -//! * We spawn an entity on the client for every entity -//! which is now within the view distance. -//! -//! This is handled by `ViewUpdateSystem`, which listens -//! to `ChunkCrossEvent`s. - -use crate::config::Config; -use crate::entity::ChunkEntities; -use crate::lazy::LazyUpdateExt; -use crate::network::{send_packet_to_player, NetworkComponent}; -use crate::player::movement::ChunkCrossEvent; -use feather_core::network::packet::implementation::DestroyEntities; -use shrev::EventChannel; -use specs::{LazyUpdate, Read, ReadStorage, ReaderId, System}; -use std::sync::Arc; - -/// System for updating entities visible -/// by the client. -#[derive(Default)] -pub struct ViewUpdateSystem { - reader: Option>, -} - -impl<'a> System<'a> for ViewUpdateSystem { - type SystemData = ( - ReadStorage<'a, NetworkComponent>, - Read<'a, EventChannel>, - Read<'a, ChunkEntities>, - Read<'a, Arc>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (networks, cross_events, chunk_entities, config, lazy) = data; - - for event in cross_events.read(self.reader.as_mut().unwrap()) { - // Find new and old entities. - let old_entities = - chunk_entities.entites_within_view_distance(event.old, config.server.view_distance); - let new_entities = - chunk_entities.entites_within_view_distance(event.new, config.server.view_distance); - - let mut to_destroy = vec![]; - - // Compute entities which are only present in one of the sets. - // If an entity is only present in `old_entities` and not `new_entities`, - // it should be destroyed on the client. - // If an entity is only present in `new_entities`, it should be spawned. - for entity in old_entities.symmetric_difference(&new_entities) { - if *entity == event.player { - continue; - } - - if old_entities.contains(entity) { - // Entity is in `old_entities` but not in `new_entities`. - // Destroy it. If the entity is a player, also destroy this player - // on the client. - to_destroy.push(entity.id() as i32); - - if let Some(network) = networks.get(*entity) { - let packet = DestroyEntities { - entity_ids: vec![event.player.id() as i32], - }; - send_packet_to_player(network, packet); - } - } else { - // Entity is in `new_entities` but not in `old_entities`. - // Spawn it. If the entity is a player, also send this player - // to that entity. - lazy.send_entity_to_player(event.player, *entity); - - if networks.get(*entity).is_some() { - lazy.send_entity_to_player(*entity, event.player); - } - } - } - - if !to_destroy.is_empty() { - let packet = DestroyEntities { - entity_ids: to_destroy, - }; - send_packet_to_player(&networks.get(event.player).unwrap(), packet); - } - } - } - - setup_impl!(reader); -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::{item, PositionComponent}; - use crate::testframework as t; - use feather_core::network::cast_packet; - use feather_core::network::packet::implementation::{SpawnObject, SpawnPlayer}; - use feather_core::{ChunkPosition, Item, ItemStack, PacketType}; - use hashbrown::HashSet; - use specs::{Builder, WorldExt}; - - #[test] - fn test_view_update_system() { - let (mut world, mut dispatcher) = t::builder() - .with(ViewUpdateSystem::default(), "view") - .build(); - - let player_chunk = ChunkPosition::new(0, 0); - - let player1 = t::add_player_without_holder(&mut world); - let player2 = t::add_player_without_holder(&mut world); - - let entity1 = item::create( - &world.fetch(), - &world.fetch(), - ItemStack::new(Item::Stone, 0), - 0, - ) - .with(PositionComponent::default()) - .build(); - let entity2 = item::create( - &world.fetch(), - &world.fetch(), - ItemStack::new(Item::Stone, 0), - 0, - ) - .with(PositionComponent::default()) - .build(); - let entity3 = item::create( - &world.fetch(), - &world.fetch(), - ItemStack::new(Item::Stone, 0), - 0, - ) - .with(PositionComponent::default()) - .build(); - let entity4 = item::create( - &world.fetch(), - &world.fetch(), - ItemStack::new(Item::Stone, 0), - 0, - ) - .with(PositionComponent::default()) - .build(); - - let mut config = Config::default(); - config.server.view_distance = 4; - world.insert(Arc::new(config)); - - { - let mut chunk_entities = world.fetch_mut::(); - chunk_entities.add_to_chunk(player_chunk, player1.entity); - chunk_entities.add_to_chunk(player_chunk, player2.entity); - chunk_entities.add_to_chunk(ChunkPosition::new(3, -3), entity1); - chunk_entities.add_to_chunk(player_chunk, entity2); - chunk_entities.add_to_chunk(ChunkPosition::new(4, -3), entity3); - chunk_entities.add_to_chunk(ChunkPosition::new(100, 103), entity4); - } - - let event = ChunkCrossEvent { - player: player1.entity, - old: ChunkPosition::new(100, 103), - new: player_chunk, - }; - t::trigger_event(&world, event); - - world.maintain(); - dispatcher.dispatch(&world); - world.maintain(); - dispatcher.dispatch(&world); - world.maintain(); - - let packets = t::received_packets(&player1, None); - - let mut received_spawns = HashSet::new(); - - for packet in packets { - dbg!(packet.ty()); - match packet.ty() { - PacketType::DestroyEntities => { - let packet = cast_packet::(&*packet); - let destroyed = packet.entity_ids.iter().cloned().collect::>(); - assert_eq!(destroyed.len(), 1); - assert!(destroyed.contains(&(entity4.id() as i32))); - } - PacketType::SpawnObject => { - let packet = cast_packet::(&*packet); - received_spawns.insert(packet.entity_id); - } - PacketType::SpawnPlayer => { - let packet = cast_packet::(&*packet); - received_spawns.insert(packet.entity_id); - } - _ => (), - } - } - - println!("{:?}", received_spawns); - assert_eq!(received_spawns.len(), 3); - assert!(received_spawns.contains(&(entity1.id() as i32))); - assert!(received_spawns.contains(&(entity2.id() as i32))); - assert!(received_spawns.contains(&(player2.entity.id() as i32))); - - // Confirm that `player1` was sent to `player2`. - let packets = t::received_packets(&player2, None); - assert_eq!(packets.len(), 2); // One for Spawn Player, one for metadata - - let packet = packets - .into_iter() - .find(|packet| packet.ty() == PacketType::SpawnPlayer) - .unwrap(); - let packet = cast_packet::(&*packet); - assert_eq!(packet.entity_id, player1.entity.id() as i32); - } -} diff --git a/server/src/prelude.rs b/server/src/prelude.rs deleted file mode 100644 index b656211e1..000000000 --- a/server/src/prelude.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub use super::TPS; -pub use crate::config::Config; -pub use feather_core::prelude::*; -pub use std::cell::RefCell; -pub use std::rc::Rc; diff --git a/server/src/systems.rs b/server/src/systems.rs deleted file mode 100644 index a216a294b..000000000 --- a/server/src/systems.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Constant string names for each system. -//! This file does not actually contain the systems -//! themselves. - -// Chunk logic -pub const CHUNK_LOAD: &str = "chunk_load"; -pub const CHUNK_OPTIMIZE: &str = "chunk_optimize"; - -pub const CHUNK_UNLOAD: &str = "chunk_unload"; -pub const CHUNK_HOLD_REMOVE: &str = "chunk_hold_remove"; - -// Player -pub const PLAYER_DIGGING: &str = "player_digging"; -pub const PLAYER_ANIMATION: &str = "player_animation"; -pub const CREATIVE_INVENTORY: &str = "creative_inventory"; -pub const HELD_ITEM_CHANGE: &str = "held_item_change"; -pub const PLAYER_MOVEMENT: &str = "player_movement"; -pub const PLAYER_CHAT: &str = "player_chat"; -pub const BLOCK_PLACEMENT: &str = "block_placement"; -pub const PLAYER_DATA_SAVE: &str = "player_data_save"; - -pub const CHUNK_CROSS: &str = "chunk_cross"; -pub const PLAYER_INIT: &str = "player_init"; -pub const CLIENT_CHUNK_UNLOAD: &str = "client_chunk_unload"; - -pub const VIEW_UPDATE: &str = "view_update"; -pub const HELD_ITEM_BROADCAST: &str = "held_item_broadcast"; -pub const JOIN_BROADCAST: &str = "join_broadcast"; -pub const DISCONNECT_BROADCAST: &str = "disconnect_broadcast"; -pub const ANIMATION_BROADCAST: &str = "animation_broadcast"; -pub const EQUIPMENT_SEND: &str = "equipment_send"; -pub const RESOURCE_PACK_SEND: &str = "resource_pack_send"; -pub const CHUNK_SEND: &str = "chunk_send"; -pub const BLOCK_BREAK_BROADCAST: &str = "block_break_broadcast"; -pub const BLOCK_UPDATE_PROPAGATE: &str = "block_update_propagate"; -pub const BLOCK_FALLING_CREATION: &str = "block_falling_creation"; -pub const SET_SLOT: &str = "set_slot"; -pub const CHAT_BROADCAST: &str = "chat_broadcast"; - -// Entity -pub const ITEM_COLLECT: &str = "item_collect"; - -pub const CHUNK_ENTITIES_UPDATE: &str = "chunk_entities_update"; -pub const CHUNK_ENTITIES_LOAD: &str = "chunk_entities_load"; -pub const ENTITY_DESTROY: &str = "entity_destroy"; -pub const ITEM_SPAWN: &str = "item_spawn"; -pub const ITEM_MERGE: &str = "item_merge"; -pub const SHOOT_ARROW: &str = "shoot_arrow"; -pub const CHUNK_SAVE: &str = "chunk_save"; - -pub const ENTITY_MOVE_BROADCAST: &str = "entity_move_broadcast"; -pub const ENTITY_SPAWN_BROADCAST: &str = "entity_spawn_broadcast"; -pub const ENTITY_VELOCITY_BROADCAST: &str = "entity_velocity_broadcast"; -pub const ENTITY_DESTROY_BROADCAST: &str = "entity_destroy_broadcast"; -pub const ENTITY_METADATA_BROADCAST: &str = "entity_metadata_broadcast"; -pub const BLOCK_FALLING_LANDING: &str = "block_falling_landing"; - -// Physics -pub const ENTITY_PHYSICS: &str = "entity_physics"; - -// Other -pub const JOIN_HANDLER: &str = "join_handler"; -pub const NETWORK: &str = "network"; - -pub const TIME_INCREMENT: &str = "time_increment"; -pub const TIME_SEND: &str = "time_send"; - -pub const BROADCASTER: &str = "broadcaster"; diff --git a/server/src/testframework.rs b/server/src/testframework.rs deleted file mode 100644 index fef503cde..000000000 --- a/server/src/testframework.rs +++ /dev/null @@ -1,481 +0,0 @@ -//! Helper framework for writing unit tests. - -use std::net::TcpListener; -use std::sync::atomic::AtomicUsize; -use std::sync::Arc; - -use glm::DVec3; -use rand::Rng; -use shrev::EventChannel; -use specs::{Builder, Dispatcher, DispatcherBuilder, Entity, ReaderId, System, World, WorldExt}; -use uuid::Uuid; - -use feather_core::level::LevelData; -use feather_core::network::packet::{Packet, PacketType}; -use feather_core::world::block::Block; -use feather_core::world::chunk::Chunk; -use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition, Position}; -use feather_core::Gamemode; - -use crate::chunk_logic::{ChunkHolders, ChunkLoadSystem}; -use crate::config::Config; -use crate::entity::metadata::{self, Metadata}; -use crate::entity::{ - ArrowComponent, ChunkEntities, EntityDestroyEvent, EntitySendEvent, EntitySpawnEvent, - ItemComponent, LastKnownPositionComponent, NamedComponent, PacketCreatorComponent, - PlayerComponent, PositionComponent, SerializerComponent, VelocityComponent, -}; -use crate::io::ServerToWorkerMessage; -use crate::network::{NetworkComponent, PacketQueue}; -use crate::physics::PhysicsComponent; -use crate::player::{InventoryComponent, PlayerDisconnectEvent}; -use crate::util::BroadcasterSystem; -use crate::worldgen::{EmptyWorldGenerator, WorldGenerator}; -use crate::{player, PlayerCount}; -use bitflags::_core::cell::RefCell; - -/// Initializes a Specs world and dispatcher -/// using default configuration options and an -/// available server port. -pub fn init_world<'a, 'b>() -> (World, Dispatcher<'a, 'b>) { - let mut config = Config::default(); - config.server.port = find_open_port().unwrap(); - - let config = Arc::new(config); - - let player_count = Arc::new(PlayerCount(AtomicUsize::new(0))); - let server_icon = Arc::new(None); - let ioman = super::init_io_manager( - Arc::clone(&config), - Arc::clone(&player_count), - Arc::clone(&server_icon), - ); - let level = LevelData::default(); - - let (mut world, dispatcher) = super::init_world(config, player_count, ioman, level); - register_components(&mut world); - (world, dispatcher) -} - -pub struct Player { - pub entity: Entity, - pub network_sender: crossbeam::Sender, - pub network_receiver: RefCell>, -} - -/// Adds a player to the world, inserting -/// all the necessary components. Returns -/// a number of useful channels. -/// -/// # Notes -/// * A `ChunkHolders` and `ChunkEntities` entry -/// is created for the player. If this behavior is not -/// desired, use `add_player_without_holder`. -pub fn add_player(world: &mut World) -> Player { - let player = add_player_without_holder(world); - - let mut chunk_holders = world.fetch_mut::(); - - let view_distance = i32::from(world.fetch::>().server.view_distance); - - for x in -view_distance..=view_distance { - for z in -view_distance..=view_distance { - chunk_holders.insert_holder(ChunkPosition::new(x, z), player.entity); - } - } - - let mut chunk_entities = world.fetch_mut::(); - chunk_entities.add_to_chunk(ChunkPosition::new(0, 0), player.entity); - - player -} - -/// Adds a player to the world without adding the `ChunkHolders` -/// and `ChunkEntities` entries. -pub fn add_player_without_holder(world: &mut World) -> Player { - let (ns1, nr1) = futures::channel::mpsc::unbounded(); - let (ns2, nr2) = crossbeam::unbounded(); - let entity = world - .create_entity() - .with(NetworkComponent::new(ns1, nr2)) - .with(PlayerComponent { - gamemode: Gamemode::Creative, - profile_properties: vec![], - }) - .with(PositionComponent { - current: position!(0.0, 0.0, 0.0), - previous: position!(0.0, 0.0, 0.0), - }) - .with(NamedComponent { - display_name: "".to_string(), - uuid: Uuid::new_v4(), - }) - .with(InventoryComponent::default()) - .with(Metadata::Player(metadata::Player::default())) - .with(LastKnownPositionComponent::default()) - .with(PacketCreatorComponent(&player::create_packet)) - .build(); - - Player { - entity, - network_sender: ns2, - network_receiver: RefCell::new(nr1), - } -} - -/// Asserts that the given player has received -/// a packet of the given type, returning the packet. -pub fn assert_packet_received(player: &Player, ty: PacketType) -> Box { - while let Ok(Some(msg)) = player.network_receiver.borrow_mut().try_next() { - if let ServerToWorkerMessage::SendPacket(packet) = msg { - if packet.ty() == ty { - return packet; - } - } - } - - panic!(); -} - -/// Asserts that a player did not receive -/// any packets of the given type. -/// Panics if not. -pub fn assert_packet_not_received(player: &Player, ty: PacketType) { - while let Ok(Some(msg)) = player.network_receiver.borrow_mut().try_next() { - if let ServerToWorkerMessage::SendPacket(packet) = msg { - assert_ne!(packet.ty(), ty); - } - } -} - -/// Retrieves up to `cap` packets sent to a player, if any. -/// If `cap` is set to `None`, all packets will be read. -/// -/// Note that this function consumes messages in -/// the network channel until enough packets have been read. -pub fn received_packets(player: &Player, cap: Option) -> Vec> { - let mut result = vec![]; - - while let Ok(Some(msg)) = player.network_receiver.borrow_mut().try_next() { - if let ServerToWorkerMessage::SendPacket(pack) = msg { - result.push(pack); - } - if let Some(cap) = cap.as_ref() { - if result.len() >= *cap { - break; - } - } - } - - result -} - -/// Adds a received packet to the packet queue -/// for a given player. -pub fn receive_packet(player: &Player, world: &World, packet: P) { - let queue = world.fetch_mut::(); - queue.add_for_packet(player.entity, Box::new(packet)); -} - -/// Attempts to find an available port. -fn find_open_port() -> Option { - let start = rand::thread_rng().gen_range(10000, 30000); - (start..60000).find(|port| TcpListener::bind(("127.0.0.1", *port)).is_ok()) -} - -/// Asserts that a player was disconnected, panicking if not. -pub fn assert_disconnected(player: &Player) { - let mut disconnected = false; - for packet in received_packets(player, None) { - if packet.ty() == PacketType::DisconnectPlay { - disconnected = true; - } - } - - assert!(disconnected); -} - -/// Asserts that a player was not disconnected, panicking -/// if they were. -pub fn assert_not_disconnected(player: &Player) { - let mut disconnected = false; - for packet in received_packets(player, None) { - if packet.ty() == PacketType::DisconnectPlay { - disconnected = true; - } - } - - assert!(!disconnected); -} - -/// Sends a packet to the player. -pub fn send_packet(player: &Player, packet: P) { - player - .network_sender - .send(ServerToWorkerMessage::NotifyPacketReceived(Box::new( - packet, - ))) - .unwrap(); -} - -/// Registers a reader for events of the given type. -pub fn reader(w: &World) -> ReaderId { - let mut channel = w.fetch_mut::>(); - channel.register_reader() -} - -/// Triggers the given event, writing it to -/// the corresponding `EventChannel`. -pub fn trigger_event(world: &World, event: E) { - let mut channel = world.fetch_mut::>(); - channel.single_write(event); -} - -/// Returns all triggered events of a given type. -pub fn triggered_events( - world: &World, - reader: &mut ReaderId, -) -> Vec { - let channel = world.fetch::>(); - channel.read(reader).cloned().collect() -} - -/// Populates a 15x15 area of chunks around the origin -/// with air. -pub fn populate_with_air(world: &mut World) { - for x in -15..=15 { - for z in -15..=15 { - let chunk = Chunk::new(ChunkPosition::new(x, z)); - world - .fetch_mut::() - .set_chunk_at(chunk.position(), chunk); - } - } -} - -/// Asserts that an entity was not removed. -pub fn assert_not_removed(world: &World, entity: Entity) { - assert!(world.entities().is_alive(entity)); -} - -/// Asserts that an entity was removed. -pub fn assert_removed(world: &World, entity: Entity) { - assert!(!world.entities().is_alive(entity)); -} - -/// Retrieves the position of an entity. -pub fn entity_pos(world: &World, entity: Entity) -> Position { - world - .read_component::() - .get(entity) - .unwrap() - .current -} - -/// Retrieves the velocity of an entity. -pub fn entity_vel(world: &World, entity: Entity) -> Option { - if let Some(comp) = world.read_component::().get(entity) { - Some(comp.0) - } else { - None - } -} - -/// Sets an entity's position. -pub fn set_entity_pos(world: &World, entity: Entity, pos: Position) { - let mut storage = world.write_component::(); - storage.get_mut(entity).unwrap().current = pos; -} - -/// Sets an entity's velocity. -pub fn set_entity_velocity(world: &World, entity: Entity, vel: DVec3) { - let mut storage = world.write_component::(); - storage.get_mut(entity).unwrap().0 = vel; -} - -/// Sets the block at the given position in the world. -pub fn set_block(x: i32, y: i32, z: i32, block: Block, world: &World) { - let mut chunk_map = world.fetch_mut::(); - chunk_map - .set_block_at(BlockPosition::new(x, y, z), block) - .unwrap(); -} - -/// A dispatcher builder for isolating tests. -pub struct TestBuilder<'a, 'b> { - world: World, - dispatcher: DispatcherBuilder<'a, 'b>, -} - -impl<'a, 'b> TestBuilder<'a, 'b> { - pub fn with(mut self, system: S, name: &'static str) -> Self - where - S: for<'c> System<'c> + Send + 'a, - { - self.dispatcher.add(system, name, &[]); - self - } - - pub fn with_dep(mut self, system: S, name: &'static str, deps: &[&'static str]) -> Self - where - S: for<'c> System<'c> + Send + 'a, - { - self.dispatcher.add(system, name, deps); - self - } - - pub fn build(mut self) -> (World, Dispatcher<'a, 'b>) { - self.world - .insert(Arc::new(PlayerCount(AtomicUsize::new(0)))); - self.world - .insert(EventChannel::::new()); - self.world.insert(EventChannel::::new()); - self.world.insert(EventChannel::::new()); - self.world.insert(crate::time::Time(0)); - self.world.insert(ChunkHolders::default()); - self.world.insert(ChunkEntities::default()); - self.world.insert(Arc::new(Config::default())); - - let generator: Arc = Arc::new(EmptyWorldGenerator {}); - self.world.insert(generator); - - let mut chunk_system = ChunkLoadSystem {}; - chunk_system.setup(&mut self.world); - - // Insert the broadcaster system, since it is so commonly - // used that it should be used for all tests. - self.dispatcher.add_barrier(); - self.dispatcher.add(BroadcasterSystem, "", &[]); - - let mut dispatcher = self.dispatcher.build(); - dispatcher.setup(&mut self.world); - - register_components(&mut self.world); - - (self.world, dispatcher) - } -} - -fn register_components(world: &mut World) { - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - world.register::(); - - world - .entry() - .or_insert(EventChannel::::default()); -} - -pub fn builder<'a, 'b>() -> TestBuilder<'a, 'b> { - TestBuilder { - world: World::new(), - dispatcher: DispatcherBuilder::new(), - } -} - -/// Heh... tests for the testing framework. -/// Not sure what the point of this is, since -/// all other tests would fail if the testing -/// framework didn't work. -mod tests { - use feather_core::network::packet::implementation::{DisconnectPlay, LoginStart}; - - use crate::entity::{PlayerComponent, PositionComponent}; - use crate::network::{send_packet_to_player, NetworkComponent}; - - use super::*; - - #[test] - fn test_find_open_port() { - let port = find_open_port().unwrap(); - println!("Found open port: {}", port); - assert!(TcpListener::bind(("127.0.0.1", port)).is_ok()); - } - - #[test] - fn test_init_world() { - // Check that initializing the world doesn't cause - // a panic. - let (w, mut d) = init_world(); - - // Check that running the dispatcher works fine - d.dispatch(&w); - } - - #[test] - fn test_add_player() { - let (mut w, _) = init_world(); - - let entity = add_player(&mut w).entity; - - assert!(w.read_component::().get(entity).is_some()); - assert!(w - .read_component::() - .get(entity) - .is_some()); - assert!(w.read_component::().get(entity).is_some()); - } - - #[test] - fn test_received_packets() { - let (mut w, _) = init_world(); - - let player = add_player(&mut w); - - let cap = 1; - send_packet_to_player( - w.read_component().get(player.entity).unwrap(), - LoginStart::new("".to_string()), - ); - send_packet_to_player( - w.read_component().get(player.entity).unwrap(), - LoginStart::new("".to_string()), - ); - - let packets = received_packets(&player, Some(cap)); - assert_eq!(packets.len(), 1); - - let packets = received_packets(&player, Some(cap)); - assert_eq!(packets.len(), 1); - } - - #[test] - #[should_panic] - fn test_assert_packet_received() { - let (mut w, _) = init_world(); - - let player = add_player(&mut w); - assert_packet_received(&player, PacketType::Handshake); - } - - #[test] - #[should_panic] - fn test_assert_disconnected() { - let (mut w, _) = init_world(); - - let player = add_player(&mut w); - assert_disconnected(&player); - } - - #[test] - #[should_panic] - fn test_assert_not_disconnected() { - let (mut w, _) = init_world(); - - let disconnect = DisconnectPlay::new("bla".to_string()); - - let player = add_player(&mut w); - send_packet_to_player(w.read_component().get(player.entity).unwrap(), disconnect); - assert_not_disconnected(&player); - } -} diff --git a/server/src/util/broadcaster.rs b/server/src/util/broadcaster.rs deleted file mode 100644 index 7da99c586..000000000 --- a/server/src/util/broadcaster.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! Implements a broadcaster, used to lazily broadcast -//! packets to players able to see a given entity. - -use crate::chunk_logic::ChunkHolders; -use crate::entity::PositionComponent; -use crate::network::{send_packet_boxed_to_player, NetworkComponent}; -use crate::util::Util; -use crossbeam::queue::SegQueue; -use feather_core::{ChunkPosition, Packet}; -use specs::{Entities, Entity, Read, ReadStorage, System}; - -/// Broadcaster used to lazily broadcast packets. -#[derive(Default)] -pub struct Broadcaster { - /// Internal queue of broadcasts to send. - queue: SegQueue, -} - -impl Broadcaster { - /// Lazily broadcasts a packet to all players - /// able to see a given entity. - pub fn broadcast_entity_update

(&self, entity: Entity, packet: P, neq: Option) - where - P: Packet + 'static, - { - self.queue.push(BroadcastRequest { - condition: BroadcastCondition::Entity(entity), - packet: Box::new(packet), - neq, - }); - } - - /// Lazily broadcasts a packet to all players - /// able to see a given chunk. - pub fn broadcast_chunk_update

(&self, chunk: ChunkPosition, packet: P, neq: Option) - where - P: Packet + 'static, - { - self.queue.push(BroadcastRequest { - condition: BroadcastCondition::Chunk(chunk), - packet: Box::new(packet), - neq, - }); - } - - /// Lazily sends a packet to a player. - pub fn lazy_send_packet_to_player

(&self, player: Entity, packet: P) - where - P: Packet + 'static, - { - self.queue.push(BroadcastRequest { - condition: BroadcastCondition::ToPlayer(player), - packet: Box::new(packet), - neq: None, // No effect - }); - } -} - -/// A broadcast request. -struct BroadcastRequest { - /// Packet will only be sent to players able to see - /// this entity or chunk. - condition: BroadcastCondition, - /// The packet to broadcast. - packet: Box, - /// Optional entity not to send to. - neq: Option, -} - -#[derive(Debug, Clone, Copy)] -enum BroadcastCondition { - Entity(Entity), - Chunk(ChunkPosition), - ToPlayer(Entity), -} - -/// System for flushing the `Broadcaster` queue and broadcasting -/// the necessary packets. -pub struct BroadcasterSystem; - -impl<'a> System<'a> for BroadcasterSystem { - type SystemData = ( - ReadStorage<'a, PositionComponent>, - ReadStorage<'a, NetworkComponent>, - Read<'a, Util>, - Read<'a, ChunkHolders>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (positions, networks, util, chunk_holders, entities) = data; - - let broadcaster = &util.broadcaster; - - while let Ok(request) = broadcaster.queue.pop() { - // Broadcast packet. - // Iterate over entities in the chunk_holders - // entry for the chunk. If they are a player, - // send the packet. - // This works because any player able to see - // a chunk will always have a chunk holder on the chunk. - - let chunk = match request.condition { - BroadcastCondition::Entity(entity) => { - // Prevents a panic if the entity was destroyed. - if !entities.is_alive(entity) { - continue; - } - - positions.get(entity).unwrap().current.chunk_pos() - } - BroadcastCondition::Chunk(chunk) => chunk, - BroadcastCondition::ToPlayer(player) => { - if entities.is_alive(player) { - send_packet_boxed_to_player(networks.get(player).unwrap(), request.packet); - } - - continue; // Special case - no chunk - } - }; - if let Some(holders) = chunk_holders.holders_for(chunk) { - for holder in holders { - if let Some(neq) = request.neq.as_ref() { - if *holder == *neq { - continue; - } - } - - if let Some(network) = networks.get(*holder) { - send_packet_boxed_to_player(network, request.packet.box_clone()); - } - } - } - } - } -} diff --git a/server/src/util/macros.rs b/server/src/util/macros.rs deleted file mode 100644 index 66e6b841a..000000000 --- a/server/src/util/macros.rs +++ /dev/null @@ -1,70 +0,0 @@ -/// Asserts that a floating-point value is within -/// a certain range of the expected value. -#[cfg(test)] -macro_rules! assert_float_eq { - ($left:expr, $right:expr) => { - assert_float_eq!($left, $right, 0.001); - }; - ($left:expr, $right:expr, $range:expr) => { - let range = ($left - $range)..($left + $range); - assert!(range.contains(&$right)); - }; -} - -/// Checks that two positions are approximately equivalent. -#[cfg(test)] -macro_rules! assert_pos_eq { - ($left:expr, $right:expr) => { - assert_float_eq!($left.x, $right.x, 0.01); - assert_float_eq!($left.y, $right.y, 0.01); - assert_float_eq!($left.z, $right.z, 0.011); - }; -} - -/// Generates a setup() implementation for a system -/// which initializes an internal event reader. -macro_rules! setup_impl { - ($($reader:ident),+) => { - fn setup(&mut self, world: &mut specs::World) { - use specs::SystemData; - use shrev::EventChannel; - Self::SystemData::setup(world); - - $(self.$reader = Some(world.fetch_mut::>().register_reader());)+ - } - }; -} - -macro_rules! flagged_setup_impl { - ($component:ident, $reader:ident) => { - fn setup(&mut self, world: &mut specs::World) { - use specs::{SystemData, WorldExt}; - Self::SystemData::setup(world); - - self.$reader = Some(world.write_component::<$component>().register_reader()); - } - } -} - -macro_rules! read_flagged_events { - ($storage:ident, $reader:expr, $dirty:expr) => { - for event in $storage.channel().read($reader.as_mut().unwrap()) { - match event { - ComponentEvent::Inserted(id) | ComponentEvent::Modified(id) => { - $dirty.add(*id); - } - _ => (), - } - } - }; -} - -macro_rules! continue_if_none { - ($option:expr) => { - if let Some(value) = $option { - value - } else { - continue; - } - }; -} diff --git a/server/src/util/mod.rs b/server/src/util/mod.rs deleted file mode 100644 index e3798445a..000000000 --- a/server/src/util/mod.rs +++ /dev/null @@ -1,169 +0,0 @@ -//! Assorted utilities for use in Feather's codebase. -use bumpalo::Bump; -use feather_core::{ChunkPosition, Packet}; -use glm::DVec3; -use thread_local::ThreadLocal; - -#[macro_use] -mod macros; -mod broadcaster; - -use broadcaster::Broadcaster; -pub use broadcaster::BroadcasterSystem; -pub use macros::*; -use specs::Entity; - -/// Converts float-based velocity in blocks per tick -/// to the format used by the protocol. -pub fn protocol_velocity(vel: DVec3) -> (i16, i16, i16) { - // Apparently, these are in units of 1/8000 block per tick. - ( - (vel.x * 8000.0) as i16, - (vel.y * 8000.0) as i16, - (vel.z * 8000.0) as i16, - ) -} - -/// General-purpose utility resource, used to -/// ergonomically perform various actions without -/// specifying ridiculous amounts of system dependencies. -/// -/// This struct is thread-safe and should never be used -/// as a `Write` dependency. -/// -/// # Available functions -/// * `alloc` - used to allocate temporary values -/// using a bump allocator. When allocating values -/// which will only be used inside a function, for example, -/// this function should be used rather than allocating -/// directly on the heap. -/// * `broadcast` - lazily broadcasts a packet to all players -/// who are able to see a given chunk. This can be used -/// to broadcast movement updates, for example. -#[derive(Default)] -pub struct Util { - /// Thread-local bump allocator, reset - /// every tick. - /// - /// This is used to reduce allocation frequency. - bump: ThreadLocal, - /// The broadcaster, used to lazily broadcast packets. - broadcaster: Broadcaster, -} - -impl Util { - /// Returns the thread-local bump allocator. - pub fn bump(&self) -> &Bump { - self.bump.get_or_default() - } - - /// Allocates a value using the bump allocator - /// for this thread. - /// - /// This is equivalent to `Util::bump().alloc(value)`. - #[allow(clippy::mut_from_ref)] // This is sound—it just redirects to `bumpalo`. - pub fn alloc(&self, value: T) -> &mut T { - self.bump().alloc(value) - } - - /// This should be called at the end of every tick. - pub fn reset(&mut self) { - // Reset bump allocators - for bump in self.bump.iter_mut() { - bump.reset(); - } - } - - /// Broadcasts a packet to all players who - /// are able to see a given entity. - /// - /// The packet is sent lazily in a separate system. - /// - /// If `neq` is set to an entity, the packet - /// will not be sent to that player. - /// - /// This function runs in linear time with - /// regard to the number of players able to see - /// the entity. - pub fn broadcast_entity_update

(&self, entity: Entity, packet: P, neq: Option) - where - P: Packet + 'static, - { - self.broadcaster - .broadcast_entity_update(entity, packet, neq); - } - - /// Broadcasts a packet to all players who - /// are able to see a given chunk. - /// - /// The packet is sent lazily in a separate system. - /// - /// If `neq` is set to an entity, the packet - /// will not be sent to that player. - /// - /// This function runs in linear time with - /// regard to the number of players able to see - /// the chunk. - pub fn broadcast_chunk_update

(&self, chunk: ChunkPosition, packet: P, neq: Option) - where - P: Packet + 'static, - { - self.broadcaster.broadcast_chunk_update(chunk, packet, neq); - } - - /// Similar to `broadcast_*_update`, but only sends to the specified - /// player. - /// - /// This can be used when a packet needs to be sent to a specified - /// player before another packet is broadcasted. - pub fn lazy_send_packet_to_player

(&self, player: Entity, packet: P) - where - P: Packet + 'static, - { - self.broadcaster.lazy_send_packet_to_player(player, packet); - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::chunk_logic::ChunkHolders; - use crate::testframework as t; - use crate::util::broadcaster::BroadcasterSystem; - use feather_core::network::packet::implementation::EntityHeadLook; - use feather_core::PacketType; - use specs::WorldExt; - - #[test] - fn test_broadcast() { - let mut chunk_holders = ChunkHolders::default(); - - let chunk = ChunkPosition::new(0, 0); - let other_chunk = ChunkPosition::new(10, 1); - - let (mut world, mut dispatcher) = t::builder().with(BroadcasterSystem, "").build(); - let player1 = t::add_player(&mut world); - let player2 = t::add_player(&mut world); - let player3 = t::add_player(&mut world); - - chunk_holders.insert_holder(chunk, player1.entity); - chunk_holders.insert_holder(chunk, player2.entity); - chunk_holders.insert_holder(other_chunk, player3.entity); - - let packet = EntityHeadLook::default(); - - let util = Util::default(); - - util.broadcast_entity_update(player1.entity, packet, Some(player2.entity)); - - world.insert(util); - world.insert(chunk_holders); - - dispatcher.dispatch(&world); - world.maintain(); - - t::assert_packet_received(&player1, PacketType::EntityHeadLook); - t::assert_packet_not_received(&player2, PacketType::EntityHeadLook); - t::assert_packet_not_received(&player3, PacketType::EntityHeadLook); - } -} From c1b9b78f6f1fe15f1c08cf57cc8f0a3ee45e622b Mon Sep 17 00:00:00 2001 From: caelunshun Date: Thu, 14 Nov 2019 13:07:48 -0700 Subject: [PATCH 03/37] Reimplement most of the network system --- Cargo.lock | 18 +- core/src/network/mod.rs | 4 +- server/Cargo.toml | 2 +- server/src/joinhandler.rs | 237 ------------------- server/src/lib.rs | 5 +- server/src/network.rs | 483 +++++++++----------------------------- 6 files changed, 131 insertions(+), 618 deletions(-) delete mode 100644 server/src/joinhandler.rs diff --git a/Cargo.lock b/Cargo.lock index 05ac9fe2e..faded77b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -695,7 +695,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2509,7 +2509,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks#5151057974bfd22317ef64b0e1eb455a0aa80442" +source = "git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7#0691131efcffea7c0225f9d6203b58eb2cc27cd7" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2525,6 +2525,17 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)", +] + +[[package]] +name = "tonks-macros" +version = "0.1.0" +source = "git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7#0691131efcffea7c0225f9d6203b58eb2cc27cd7" +dependencies = [ + "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -3135,7 +3146,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/core/src/network/mod.rs b/core/src/network/mod.rs index 732b62454..dfc244b64 100644 --- a/core/src/network/mod.rs +++ b/core/src/network/mod.rs @@ -2,6 +2,6 @@ pub mod codec; pub mod mctypes; pub mod packet; -pub fn cast_packet(packet: &dyn packet::Packet) -> &P { - packet.as_any().downcast_ref().unwrap() +pub fn cast_packet(packet: Box) -> P { + packet.downcast().unwrap() } diff --git a/server/Cargo.toml b/server/Cargo.toml index 1f2d0ee63..feaa9a857 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,7 +17,7 @@ feather-blocks = { path = "../blocks" } feather-core = { path = "../core" } feather-item-block = { path = "../item_block" } legion = { git = "https://github.com/TomGillen/legion", rev = "2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b" } -tonks = { git = "https://github.com/feather-rs/tonks" } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "0691131efcffea7c0225f9d6203b58eb2cc27cd7" } crossbeam = "0.7" log = "0.4" simple_logger = "1.3" diff --git a/server/src/joinhandler.rs b/server/src/joinhandler.rs deleted file mode 100644 index ad38803cb..000000000 --- a/server/src/joinhandler.rs +++ /dev/null @@ -1,237 +0,0 @@ -//! The join handler, in contrast to the initial handler, -//! takes over after the login sequence has completed. -//! It's responsible for asyncrhonously loading the player's -//! data (inventory, chunks, etc.) and then sending the necessary -//! packets to join the player. After completion, the component is -//! removed. - -use std::sync::atomic::Ordering; -use std::sync::Arc; - -use shrev::EventChannel; -use specs::{ - Component, Entities, Entity, HashMapStorage, Join, LazyUpdate, Read, ReadExpect, ReadStorage, - System, Write, WriteStorage, -}; - -use feather_core::level::LevelData; -use feather_core::network::packet::implementation::{ - JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, -}; -use feather_core::world::{BlockPosition, ChunkMap, ChunkPosition}; -use feather_core::{Difficulty, Dimension}; - -use crate::chunk_logic::{ChunkHolderComponent, ChunkHolders, ChunkWorkerHandle}; -use crate::config::Config; -use crate::entity::{EntitySpawnEvent, PlayerComponent, PositionComponent}; -use crate::network::NetworkComponent; -use crate::player::{ChunkPendingComponent, InventoryUpdateEvent, LoadedChunksComponent}; -use crate::PlayerCount; - -#[derive(Default)] -pub struct JoinHandlerComponent { - stage: Stage, -} - -impl JoinHandlerComponent { - pub fn new() -> Self { - Self { - stage: Stage::Initial, - } - } -} - -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -enum Stage { - Initial, - AwaitChunkSends, -} - -impl Default for Stage { - fn default() -> Self { - Stage::Initial - } -} - -impl Component for JoinHandlerComponent { - type Storage = HashMapStorage; -} - -/// Event which is triggered when a player -/// completes the join process (i.e. when -/// all chunks have been sent). -pub struct PlayerJoinEvent { - pub player: Entity, -} - -/// System for join handling. -pub struct JoinHandlerSystem; - -impl<'a> System<'a> for JoinHandlerSystem { - type SystemData = ( - WriteStorage<'a, JoinHandlerComponent>, - ReadStorage<'a, NetworkComponent>, - ReadStorage<'a, ChunkPendingComponent>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - ReadExpect<'a, ChunkWorkerHandle>, - Entities<'a>, - Read<'a, LazyUpdate>, - Read<'a, Arc>, - Read<'a, Arc>, - Read<'a, ChunkMap>, - Write<'a, ChunkHolders>, - Read<'a, LevelData>, - WriteStorage<'a, ChunkHolderComponent>, - WriteStorage<'a, LoadedChunksComponent>, - ReadStorage<'a, PlayerComponent>, - ReadStorage<'a, PositionComponent>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut joincomps, - netcomps, - pending_chunks, - mut join_events, - mut spawn_events, - mut inv_events, - worker_handle, - entities, - lazy, - config, - player_count, - chunk_map, - mut holders, - level, - mut holder_comps, - mut loaded_chunks_comps, - playercomps, - positions, - ) = data; - - let mut to_remove = vec![]; - - for (player, net, join_handler, pending_chunks) in - (&entities, &netcomps, &mut joincomps, &pending_chunks).join() - { - match join_handler.stage { - Stage::Initial => { - let playercomp = playercomps.get(player).unwrap(); - - let level_type = &level.generator_name; - - // Send Join Game, then queue chunks for loading + sending. - let join_game = JoinGame::new( - player.id() as i32, - playercomp.gamemode.get_id(), - Dimension::Overwold.get_id(), - Difficulty::Medium.get_id(), - 0, // Max players - not used - level_type.to_string(), - false, // Reduced debug info - ); - crate::network::send_packet_to_player(net, join_game); - - let mut holder_comp = ChunkHolderComponent::new(); - let mut loaded_chunks_comp = LoadedChunksComponent::default(); - - let player_pos = positions.get(player).unwrap().current.chunk_pos(); - - // Offsets from the origin to center view distance on - let chunk_offset_x = player_pos.x; - let chunk_offset_z = player_pos.z; - - // Queue chunks for sending. - let view_distance = i32::from(config.server.view_distance); - let mut chunks = Vec::with_capacity((view_distance * view_distance) as usize); - for x in -view_distance..=view_distance { - for z in -view_distance..=view_distance { - let chunk = ChunkPosition::new(x + chunk_offset_x, z + chunk_offset_z); - chunks.push(chunk); - } - } - - // Sort chunks so that closest chunks are sent first. - chunks.sort_unstable_by(|a, b| { - a.manhattan_distance(player_pos) - .cmp(&b.manhattan_distance(player_pos)) - }); - - // Queue chunks for loading + sending - chunks.into_iter().for_each(|chunk| { - crate::player::send_chunk_to_player( - chunk, - net, - player, - &chunk_map, - &worker_handle, - &mut holders, - &mut holder_comp, - &mut loaded_chunks_comp, - &lazy, - ); - }); - - holder_comps.insert(player, holder_comp).unwrap(); - loaded_chunks_comps - .insert(player, loaded_chunks_comp) - .unwrap(); - - // Increment player count - player_count.0.fetch_add(1, Ordering::SeqCst); - - join_handler.stage = Stage::AwaitChunkSends; - } - Stage::AwaitChunkSends => { - // If 0 chunks have yet to be sent, join the player by sending spawn position. - // See https://wiki.vg/Protocol_FAQ - if pending_chunks.len() != 0 { - continue; - } - - // SpawnPosition packet: world spawn (used for compass) - let level_spawn_block_pos = - BlockPosition::new(level.spawn_x, level.spawn_y, level.spawn_z); - let level_spawn_position = SpawnPosition::new(level_spawn_block_pos); - crate::network::send_packet_to_player(net, level_spawn_position); - - // Initial position/rotation for the player when they spawn - let player_pos = positions.get(player).unwrap().current; - let position_and_look = PlayerPositionAndLookClientbound::new( - player_pos.x, - player_pos.y, - player_pos.z, - player_pos.yaw, - player_pos.pitch, - 0, // Flags - unused by us - 0, // Teleport ID - unused by us - ); - crate::network::send_packet_to_player(net, position_and_look); - - // Trigger events - let event = PlayerJoinEvent { player }; - join_events.single_write(event); - - let event = EntitySpawnEvent { entity: player }; - spawn_events.single_write(event); - - // Trigger inventory update event on the entire inventory - let event = InventoryUpdateEvent { - slots: (0..46).collect(), - player, - }; - inv_events.single_write(event); - - // We're finished here. - to_remove.push(player); - } - } - } - - to_remove.into_iter().for_each(|player| { - joincomps.remove(player); - }); - } -} diff --git a/server/src/lib.rs b/server/src/lib.rs index acd131abe..5bbc8850c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -25,6 +25,8 @@ extern crate feather_codegen; extern crate bitflags; #[macro_use] extern crate feather_core; +#[macro_use] +extern crate tonks; extern crate nalgebra_glm as glm; @@ -39,6 +41,7 @@ use specs::{Builder, Dispatcher, DispatcherBuilder, Entity, LazyUpdate, World, W use feather_core::network::packet::implementation::DisconnectPlay; use crate::chunk_logic::{ChunkHolders, ChunkWorkerHandle}; +use crate::config::Config; use crate::worldgen::{ ComposableGenerator, EmptyWorldGenerator, SuperflatWorldGenerator, WorldGenerator, }; @@ -51,7 +54,6 @@ use std::hash::{Hash, Hasher}; use std::io::{Read, Write}; use std::path::Path; use std::process::exit; -use crate::config::Config; #[global_allocator] static ALLOC: System = System; @@ -60,7 +62,6 @@ pub mod chunk_logic; pub mod chunkworker; pub mod config; pub mod io; -pub mod joinhandler; pub mod network; pub mod physics; pub mod shutdown; diff --git a/server/src/network.rs b/server/src/network.rs index fdf2b13fa..513a7b6cc 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -1,417 +1,154 @@ -use parking_lot::Mutex; +//! Network logic. This module includes: +//! * `Network`, a component assigned to entities +//! which can send and receive packets. (This is only +//! added for players, obviously.) +//! * `PacketQueue`, which stores packets received +//! from players and allows systems to poll for packets +//! received of a given type. -use crossbeam::Receiver; -use futures::channel::mpsc::UnboundedSender as Sender; -use shrev::EventChannel; -use specs::{ - Component, DenseVecStorage, Entities, Entity, Join, LazyUpdate, Read, ReadStorage, System, - WorldExt, Write, WriteStorage, -}; - -use feather_core::network::packet::{implementation::*, Packet, PacketType}; - -use crate::entity::PlayerComponent; use crate::io::{ListenerToServerMessage, NetworkIoManager, ServerToWorkerMessage}; -use crate::joinhandler::JoinHandlerComponent; -use crate::prelude::*; -use crate::{disconnect_player_without_packet, TickCount}; -use strum::EnumCount; - -//const MAX_KEEP_ALIVE_TIME: u64 = 30; -//const HEAD_OFFSET: f64 = 1.62; // Offset from feet pos to head pos - -/// A packet received from a player. -pub type QueuedPacket = (Entity, Box); - -/// Vector of `QueuedPacket`. -type QueuedPackets = Vec; - -/// A component which contains the received packets -/// for this tick. +use crossbeam::Receiver; +use feather_core::network::cast_packet; +use feather_core::{Packet, PacketType}; +use futures::channel::mpsc::UnboundedSender; +use legion::entity::Entity; +use legion::query::Read; +use parking_lot::{Mutex, MutexGuard}; +use std::iter; +use std::vec::Drain; +use tonks::{PreparedQuery, PreparedWorld, Query}; + +type QueuedPackets = Vec<(Entity, Box)>; + +/// The packet queue. This type allows systems to poll for +/// received packets of a given type. +/// +/// A system should never require mutable access to this type. pub struct PacketQueue { - /// Vector of packet queues. For any given packet - /// type, the queued packets of that type can - /// be found by indexing into this vector with the ordinal - /// of the packet type. - /// - /// A locked `Vec` is used rather than a `SegQueue` because - /// there is typically no contention when accessing the queue - /// for a single packet type (there is at most one system handling - /// each packet type). As a result, there is no need for a lock-free - /// data structure. + /// Vector of queued packets. This vector is indexed + /// by the ordinal of the packet type, and each + /// queue contains only packets of its type. queue: Vec>, } -impl PacketQueue { - /// Returns the packets queued for handling - /// of the given type, draining the queue of this - /// type of packet. - pub fn for_packet(&self, ty: PacketType) -> Vec { - let ordinal = ty.ordinal(); +impl Default for PacketQueue { + fn default() -> Self { + Self::new() + } +} - let mut queued_packets = self.queue[ordinal].lock(); +impl PacketQueue { + /// Creates a new, empty `PacketQueue`. + pub fn new() -> Self { + Self { + queue: iter::repeat_with(|| Mutex::new(vec![])) + .take(PacketType::count() + 1) + .collect(), + } + } - let mut new_queue = vec![]; - std::mem::swap(&mut new_queue, &mut queued_packets); + /// Returns an iterator over packets of a given type. + pub fn received(&self) -> MutexGuard> { + let queue = self.queue[P::ty().ordinal()].lock(); - new_queue + MutexGuard::map(queue, |queue| { + queue + .drain(..) + .map(|(entity, packet)| (entity, cast_packet::

(packet))) + }) } /// Adds a packet to the queue. - pub fn add_for_packet(&self, player: Entity, packet: Box) { + pub fn push(&self, packet: Box, entity: Entity) { let ordinal = packet.ty().ordinal(); - let mut queued_packets = self.queue[ordinal].lock(); - queued_packets.push((player, packet)); - } -} - -impl Default for PacketQueue { - fn default() -> Self { - // Initialize with an empty queue for each packet type. - // Packet type ordinals start at 1 (who decided this? FIXME), - // so we have to use an inclusive range. - Self { - queue: (0..=PacketType::count()) - .map(|_| Mutex::new(vec![])) - .collect(), - } + self.queue[ordinal].lock().push((packet, entity)); } } -pub struct NetworkComponent { - sender: Sender, +/// Network component containing channels to send and receive packets. +/// +/// Systems should call `Self::send` to send a packet to this entity (player). +pub struct Network { + sender: UnboundedSender, receiver: Receiver, - /// A vector of all chunks that are currently - /// being loaded and should be sent to the player - /// once they have been loaded. - pub chunks_to_send: Vec, - //last_keep_alive_time: u64, } -impl NetworkComponent { - pub fn new( - sender: Sender, - receiver: Receiver, - ) -> Self { - Self { - sender, - receiver, - chunks_to_send: vec![], - } +impl Network { + /// Sends a packet to this player. + pub fn send

(&self, packet: P) { + self.send_boxed(Box::new(packet)); } -} -impl Component for NetworkComponent { - type Storage = DenseVecStorage; -} - -/// The network system, responsible for -/// receiving and buffering packets received -/// from players. Received packets -/// are added to a queue (`PacketQueue`) so that -/// other systems can handle them. -pub struct NetworkSystem; - -/// Event which is triggered when a player joins -/// but before the join handler is completed. -pub struct PlayerPreJoinEvent { - pub player: Entity, - pub username: String, - pub uuid: Uuid, - pub profile_properties: Vec, + /// Sends a boxed packet to this player. + pub fn send_boxed(&self, packet: Box) { + // Discard error in case the channel was disconnected + // (e.g. if the player disconnected and its worker task + // shut down, and the disconnect was not yet registered + // by the server) + let _ = self + .sender + .unbounded_send(ServerToWorkerMessage::SendPacket(packet)); + } } -impl<'a> System<'a> for NetworkSystem { - type SystemData = ( - WriteStorage<'a, NetworkComponent>, - ReadStorage<'a, PlayerComponent>, - Write<'a, EventChannel>, - Write<'a, PacketQueue>, - Read<'a, NetworkIoManager>, - Entities<'a>, - Read<'a, TickCount>, - Read<'a, LazyUpdate>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut netcomps, - pcomps, - mut join_events, - packet_queue, - ioman, - entities, - tick_count, - lazy, - ) = data; - // Poll for new connections - while let Ok(msg) = ioman.receiver.try_recv() { +/// The network system. This system is responsible for: +/// * Handling player disconnects. +/// * Pushing received packets to the packet queue. +/// * Accepting new clients and creating entities for them. +#[system] +pub fn network( + io: &NetworkIoManager, + packet_queue: &PacketQueue, + mut query: PreparedQuery>, + world: &mut PreparedWorld, +) { + /// For each `Network`, handle any disconnects and received packets. + query.par_entities_for_each(world, |(entity, network): (Entity, Network)| { + while let Ok(msg) = network.receiver.try_recv() { match msg { - ListenerToServerMessage::NewClient(info) => { - // New connection - handle it - info!("Accepting connection from {}", info.ip); - let netcomp = NetworkComponent::new(info.sender, info.receiver); - - // Create entity - let new_entity = entities.create(); - netcomps.insert(new_entity, netcomp).unwrap(); - - // Create join handler - let join_handler = JoinHandlerComponent::new(); - lazy.exec_mut(move |world| { - world - .write_component::() - .insert(new_entity, join_handler) - .unwrap(); - }); - - // Queue event - let event = PlayerPreJoinEvent { - player: new_entity, - username: info.username.clone(), - uuid: info.uuid, - profile_properties: info.profile.clone(), - }; - join_events.single_write(event); - } - } - } - - // Receive packets + disconnects from players - for (player, netcomp) in (&entities, &netcomps).join() { - while let Ok(msg) = netcomp.receiver.try_recv() { - match msg { - ServerToWorkerMessage::NotifyPacketReceived(packet) => { - packet_queue.add_for_packet(player, packet); - } - ServerToWorkerMessage::NotifyDisconnect(reason) => { - lazy.exec_mut(move |world| { - disconnect_player_without_packet(player, world, reason) - }); - break; - } - _ => panic!("Network system received invalid message from IO worker}"), + ServerToWorkerMessage::NotifyDisconnect(reason) => unimplemented!(), + ServerToWorkerMessage::SendPacket(packet) => { + packet_queue.push(packet, entity); } + _ => unreachable!(), } } + }); - // Send keepalives every second. The dependency on the player - // component is required because keepalives should - // only be sent to players who have joined (completed - // the login process). - // TODO check that player hasn't timed out - if tick_count.0 % TPS == 0 { - for (netcomp, _) in (&netcomps, &pcomps).join() { - send_packet_to_player(netcomp, KeepAliveClientbound::new(0)); - } + // Handle new clients. + while let Ok(msg) = io.receiver.try_recv() { + match msg { + ListenerToServerMessage::NewClient(info) => unimplemented!(), } } } -/// Sends a packet to all players on the server, excluding -/// `neq`, if it exists. -pub fn send_packet_to_all_players( - net_comps: &ReadStorage, - entities: &Entities, - packet: P, - neq: Option, -) { - for (entity, net) in (entities, net_comps).join() { - if let Some(e) = neq.as_ref() { - if *e == entity { - continue; // Exclude this entity - } - } - - send_packet_to_player(net, packet.clone()); - } -} - -/// Sends a packet to the given player. -pub fn send_packet_to_player(comp: &NetworkComponent, packet: P) { - send_packet_boxed_to_player(comp, Box::new(packet)); -} - -/// Sends a packet to the given player. -pub fn send_packet_boxed_to_player(comp: &NetworkComponent, packet: Box) { - let _ = comp - .sender - .unbounded_send(ServerToWorkerMessage::SendPacket(packet)); -} - #[cfg(test)] mod tests { use super::*; - use crate::io::NewClientInfo; - use crate::player::PlayerDisconnectEvent; - use crate::testframework as t; - use std::net::SocketAddr; - - #[test] - fn test_packet_queue() { - let queue = PacketQueue::default(); - - let (mut w, _) = t::init_world(); - let player = t::add_player(&mut w); - - let packet = LoginStart::new("test".to_string()); - queue.add_for_packet(player.entity, Box::new(packet)); - - let packets = queue.for_packet(PacketType::LoginStart); - assert_eq!(packets.len(), 1); - - let (entity, packet) = packets.first().unwrap(); - assert_eq!(*entity, player.entity); - assert_eq!(packet.ty(), PacketType::LoginStart); - } - - #[test] - fn test_new_client() { - let (mut w, mut d) = t::init_world(); - - let ioman = w.fetch_mut::(); - - let (send1, _recv1) = futures::channel::mpsc::unbounded(); - let (_send2, recv2) = crossbeam::unbounded(); - - let new_client = NewClientInfo { - ip: SocketAddr::new("127.0.0.1".parse().unwrap(), 25565), - username: "".to_string(), - profile: vec![], - uuid: Uuid::new_v4(), - sender: send1, - receiver: recv2, - }; - - let msg = ListenerToServerMessage::NewClient(new_client); - ioman.listener_sender.send(msg).unwrap(); - - let mut event_reader = t::reader(&w); - - drop(ioman); - - // Call the network system - d.dispatch(&w); - - w.maintain(); - - // Confirm that an entity was created - let mut count = 0; - let mut entity = None; - for (e, _, _) in ( - &*w.entities(), - &w.read_component::(), - &w.read_component::(), - ) - .join() - { - entity = Some(e); - count += 1; - } - assert_eq!(count, 1); - - // Confirm that playerprejoinevent was queued - let channel = w.fetch_mut::>(); - let join_events: Vec<&PlayerPreJoinEvent> = channel.read(&mut event_reader).collect(); - assert_eq!(join_events.len(), 1); - let event = join_events.first().unwrap(); - - assert_eq!(event.player, entity.unwrap()); - } - - #[test] - fn test_packet_receive() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - // Send a packet - let packet = LoginStart::new("".to_string()); - t::send_packet(&player, packet); - - // Run system - d.dispatch(&w); - - w.maintain(); - - // Confirm that packet was received properly - let queue = w.fetch::(); - let packets = queue.for_packet(PacketType::LoginStart); - - assert_eq!(packets.len(), 1); - - let (entity, _packet) = packets.first().unwrap(); - assert_eq!(*entity, player.entity); - } - - #[test] - fn test_disconnect() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - - let mut event_reader = t::reader(&w); - - player - .network_sender - .send(ServerToWorkerMessage::NotifyDisconnect( - "reason".to_string(), - )) - .unwrap(); - - d.dispatch(&w); - - w.maintain(); - - let channel = w.fetch::>(); - let events = channel.read(&mut event_reader).collect::>(); - - assert_eq!(events.len(), 1); - let first = events.first().unwrap(); - assert_eq!(first.player, player.entity); - } + use feather_core::network::packet::implementation::Handshake; + use feather_core::network::packet::PacketType::SpawnObject; + use legion::world::World; #[test] - fn test_keep_alives() { - let (mut w, mut d) = t::init_world(); - - let player = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - - d.dispatch(&w); - - t::assert_packet_received(&player, PacketType::KeepAliveClientbound); - t::assert_packet_received(&player2, PacketType::KeepAliveClientbound); - } - - #[test] - fn test_send_packet_to_all_players() { - let (mut w, _) = t::init_world(); - - let player1 = t::add_player(&mut w); - let player2 = t::add_player(&mut w); - let player3 = t::add_player(&mut w); - - let packet = LoginStart::new("test".to_string()); + fn packet_queue() { + let queue = PacketQueue::new(); - send_packet_to_all_players( - &w.read_component(), - &w.entities(), - packet, - Some(player1.entity), - ); + let mut world = World::new(); + let entities = world.insert((), vec![(), ()]); - dbg!(); + queue.push(Box::new(Handshake::default()), entities[0]); + queue.push(Box::new(SpawnObject::default()), entities[1]); + queue.push(Handshake::default(), entities[1]); - t::assert_packet_received(&player2, PacketType::LoginStart); - dbg!(); - t::assert_packet_received(&player3, PacketType::LoginStart); - dbg!(); + let mut handshakes = queue.received::(); + assert_eq!(handshakes.next().unwrap().0, entities[0]); + assert_eq!(handshakes.next().unwrap().0, entities[1]); + assert!(handshakes.next().is_none()); - // Check that exclusion was not sent - let sent = t::received_packets(&player1, None); - dbg!(); - assert!(sent.is_empty()); + let mut spawn_objects = queue.received::(); + assert_eq!(spawn_objects.next().unwrap().0, entities[1]); + assert!(spawn_objects.next().is_none()); } } From 9b21375967f5b607b1c44d2a4aa89452e345894e Mon Sep 17 00:00:00 2001 From: caelunshun Date: Thu, 14 Nov 2019 13:37:32 -0700 Subject: [PATCH 04/37] Add crate-level docs on ECS --- server/src/lib.rs | 96 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/server/src/lib.rs b/server/src/lib.rs index 5bbc8850c..8a419d4cf 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -1,7 +1,95 @@ -// Specs systems tend to have very long -// tuples as their SystemData, and Clippy -// doesn't seem to like this. -#![allow(clippy::type_complexity)] +//! Feather, a Minecraft server implementation in Rust. +//! +//! This is the developer documenation, and anyone wishing to contribute +//! should read this first. +//! +//! The core of Feather is based on [`legion`](https://github.com/TomGillen/legion), +//! a fast ECS for Rust, and [`tonks`](https://github.com/feather-rs/tonks), a system +//! scheduler built on Legion. As a result, we use the ECS architecture: the +//! entire server consists of _entities_, simple IDs with no data; _components_, +//! arbitrary data, such as positions, which can be attached to an entity; +//! and _systems_, functions which can run logic over entities and components. +//! +//! The benefit of this design is the splitting between data and logic. With a traditional +//! object-oriented design, there would be an Entity class from which other entities +//! inherit and can override logic. However, this model does not work well with Rust's +//! borrow checker (as we found out in the early days of Feather, when this design was +//! used), and more importantly, it reduces flexibility. Say, for example, that a plugin +//! wants to modify the physics behavior of a cow by increasing gravity. With the object-oriented +//! design, it would have to somehow modify the `run_physics` method on `Cow`, which is not +//! possible in a native language (although it can be done in some languages using class rewriting). +//! On the other hand, using Feather, there is a `Physics` component which stores gravity, +//! drag, etc. for an entity, and all the plugin has to do is modify that component. +//! +//! Another benefit of the ECS architecture is performance. With the OO design, entities +//! would likely be stored in a `Vec>`, which is horribly inefficient +//! with regards to cache locality and iteration performance. Legion, however, stores +//! entities in an efficient manner such that many of the same type of component +//! are stored contiguously, which is excellent for cache performance. +//! +//! Here is a more in-depth description of each concept in Feather. +//! +//! ## Entities +//! Entities, or `legion::entity::Entity`, are simple numerical IDs: they store no +//! data, but components can be attached to an entity. See the systems section +//! for information on how to access this data. +//! +//! ## Components +//! Components store data associated with an entity, such as `Position`. +//! Arbitrary amounts of components can be associated with any given entity. +//! +//! ## Resources +//! Resources are a branching off from the pure ECS concept. Like components, they +//! store arbitrary data in the form of structs; however, they are not associated +//! with any entity. An example of a resource might be the chunk map, which allows +//! access to blocks in the world. +//! +//! ## Systems +//! The systems concept is where things become more complex. `tonks`, the library +//! which runs systems, runs them _in parallel_, effectively multithreading the +//! entire server. This is still safe because the scheduler ensures that no +//! two systems which write to the same data run at the same time. +//! +//! A consequence of the above is that systems must explicitly state which +//! resources and components they might access. If a system needs access to positions, +//! it needs to state this upfront so the scheduler can ensure memory safety. +//! +//! Systems can be written by creating a function annotated with the `system` attribute. +//! `tonks` will automatically detect which resources are accessed based on the function +//! parameters, and it will register them to the system scheduler without you +//! having to do anything. (How does this work? Don't even bother.) +//! +//! ## Events +//! Events are another concept unique to Feather, at least in the way they are implemented +//! here. The name states what they are—BlockChangeEvent, for example, is triggered when +//! a block is updated. +//! +//! A system can trigger an event by specifying an `&mut Trigger` resource. +//! +//! ## Event handlers +//! Event handlers are similar to systems, but they only run when the event they +//! handle is triggered. Use the `event_handler` attribute on a function to register +//! it as an event handler. +//! +//! # Networking model +//! Feather consists of two key parts: the server threads, which run systems +//! over entities, and the networking tasks, which run on [`tokio`](https://github.com/tokio-rs/tokio). +//! The networking tasks will accept connections and parse any packets received +//! from the player. +//! +//! When a connection is first made to the server's TCP listener, the networking +//! task will spawn another task to handle the connection. It's important to note that +//! at this time, _the server thread is totally unaware of the connection_—networking +//! runs entirely isolated from the rest of the program. +//! +//! At this point, the initial handler takes over, which runs on the networking task. +//! It will handle the login sequence or status pings, perform authentication, etc. +//! If successful, the server is notified of the new player through a channel. +//! +//! When the server is notified of a new player, it's essential to realize +//! that the player still hasn't been sent important data, such as +//! chunk packets, inventory, time, nearby entities, etc. `PlayerJoinEvent` +//! is used to send this data. #[macro_use] extern crate log; From 106ff04b4841934b4ba4c1754fb3523cc0837976 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 15 Nov 2019 13:54:58 -0700 Subject: [PATCH 05/37] New state type for combining lots of resources; use `RwLock` to store chunks --- Cargo.lock | 1 + core/Cargo.toml | 1 + core/src/world/mod.rs | 116 +++++++++++++-------------------------- server/src/entity/mod.rs | 27 +++++++++ server/src/lazy.rs | 74 +++++++++++++++++++++++++ server/src/lib.rs | 5 +- server/src/network.rs | 19 +++++-- server/src/player/mod.rs | 33 +++++++++++ server/src/state.rs | 62 +++++++++++++++++++++ 9 files changed, 256 insertions(+), 82 deletions(-) create mode 100644 server/src/entity/mod.rs create mode 100644 server/src/lazy.rs create mode 100644 server/src/player/mod.rs create mode 100644 server/src/state.rs diff --git a/Cargo.lock b/Cargo.lock index faded77b6..4d834ea7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -598,6 +598,7 @@ dependencies = [ "nalgebra-glm 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/Cargo.toml b/core/Cargo.toml index 1e0549076..3f67ffed3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -34,3 +34,4 @@ tokio = "=0.2.0-alpha.6" failure = "0.1" bitvec = "0.15" multimap = "0.6" +parking_lot = "0.9.0" diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 907b0c218..17fa87a62 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -2,9 +2,11 @@ use crate::world::block::*; use crate::world::chunk::Chunk; use glm::{DVec3, Vec3}; use hashbrown::HashMap; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use std::fmt; use std::fmt::{Display, Formatter}; use std::ops::{Add, Sub}; +use std::sync::Arc; pub mod block; #[allow(clippy::cast_lossless)] @@ -247,97 +249,57 @@ impl Add for BlockPosition { } } -pub struct ChunkMap { - chunk_map: HashMap, -} - -impl ChunkMap { - pub fn new() -> Self { - Self { - chunk_map: HashMap::new(), - } - } +pub type ChunkMapInner = HashMap>>; - pub fn inner(&self) -> &HashMap { - &self.chunk_map - } +/// The chunk map. +/// +/// This struct stores all the chunks on the server, +/// so it allows access to blocks and lighting data. +/// +/// Chunks are internally wrapped in `Arc`, +/// allowing multiple systems to access different parts +/// of the world in parallel. Mutable access to this +/// type is only required for inserting and removing +/// chunks. +pub struct ChunkMap(ChunkMapInner); - pub fn inner_mut(&mut self) -> &mut HashMap { - &mut self.chunk_map +impl ChunkMap { + /// Retrieves a handle to the chunk at the given + /// position, or `None` if it is not loaded. + pub fn chunk_at(&self, pos: ChunkPosition) -> Option> { + self.0.get(&pos).map(|lock| lock.read()) } - /// Retrieves the chunk at the specified location. - /// If the chunk is not loaded, `None` will be returned. - pub fn chunk_at(&self, pos: ChunkPosition) -> Option<&Chunk> { - if let Some(chunk) = self.chunk_map.get(&pos) { - return Some(chunk); - } - - None + /// Retrieves a handle to the chunk at the given + /// position, or `None` if it is not loaded. + pub fn chunk_at_mut(&self, pos: ChunkPosition) -> Option> { + self.0.get(&pos).map(|lock| lock.write()) } - /// Retrieves the block at the specified - /// location. If the chunk in which the block - /// exists is not laoded, `None` is returned. + /// Retrieves the block at the given position, + /// or `None` if its chunk is not loaded. pub fn block_at(&self, pos: BlockPosition) -> Option { - if pos.y > 255 || pos.y < 0 { - return None; - } - - let chunk_pos = pos.chunk_pos(); - - if let Some(chunk) = self.chunk_at(chunk_pos) { - let rpos = chunk_relative_pos(pos); - Some(chunk.block_at(rpos.0, rpos.1, rpos.2)) - } else { - None - } + let (x, y, z) = chunk_relative_pos(pos); + self.chunk_at(pos.chunk_pos()) + .map(|chunk| chunk.block_at(x, y, z)) } /// Sets the block at the given position. - /// If the chunk in which the position resides - /// does not exist, `Err` is returned. In all - /// other cases, `Ok` is returned. /// - /// Note that on the server side, calling this function - /// does not broadcast the update in any way. As such, - /// the according function should be called instead. - pub fn set_block_at(&mut self, pos: BlockPosition, block: Block) -> Result<(), ()> { - if pos.y > 255 || pos.y < 0 { - return Err(()); - } - - let chunk_pos = pos.chunk_pos(); - - if let Some(chunk) = self.chunk_map.get_mut(&chunk_pos) { - let (x, y, z) = chunk_relative_pos(pos); - chunk.set_block_at(x, y, z, block); - Ok(()) - } else { - Err(()) - } - } - - /// Sets the chunk at the given location. - pub fn set_chunk_at(&mut self, pos: ChunkPosition, chunk: Chunk) { - self.chunk_map.insert(pos, chunk); - } - - /// Removes the chunk at the given location, - /// effectively unloading it. - pub fn unload_chunk_at(&mut self, pos: ChunkPosition) -> Option { - self.chunk_map.remove(&pos) - } + /// Returns `true` if the block was set, or `false` + /// if its chunk was not loaded and thus no operation + /// was performed. + pub fn set_block_at(&self, pos: BlockPosition, block: Block) -> bool { + let (x, y, z) = chunk_relative_pos(pos); - /// Returns an immutable reference to the internal map. - pub fn chunks(&self) -> &HashMap { - &self.chunk_map + self.chunk_at_mut(pos.chunk_pos()) + .map(|mut chunk| chunk.set_block_at(x, y, z, block)) + .is_ok() } - /// Returns a mutable reference to the internal - /// map. - pub fn chunks_mut(&mut self) -> &mut HashMap { - &mut self.chunk_map + /// Returns an iterator over chunks. + pub fn iter_chunks(&self) -> impl IntoIterator>> { + self.0.iter() } } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs new file mode 100644 index 000000000..87d23831a --- /dev/null +++ b/server/src/entity/mod.rs @@ -0,0 +1,27 @@ +//! Dealing with entities. + +use crate::lazy::EntityBuilder; +use crate::state::State; +use feather_core::Position; + +/// The velocity of an entity. +#[derive(Default, Debug, PartialEq, Clone, Copy)] +pub struct Velocity(pub glm::DVec3); + +/// The display name of the entity. +/// +/// Note that unnamed entities do not have this component. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct NameComponent(pub String); + +/// Inserts the base components for an entity into an `EntityBuilder`. +/// +/// This currently includes: +/// * Position +/// * Velocity (0) +pub fn base(state: &State, position: Position) -> EntityBuilder { + state + .create_entity() + .with_component(position) + .with_component(Velocity::default()) +} diff --git a/server/src/lazy.rs b/server/src/lazy.rs new file mode 100644 index 000000000..245ce91e5 --- /dev/null +++ b/server/src/lazy.rs @@ -0,0 +1,74 @@ +use crossbeam::queue::SegQueue; +use legion::entity::Entity; +use legion::storage::{Component, Tag}; +use legion::world::World; +use smallvec::SmallVec; + +/// Resource which allows lazy creation of entities +/// or execution of functions with world access. +pub struct Lazy { + /// Internal queue of actions to perform. + queue: SegQueue, +} + +impl Lazy { + /// Lazily executes a closure with world access. + pub fn exec(&self, f: impl FnOnce(&mut World)) { + self.queue.push(Action::Exec(Box::new(f))); + } + + /// Creates an `EntityBuilder` which can be used to lazily + /// create an entity. + pub fn create_entity(&self) -> EntityBuilder { + EntityBuilder { + lazy: self, + fns: smallvec![], + } + } + + /// Performs all queued actions. + pub fn flush(&self, world: &mut World) { + while let Ok(action) = self.queue.pop() { + match action { + Action::Exec(f) => f(world), + } + } + } +} + +/// An action which the lazy updater may perform. +enum Action { + Exec(Box), +} + +/// Builder for lazily creating entities. +pub struct EntityBuilder<'a> { + lazy: &'a Lazy, + fns: SmallVec<[Box; 8]>, +} + +impl<'a> EntityBuilder<'a> { + pub fn with_component(mut self, component: C) -> Self { + self.fns.push(move |world, entity| { + world.add_component(entity, component); + }); + self + } + + pub fn with_tag(mut self, tag: T) -> Self { + self.fns.push(move |world, entity| { + world.add_tag(entity, tag); + }); + self + } + + pub fn build(self) { + self.lazy.exec(move |world| { + let entity = world.insert((), [()].iter().copied())[0]; + + for f in self.fns { + f(world, entity); + } + }) + } +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 8a419d4cf..6900869bb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -149,11 +149,14 @@ static ALLOC: System = System; pub mod chunk_logic; pub mod chunkworker; pub mod config; +pub mod entity; pub mod io; +pub mod lazy; pub mod network; pub mod physics; +pub mod player; pub mod shutdown; -#[cfg(test)] +pub mod state; pub mod time; pub mod worldgen; diff --git a/server/src/network.rs b/server/src/network.rs index 513a7b6cc..5efc4b494 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -7,6 +7,10 @@ //! received of a given type. use crate::io::{ListenerToServerMessage, NetworkIoManager, ServerToWorkerMessage}; +use crate::lazy::Lazy; +use crate::player; +use crate::player::Player; +use crate::state::State; use crossbeam::Receiver; use feather_core::network::cast_packet; use feather_core::{Packet, PacketType}; @@ -70,8 +74,8 @@ impl PacketQueue { /// /// Systems should call `Self::send` to send a packet to this entity (player). pub struct Network { - sender: UnboundedSender, - receiver: Receiver, + pub sender: UnboundedSender, + pub receiver: Receiver, } impl Network { @@ -98,6 +102,7 @@ impl Network { /// * Accepting new clients and creating entities for them. #[system] pub fn network( + state: &State, io: &NetworkIoManager, packet_queue: &PacketQueue, mut query: PreparedQuery>, @@ -107,7 +112,11 @@ pub fn network( query.par_entities_for_each(world, |(entity, network): (Entity, Network)| { while let Ok(msg) = network.receiver.try_recv() { match msg { - ServerToWorkerMessage::NotifyDisconnect(reason) => unimplemented!(), + ServerToWorkerMessage::NotifyDisconnect(reason) => { + state.exec(move |world| { + debug_assert!(world.delete(entity), "player already deleted"); + }); + } ServerToWorkerMessage::SendPacket(packet) => { packet_queue.push(packet, entity); } @@ -119,7 +128,9 @@ pub fn network( // Handle new clients. while let Ok(msg) = io.receiver.try_recv() { match msg { - ListenerToServerMessage::NewClient(info) => unimplemented!(), + ListenerToServerMessage::NewClient(info) => { + player::create(state, info); + } } } } diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs new file mode 100644 index 000000000..7d6e26b0b --- /dev/null +++ b/server/src/player/mod.rs @@ -0,0 +1,33 @@ +//! Systems and components specific to player entities. + +use crate::entity; +use crate::entity::NameComponent; +use crate::io::NewClientInfo; +use crate::network::Network; +use crate::state::State; +use mojang_api::ProfileProperty; + +/// Profile properties of a player. +#[derive(Debug, Clone)] +pub struct ProfileProperties(pub Vec); + +/// Tag used to mark a player. +/// +/// Note that this is a _tag_, not a component. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Player; + +/// Creates a new player from the given `NewClientInfo`. +pub fn create(state: &State, info: NewClientInfo) { + entity::base(state, info.position) + .with_tag(Player) + .with_component(info.uuid) + .with_component(Network { + sender: info.sender, + receiver: info.receiver, + }) + .with_component(info.ip) + .with_component(ProfileProperties(info.profile)) + .with_component(NameComponent(info.username)) + .build(); +} diff --git a/server/src/state.rs b/server/src/state.rs new file mode 100644 index 000000000..4d7a89b16 --- /dev/null +++ b/server/src/state.rs @@ -0,0 +1,62 @@ +use crate::config::Config; +use crate::lazy::{EntityBuilder, Lazy}; +use feather_blocks::Block; +use feather_core::world::ChunkMap; +use feather_core::{BlockPosition, Chunk, ChunkPosition}; +use legion::world::World; +use parking_lot::RwLockReadGuard; +use std::sync::Arc; + +/// The state of the server. +/// +/// This state wraps numerous commonly-used resources, +/// including the chunk map (block access), config, and +/// various cached data structures, among others. +/// +/// Systems should never require mutable access to the +/// state; it is designed for read-only use. (The chunk +/// map uses `RwLock` internally, so write access isn't +/// needed to update blocks.) +pub struct State { + pub config: Arc, + + chunk_map: ChunkMap, + lazy: Lazy, +} + +impl State { + /// See `Lazy::exec()`. + pub fn exec(&self, f: impl FnOnce(&mut World)) { + self.lazy.exec(f) + } + + /// See `Lazy::create_entity()`. + pub fn create_entity(&self) -> EntityBuilder { + self.lazy.create_entity() + } + + /// See `Lazy::flush()`. + pub fn flush(&self, world: &mut World) { + self.lazy.flush(world); + } + + /// Retrieves the block at the given position, + /// or `None` if the block's chunk is not loaded. + pub fn block_at(&self, pos: BlockPosition) -> Option { + self.chunk_map.block_at(pos) + } + + /// Sets the block at the given position. + /// + /// If the block's chunk's is not loaded, returns `false`; + /// otherwise, returns `true`. + pub fn set_block_at(&self, pos: BlockPosition, block: Block) -> bool { + self.chunk_map.set_block_at(pos, block).is_ok() + } + + /// Retrieves a reference to the chunk at the given position, + /// or `None` if it not loaded. + pub fn chunk_at(&self, pos: ChunkPosition) -> Option> { + self.chunk_map.chunk_at(pos) + } +} From 9ff1e9ed74bcda8440aeacb05039c381e212b056 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 15 Nov 2019 14:09:30 -0700 Subject: [PATCH 06/37] Translate entity physics system --- server/src/physics/component.rs | 13 +- server/src/physics/entity.rs | 304 +++++++++++++------------------- server/src/physics/math.rs | 15 +- server/src/physics/mod.rs | 2 +- 4 files changed, 130 insertions(+), 204 deletions(-) diff --git a/server/src/physics/component.rs b/server/src/physics/component.rs index f10a707ec..979991bde 100644 --- a/server/src/physics/component.rs +++ b/server/src/physics/component.rs @@ -3,7 +3,6 @@ use glm::DVec3; use ncollide3d::bounding_volume::AABB; -use specs::{Component, VecStorage}; pub const DEFAULT_SLIP_MULTIPLIER: f64 = 0.6; @@ -13,7 +12,7 @@ pub const DEFAULT_SLIP_MULTIPLIER: f64 = 0.6; /// /// Typically, this component should be constructed using `PhysicsBuilder`. #[derive(Debug)] -pub struct PhysicsComponent { +pub struct Physics { /// This entity's bounding box. pub bbox: AABB, /// The drag coefficient for this entity. Each tick, @@ -33,18 +32,14 @@ pub struct PhysicsComponent { pub slip_multiplier: f64, } -impl Component for PhysicsComponent { - type Storage = VecStorage; -} - /// Builder for physics components. pub struct PhysicsBuilder { - comp: PhysicsComponent, + comp: Physics, } impl Default for PhysicsBuilder { fn default() -> Self { - let comp = PhysicsComponent { + let comp = Physics { bbox: bbox(0.5, 0.5, 0.5), drag: 0.98, gravity: -0.08, @@ -85,7 +80,7 @@ impl PhysicsBuilder { self } - pub fn build(self) -> PhysicsComponent { + pub fn build(self) -> Physics { self.comp } } diff --git a/server/src/physics/entity.rs b/server/src/physics/entity.rs index 4e1b931bd..cb71d8f9e 100644 --- a/server/src/physics/entity.rs +++ b/server/src/physics/entity.rs @@ -1,17 +1,16 @@ //! Module for performing entity physics, including velocity, drag //! and position updates each tick. -use specs::{Entities, Entity, Join, Read, ReadStorage, System, Write, WriteStorage}; - -use crate::entity::{EntityDestroyEvent, PositionComponent, VelocityComponent}; -use crate::physics::{ - block_impacted_by_ray, blocks_intersecting_bbox, AABBExt, PhysicsComponent, Side, -}; -use feather_core::world::ChunkMap; +use crate::entity::Velocity; +use crate::physics::{block_impacted_by_ray, blocks_intersecting_bbox, AABBExt, Physics, Side}; +use crate::state::State; use feather_core::Position; use feather_core::{Block, BlockExt}; -use shrev::EventChannel; +use legion::entity::Entity; +use parking_lot::Mutex; +use tonks::{PreparedQuery, PreparedWorld, Read, Trigger, Write}; +/// Event triggered when an entity lands on the ground. #[derive(Debug, Clone)] pub struct EntityPhysicsLandEvent { pub entity: Entity, @@ -20,197 +19,130 @@ pub struct EntityPhysicsLandEvent { /// System for updating all entities' positions and velocities /// each tick. -pub struct EntityPhysicsSystem; - -impl<'a> System<'a> for EntityPhysicsSystem { - type SystemData = ( - WriteStorage<'a, PositionComponent>, - WriteStorage<'a, VelocityComponent>, - ReadStorage<'a, PhysicsComponent>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - Read<'a, ChunkMap>, - Entities<'a>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut positions, - mut velocities, - physics, - mut entity_destroy_events, - mut entity_land_events, - chunk_map, - entities, - ) = data; - // Go through entities and update their positions according - // to their velocities. - - // Unfortunately, we are currently not able to parallel - // join over the position storage due to slide-rs/specs#541. - // When this issue is resolved, the join below should be switched to a parallel - // join. - - // A restricted storage is used for `velocity` so as to avoid - // triggering a velocity update event when it is not actually - // modified. - for (position, mut restrict_velocity, physics, entity) in ( - &mut positions, - &mut velocities.restrict_mut(), - &physics, - &entities, - ) - .join() - { - let mut velocity = *restrict_velocity.get_unchecked(); - - let mut pending_position = position.current + velocity.0; - - // Check for blocks along path between old position and pending position. - // This prevents entities from flying through blocks when their - // velocity is sufficiently high. - let origin = position.current.into(); - let direction = (pending_position - position.current).into(); - let distance_squared = pending_position.distance_squared(position.current); - - if let Some(impacted) = - block_impacted_by_ray(&chunk_map, origin, direction, distance_squared) - { - // Set velocities along correct axis to 0 and then set position - // to just before the bbox would have impacted the block. - let face = impacted.face; - let impact = impacted.pos; - - if face.contains(Side::EAST) || face.contains(Side::WEST) { - velocity.x = 0.0; - pending_position.x = impact.x + physics.bbox.size().x * face.as_vector().x; - } - if face.contains(Side::NORTH) || face.contains(Side::SOUTH) { - velocity.z = 0.0; - pending_position.z = impact.z + physics.bbox.size().z * face.as_vector().z; - } - if face.contains(Side::TOP) || face.contains(Side::BOTTOM) { - velocity.y = 0.0; - pending_position.y = impact.y + physics.bbox.size().y * face.as_vector().y; - } - if face.contains(Side::TOP) { - pending_position.on_ground = true; - } - } - - // Check for blocks around the bbox and apply offset - // to position to stop the bbox from intersecting blocks. - let intersect = blocks_intersecting_bbox( - &chunk_map, - position.current, - pending_position, - &physics.bbox, - ); - intersect.apply_to(&mut pending_position); - - if intersect.x_affected() { +#[system] +fn physics( + state: &State, + mut query: PreparedQuery<(Write, Write, Read)>, + mut world: PreparedWorld, + land_events: Trigger, +) { + // Using a mutex is fine, since land events are written very rairly + // and thus contention is low. + let land_events = Mutex::new(land_events); + + // Go through entities and update their positions according + // to their velocities. + query.par_entities_for_each(&mut world, |(entity, position, velocity, physics)| { + let mut velocity = *restrict_velocity.get_unchecked(); + + let mut pending_position = position.current + velocity.0; + + // Check for blocks along path between old position and pending position. + // This prevents entities from flying through blocks when their + // velocity is sufficiently high. + let origin = position.current.into(); + let direction = (pending_position - position.current).into(); + let distance_squared = pending_position.distance_squared(position.current); + + if let Some(impacted) = block_impacted_by_ray(&state, origin, direction, distance_squared) { + // Set velocities along correct axis to 0 and then set position + // to just before the bbox would have impacted the block. + let face = impacted.face; + let impact = impacted.pos; + + if face.contains(Side::EAST) || face.contains(Side::WEST) { velocity.x = 0.0; + pending_position.x = impact.x + physics.bbox.size().x * face.as_vector().x; } - - if intersect.y_affected() { - velocity.y = 0.0; - } - - if intersect.z_affected() { + if face.contains(Side::NORTH) || face.contains(Side::SOUTH) { velocity.z = 0.0; + pending_position.z = impact.z + physics.bbox.size().z * face.as_vector().z; } - - // Delete entity if it has gone into unloaded chunks. - let block_at_pos = match chunk_map.block_at(pending_position.block_pos()) { - Some(block) => block, - None => { - // Delete entity. - let event = EntityDestroyEvent { entity }; - entity_destroy_events.single_write(event); - - entities.delete(entity).unwrap(); - continue; - } - }; - - // Set on ground status - pending_position.on_ground = match chunk_map.block_at( - position!( - pending_position.x, - pending_position.y - physics.bbox.size().y / 2.0 - 0.01, - pending_position.z - ) - .block_pos(), - ) { - Some(block) => block.is_solid(), - None => false, - }; - if pending_position.on_ground && !position.current.on_ground { - entity_land_events.single_write(EntityPhysicsLandEvent { - entity, - pos: pending_position, - }); + if face.contains(Side::TOP) || face.contains(Side::BOTTOM) { + velocity.y = 0.0; + pending_position.y = impact.y + physics.bbox.size().y * face.as_vector().y; } - - // Apply drag and gravity. - - // In water and lava, gravity is four times less, and velocity is multiplied by a special drag force. - let liquid_drag = 0.8; - match block_at_pos { - Block::Water(_) => { - velocity.0 *= liquid_drag; - velocity.0.y += physics.gravity / 4.0; - } - Block::Lava(_) => { - velocity.0 *= liquid_drag - 0.3; - velocity.0.y += physics.gravity / 4.0; - } - _ => { - let slip_multiplier = physics.slip_multiplier; - if pending_position.on_ground { - velocity.0.x *= slip_multiplier; - velocity.0.z *= slip_multiplier; - } else { - velocity.0.y = physics.drag * velocity.0.y + physics.gravity; - velocity.0.x *= physics.drag; - velocity.0.z *= physics.drag; - } - } + if face.contains(Side::TOP) { + pending_position.on_ground = true; } + } - // Set new position. - // A move event is triggered through FlaggedStorage. - position.current = pending_position; + // Check for blocks around the bbox and apply offset + // to position to stop the bbox from intersecting blocks. + let intersect = + blocks_intersecting_bbox(&state, position.current, pending_position, &physics.bbox); + intersect.apply_to(&mut pending_position); - // Update velocity, if it changed. - if velocity != *restrict_velocity.get_unchecked() { - *restrict_velocity.get_mut_unchecked() = velocity; - } + if intersect.x_affected() { + velocity.x = 0.0; } - } -} -#[cfg(test)] -mod tests { - use super::*; - use crate::entity::test; - use crate::physics::PhysicsBuilder; - use crate::testframework as t; - use specs::{Builder, WorldExt}; + if intersect.y_affected() { + velocity.y = 0.0; + } - #[test] - fn test_unloaded_chunk() { - let (mut w, mut d) = t::builder().with(EntityPhysicsSystem, "").build(); + if intersect.z_affected() { + velocity.z = 0.0; + } - let entity = test::create(&mut w, position!(1000.0, 100.0, 1000.0)).build(); + // Delete entity if it has gone into unloaded chunks. + let block_at_pos = match state.block_at(pending_position.block_pos()) { + Some(block) => block, + None => { + // Delete entity. + state.exec(move |world| { + world.delete(entity); + }); + return; + } + }; + + // Set on ground status. + pending_position.on_ground = match state.block_at( + position!( + pending_position.x, + pending_position.y - physics.bbox.size().y / 2.0 - 0.01, + pending_position.z + ) + .block_pos(), + ) { + Some(block) => block.is_solid(), + None => false, + }; + if pending_position.on_ground && !position.current.on_ground { + land_events.lock().trigger(EntityPhysicsLandEvent { + entity, + pos: pending_position, + }); + } - w.write_component::() - .insert(entity, PhysicsBuilder::new().build()) - .unwrap(); + // Apply drag and gravity. - d.dispatch(&w); - w.maintain(); + // In water and lava, gravity is four times less, and velocity is multiplied by a special drag force. + let liquid_drag = 0.8; + match block_at_pos { + Block::Water(_) => { + velocity.0 *= liquid_drag; + velocity.0.y += physics.gravity / 4.0; + } + Block::Lava(_) => { + velocity.0 *= liquid_drag - 0.3; + velocity.0.y += physics.gravity / 4.0; + } + _ => { + let slip_multiplier = physics.slip_multiplier; + if pending_position.on_ground { + velocity.0.x *= slip_multiplier; + velocity.0.z *= slip_multiplier; + } else { + velocity.0.y = physics.drag * velocity.0.y + physics.gravity; + velocity.0.x *= physics.drag; + velocity.0.z *= physics.drag; + } + } + } - t::assert_removed(&w, entity); - } + // Set new position. + position.current = pending_position; + }); } diff --git a/server/src/physics/math.rs b/server/src/physics/math.rs index 11f599df0..4d02d2473 100644 --- a/server/src/physics/math.rs +++ b/server/src/physics/math.rs @@ -4,6 +4,7 @@ use crate::entity::{ChunkEntities, PositionComponent}; use crate::physics::block_bboxes::bbox_for_block; use crate::physics::AABBExt; +use crate::state::State; use feather_blocks::Block; use feather_core::world::{BlockPosition, ChunkMap, Position}; use feather_core::{BlockExt, ChunkPosition}; @@ -15,8 +16,6 @@ use ncollide3d::query; use ncollide3d::query::{Ray, RayCast}; use ncollide3d::shape::{Compound, Cuboid, ShapeHandle}; use smallvec::SmallVec; -use specs::storage::GenericReadStorage; -use specs::Entity; use std::cmp::Ordering; use std::f64::INFINITY; @@ -89,7 +88,7 @@ pub struct RayImpact { /// Traces up to `max_distance` before returning `None` /// if no block was found. pub fn block_impacted_by_ray( - chunk_map: &ChunkMap, + state: &State, origin: DVec3, ray: DVec3, max_distance_squared: f64, @@ -167,7 +166,7 @@ pub fn block_impacted_by_ray( let mut current_pos = Position::from(origin).block_pos(); while dist_traveled.magnitude_squared() < max_distance_squared { - if let Some(block) = chunk_map.block_at(current_pos) { + if let Some(block) = state.block_at(current_pos) { if block.is_solid() { // Calculate world-space position of // impact using `ncollide`. @@ -318,7 +317,7 @@ impl BlockIntersect { /// than 1 are not supported. If the bounding box's size /// is more than 1, this function will panic. pub fn blocks_intersecting_bbox( - chunk_map: &ChunkMap, + state: &State, mut from: Position, mut dest: Position, bbox: &AABB, @@ -351,7 +350,7 @@ pub fn blocks_intersecting_bbox( let mut checked = heapless::FnvIndexSet::new(); for (axis, sign) in &axis { - let compound = adjacent_to_bbox(*axis, *sign, bbox, dest, &chunk_map, &mut checked); + let compound = adjacent_to_bbox(*axis, *sign, bbox, dest, &state, &mut checked); blocks.push(compound); } @@ -426,7 +425,7 @@ pub fn adjacent_to_bbox( sign: i32, bbox: &AABB, pos: Position, - chunk_map: &ChunkMap, + state: &State, checked: &mut heapless::FnvIndexSet, ) -> Compound { assert!(axis <= 2); @@ -486,7 +485,7 @@ pub fn adjacent_to_bbox( continue; } - match chunk_map.block_at(block_pos) { + match state.block_at(block_pos) { Some(block) => { if block.is_solid() { checked.insert(block_pos).unwrap(); diff --git a/server/src/physics/mod.rs b/server/src/physics/mod.rs index c3423d4c7..3e66be0c4 100644 --- a/server/src/physics/mod.rs +++ b/server/src/physics/mod.rs @@ -6,7 +6,7 @@ mod entity; mod math; use crate::systems::ENTITY_PHYSICS; -pub use component::{AABBExt, PhysicsBuilder, PhysicsComponent}; +pub use component::{AABBExt, Physics, PhysicsBuilder}; pub use entity::{EntityPhysicsLandEvent, EntityPhysicsSystem}; pub use math::*; use specs::DispatcherBuilder; From 0e509c4f6db1fc281a6fb30daf1156afd758916f Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 15 Nov 2019 17:00:42 -0700 Subject: [PATCH 07/37] Begin translating chunk logic --- Cargo.lock | 29 ++++-- core/src/world/mod.rs | 11 +++ server/Cargo.toml | 4 +- server/src/chunk_entities.rs | 43 +++++++++ server/src/chunk_logic.rs | 95 +++++++------------ .../src/{chunkworker.rs => chunk_worker.rs} | 0 server/src/entity/mod.rs | 10 +- .../{initialhandler.rs => initial_handler.rs} | 0 server/src/io/mod.rs | 4 +- server/src/io/worker.rs | 2 +- server/src/lazy.rs | 13 ++- server/src/lib.rs | 3 +- server/src/physics/mod.rs | 12 +-- server/src/shutdown.rs | 6 +- server/src/state.rs | 27 +++++- 15 files changed, 168 insertions(+), 91 deletions(-) create mode 100644 server/src/chunk_entities.rs rename server/src/{chunkworker.rs => chunk_worker.rs} (100%) rename server/src/io/{initialhandler.rs => initial_handler.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 4d834ea7e..6ccac05f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -526,6 +526,14 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "evmap" +version = "7.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -646,6 +654,7 @@ dependencies = [ name = "feather-server" version = "0.5.0" dependencies = [ + "ahash 0.2.16 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "bitvec 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -656,6 +665,7 @@ dependencies = [ "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "derive-new 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "evmap 7.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "feather-blocks 0.5.0", "feather-codegen 0.5.0", @@ -696,7 +706,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2187,6 +2197,11 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smallvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "sourcefile" version = "0.1.4" @@ -2510,7 +2525,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7#0691131efcffea7c0225f9d6203b58eb2cc27cd7" +source = "git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809#3fcc5368e6ee072c530762fa2b08d020a542a809" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2526,13 +2541,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7#0691131efcffea7c0225f9d6203b58eb2cc27cd7" +source = "git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809#3fcc5368e6ee072c530762fa2b08d020a542a809" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2950,6 +2965,7 @@ dependencies = [ "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" +"checksum evmap 7.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4115e31301bb9dafa8ea8549432b14a51e19eecaa9e5c6041f099545e955c225" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" @@ -3115,6 +3131,7 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slotmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "759fd553261805f128e2900bf69ab3d034260bc338caf7f0ee54dbf035c85acd" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" @@ -3147,8 +3164,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=0691131efcffea7c0225f9d6203b58eb2cc27cd7)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=3fcc5368e6ee072c530762fa2b08d020a542a809)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 17fa87a62..39d61812d 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -301,6 +301,17 @@ impl ChunkMap { pub fn iter_chunks(&self) -> impl IntoIterator>> { self.0.iter() } + + /// Inserts a new chunk into the chunk map. + pub fn insert(&mut self, chunk: Chunk) { + self.0 + .insert(chunk.position(), Arc::new(RwLock::new(chunk))); + } + + /// Removes the chunk at the given position, returning `true` if it existed. + pub fn remove(&mut self, pos: ChunkPosition) -> bool { + self.0.remove(&pos).is_some() + } } impl Default for ChunkMap { diff --git a/server/Cargo.toml b/server/Cargo.toml index feaa9a857..b6d700ba3 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -17,7 +17,7 @@ feather-blocks = { path = "../blocks" } feather-core = { path = "../core" } feather-item-block = { path = "../item_block" } legion = { git = "https://github.com/TomGillen/legion", rev = "2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "0691131efcffea7c0225f9d6203b58eb2cc27cd7" } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "3fcc5368e6ee072c530762fa2b08d020a542a809" } crossbeam = "0.7" log = "0.4" simple_logger = "1.3" @@ -65,6 +65,8 @@ tokio-executor = "=0.2.0-alpha.6" futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] } humantime-serde = "0.1" ctrlc = "3.1" +evmap = "7.1" +ahash = "0.2" [dev-dependencies] criterion = "0.3.0" diff --git a/server/src/chunk_entities.rs b/server/src/chunk_entities.rs new file mode 100644 index 000000000..16cdbcd4a --- /dev/null +++ b/server/src/chunk_entities.rs @@ -0,0 +1,43 @@ +use ahash::ABuildHasher; +use evmap::shallow_copy::CopyValue; +use evmap::{ReadHandle, ReadHandleFactory, WriteHandle}; +use feather_core::ChunkPosition; +use legion::entity::Entity; +use thread_local::ThreadLocal; + +/// Stores which entities belong to every given chunk. +/// +/// This data structure can be used to accelerate certain +/// operations, such as querying for entities +/// within some distance of a position. In addition, +/// it can be used to send all entities in a chunk +/// to a player. +/// +/// This structure is internally stored in `State`, using +/// `evmap` for concurrent map access. +/// +/// Do note that the information in this structure is not necessarily up to date, +/// although a best effort is made to update the data. +pub struct ChunkEntities { + writer: WriteHandle, (), ABuildHasher>, + factory: ReadHandleFactory, (), ABuildHasher>, + readers: ThreadLocal, (), ABuildHasher>>, +} + +impl ChunkEntities { + pub fn new() -> Self { + let (reader, writer) = evmap::with_hasher((), ABuildHasher); + Self { + writer, + factory: reader.factory(), + readers: ThreadLocal::new(), + } + } + /// Returns a slice of entities in the given chunk. + pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &[CopyValue] { + self.readers + .get_or(|| self.factory.handle()) + .get_and(&chunk, |slice| slice) + .unwrap_or(&[]) + } +} diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index 2212903ef..9331fc3bc 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -3,10 +3,6 @@ //! //! Also handles unloading chunks when unused. use crossbeam::channel::{Receiver, Sender}; -use shrev::{EventChannel, ReaderId}; -use specs::{ - Component, DispatcherBuilder, Entity, Read, ReadExpect, ReadStorage, System, World, Write, -}; use std::sync::atomic::{AtomicU32, Ordering}; use feather_core::world::{ChunkMap, ChunkPosition}; @@ -14,25 +10,25 @@ use feather_core::world::{ChunkMap, ChunkPosition}; use rayon::prelude::*; use crate::config::Config; -use crate::entity::EntityDestroyEvent; -use crate::systems::{CHUNK_HOLD_REMOVE, CHUNK_LOAD, CHUNK_OPTIMIZE, CHUNK_UNLOAD}; +use crate::state::State; use crate::worldgen::WorldGenerator; -use crate::{chunkworker, current_time_in_millis, TickCount, TPS}; +use crate::{chunk_worker, current_time_in_millis, TickCount, TPS}; use feather_core::entity::EntityData; use feather_core::Chunk; use hashbrown::HashSet; +use legion::entity::Entity; use multimap::MultiMap; -use specs::storage::BTreeStorage; use std::collections::VecDeque; use std::path::Path; use std::sync::Arc; +use tonks::Trigger; /// A handle for interacting with the chunk /// worker thread. #[derive(Debug, Clone)] pub struct ChunkWorkerHandle { - pub sender: Sender, - pub receiver: Receiver, + pub sender: Sender, + pub receiver: Receiver, } /// Event which is triggered when a chunk is loaded. @@ -56,54 +52,33 @@ pub struct ChunkUnloadEvent { } /// System for receiving loaded chunks from the chunk worker thread. -pub struct ChunkLoadSystem; - -impl<'a> System<'a> for ChunkLoadSystem { - type SystemData = ( - Write<'a, ChunkMap>, - Write<'a, EventChannel>, - Write<'a, EventChannel>, - ReadExpect<'a, ChunkWorkerHandle>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (mut chunk_map, mut load_events, mut fail_events, handle) = data; - - while let Ok(reply) = handle.receiver.try_recv() { - if let chunkworker::Reply::LoadedChunk(pos, result) = reply { - match result { - Ok((chunk, entities)) => { - chunk_map.set_chunk_at(pos, chunk); - - // Trigger event - let event = ChunkLoadEvent { pos, entities }; - load_events.single_write(event); - - trace!("Loaded chunk at {:?}", pos); - } - Err(err) => { - warn!("Failed to load chunk at {:?}: {}", pos, err); - let event = ChunkLoadFailEvent { pos }; - fail_events.single_write(event); - } +#[system] +fn chunk_load_system( + state: &State, + handle: &ChunkWorkerHandle, + mut load_events: Trigger, + mut fail_events: Trigger, +) { + while let Ok(reply) = handle.receiver.try_recv() { + if let chunk_worker::Reply::LoadedChunk(pos, result) = reply { + match result { + Ok((chunk, entities)) => { + state.lazy_insert_chunk(chunk); + + // Trigger event + let event = ChunkLoadEvent { pos, entities }; + load_events.trigger(event); + + trace!("Loaded chunk at {:?}", pos); + } + Err(err) => { + warn!("Failed to load chunk at {:?}: {}", pos, err); + let event = ChunkLoadFailEvent { pos }; + fail_events.trigger(event); } } } } - - fn setup(&mut self, world: &mut World) { - use specs::prelude::SystemData; - - let generator = world.fetch_mut::>().clone(); - let world_name = &world.fetch_mut::>().world.name.clone(); - let world_dir = Path::new(world_name); - - info!("Starting chunk worker thread"); - let (sender, receiver) = chunkworker::start(world_dir, generator); - world.insert(ChunkWorkerHandle { sender, receiver }); - - Self::SystemData::setup(world); - } } /// Asynchronously loads the chunk at the given position. @@ -116,7 +91,7 @@ pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { // Send request to chunk worker thread handle .sender - .send(chunkworker::Request::LoadChunk(pos)) + .send(chunk_worker::Request::LoadChunk(pos)) .unwrap(); } @@ -124,7 +99,7 @@ pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { pub fn save_chunk(handle: &ChunkWorkerHandle, chunk: Arc, entities: Vec) { handle .sender - .send(chunkworker::Request::SaveChunk(chunk, entities)) + .send(chunk_worker::Request::SaveChunk(chunk, entities)) .unwrap(); } @@ -163,7 +138,7 @@ impl ChunkHolders { &mut self, chunk: ChunkPosition, holder: Entity, - events: &mut EventChannel, + trigger: &mut Trigger, ) { if let Some(vec) = self.inner.get_vec_mut(&chunk) { let index = vec.iter().position(|e| *e == holder); @@ -175,7 +150,7 @@ impl ChunkHolders { entity: holder, chunk, }; - events.single_write(event); + trigger.trigger(event); } } } @@ -474,7 +449,7 @@ mod tests { let chunk_map = ChunkMap::new(); let pos = ChunkPosition::new(0, 0); send2 - .send(chunkworker::Reply::LoadedChunk( + .send(chunk_worker::Reply::LoadedChunk( pos, Ok((Chunk::new(pos), vec![])), )) @@ -515,7 +490,7 @@ mod tests { let recv = recv1.try_recv().unwrap(); match recv { - chunkworker::Request::LoadChunk(recv_pos) => assert_eq!(recv_pos, pos), + chunk_worker::Request::LoadChunk(recv_pos) => assert_eq!(recv_pos, pos), _ => panic!(), } } diff --git a/server/src/chunkworker.rs b/server/src/chunk_worker.rs similarity index 100% rename from server/src/chunkworker.rs rename to server/src/chunk_worker.rs diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 87d23831a..1efc715d0 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -2,7 +2,15 @@ use crate::lazy::EntityBuilder; use crate::state::State; -use feather_core::Position; +use feather_core::{ChunkPosition, Position}; +use legion::prelude::Entity; +use parking_lot::Mutex; + +/// Event triggered when an entity is removed. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EntityDeleteEvent { + entity: Entity, +} /// The velocity of an entity. #[derive(Default, Debug, PartialEq, Clone, Copy)] diff --git a/server/src/io/initialhandler.rs b/server/src/io/initial_handler.rs similarity index 100% rename from server/src/io/initialhandler.rs rename to server/src/io/initial_handler.rs diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 106f04480..71ec0e32f 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -5,7 +5,7 @@ use std::net::SocketAddr; use std::sync::Arc; use uuid::Uuid; -mod initialhandler; +mod initial_handler; mod listener; mod worker; @@ -75,7 +75,7 @@ impl Default for NetworkIoManager { /// Initializes certain static variables. pub fn init() { - lazy_static::initialize(&initialhandler::RSA_KEY); + lazy_static::initialize(&initial_handler::RSA_KEY); } async fn run_listener( diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index a312f6b39..d9a966f04 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -7,7 +7,7 @@ //! to the worker for any given client. use crate::config::Config; -use crate::io::initialhandler::{Action, InitialHandler}; +use crate::io::initial_handler::{Action, InitialHandler}; use crate::io::{ListenerToServerMessage, NewClientInfo, ServerToWorkerMessage}; use crate::PlayerCount; use feather_core::network::codec::MinecraftCodec; diff --git a/server/src/lazy.rs b/server/src/lazy.rs index 245ce91e5..265c61b94 100644 --- a/server/src/lazy.rs +++ b/server/src/lazy.rs @@ -3,6 +3,7 @@ use legion::entity::Entity; use legion::storage::{Component, Tag}; use legion::world::World; use smallvec::SmallVec; +use tonks::Scheduler; /// Resource which allows lazy creation of entities /// or execution of functions with world access. @@ -14,6 +15,12 @@ pub struct Lazy { impl Lazy { /// Lazily executes a closure with world access. pub fn exec(&self, f: impl FnOnce(&mut World)) { + self.exec_with_scheduler(move |world, _| f(world)); + } + + /// Lazily executes a closure with world and scheduler (resource) + /// access. + pub fn exec_with_scheduler(&self, f: impl FnOnce(&mut World, &mut Scheduler)) { self.queue.push(Action::Exec(Box::new(f))); } @@ -27,10 +34,10 @@ impl Lazy { } /// Performs all queued actions. - pub fn flush(&self, world: &mut World) { + pub fn flush(&self, world: &mut World, scheduler: &mut Scheduler) { while let Ok(action) = self.queue.pop() { match action { - Action::Exec(f) => f(world), + Action::Exec(f) => f(world, scheduler), } } } @@ -38,7 +45,7 @@ impl Lazy { /// An action which the lazy updater may perform. enum Action { - Exec(Box), + Exec(Box), } /// Builder for lazily creating entities. diff --git a/server/src/lib.rs b/server/src/lib.rs index 6900869bb..4b3b1f753 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -146,8 +146,9 @@ use std::process::exit; #[global_allocator] static ALLOC: System = System; +pub mod chunk_entities; pub mod chunk_logic; -pub mod chunkworker; +pub mod chunk_worker; pub mod config; pub mod entity; pub mod io; diff --git a/server/src/physics/mod.rs b/server/src/physics/mod.rs index 3e66be0c4..aeec599ef 100644 --- a/server/src/physics/mod.rs +++ b/server/src/physics/mod.rs @@ -5,16 +5,6 @@ mod component; mod entity; mod math; -use crate::systems::ENTITY_PHYSICS; pub use component::{AABBExt, Physics, PhysicsBuilder}; -pub use entity::{EntityPhysicsLandEvent, EntityPhysicsSystem}; +pub use entity::EntityPhysicsLandEvent; pub use math::*; -use specs::DispatcherBuilder; - -pub fn init_logic(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(EntityPhysicsSystem, ENTITY_PHYSICS, &[]); -} - -pub fn init_handlers(_dispatcher: &mut DispatcherBuilder) { - // nothing -} diff --git a/server/src/shutdown.rs b/server/src/shutdown.rs index 5bcb42a43..ab85578da 100644 --- a/server/src/shutdown.rs +++ b/server/src/shutdown.rs @@ -6,7 +6,7 @@ use crate::entity::{NamedComponent, PlayerComponent, PositionComponent}; use crate::player; use crate::player::InventoryComponent; use crate::time::Time; -use crate::{chunkworker, entity}; +use crate::{chunk_worker, entity}; use crossbeam::Sender; use feather_core::level::{save_level_file, LevelData, Root}; use feather_core::prelude::ChunkMap; @@ -34,12 +34,12 @@ pub fn save_chunks(world: &mut World) { world.maintain(); let handle = world.fetch::(); - handle.sender.send(chunkworker::Request::ShutDown).unwrap(); + handle.sender.send(chunk_worker::Request::ShutDown).unwrap(); let mut saved = 0; // Wait for chunks to finish saving while let Ok(msg) = handle.receiver.recv() { - if let chunkworker::Reply::SavedChunk(_) = msg { + if let chunk_worker::Reply::SavedChunk(_) = msg { saved += 1; } diff --git a/server/src/state.rs b/server/src/state.rs index 4d7a89b16..7310db1a2 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -6,6 +6,7 @@ use feather_core::{BlockPosition, Chunk, ChunkPosition}; use legion::world::World; use parking_lot::RwLockReadGuard; use std::sync::Arc; +use tonks::Scheduler; /// The state of the server. /// @@ -36,8 +37,8 @@ impl State { } /// See `Lazy::flush()`. - pub fn flush(&self, world: &mut World) { - self.lazy.flush(world); + pub fn flush(&self, world: &mut World, scheduler: &mut Scheduler) { + self.lazy.flush(world, scheduler); } /// Retrieves the block at the given position, @@ -59,4 +60,26 @@ impl State { pub fn chunk_at(&self, pos: ChunkPosition) -> Option> { self.chunk_map.chunk_at(pos) } + + /// Lazily inserts the given chunk into the chunk map. + pub fn lazy_insert_chunk(&self, chunk: Chunk) { + self.lazy.exec_with_scheduler(move |_, scheduler| { + scheduler + .resources() + .get_mut::() + .chunk_map + .insert(chunk); + }); + } + + /// Lazily removes the given chunk from the chunk map. + pub fn lazy_remove_chunk(&self, pos: ChunkPosition) { + self.lazy.exec_with_scheduler(move |_, scheduler| { + scheduler + .resources() + .get_mut::() + .chunk_map + .remove(pos); + }); + } } From ffcb46e4357cca597fe98e758e5949d13de9680a Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 15 Nov 2019 18:27:39 -0700 Subject: [PATCH 08/37] Reimplement the rest of chunk logic --- Cargo.lock | 1 + core/Cargo.toml | 3 +- core/src/world/mod.rs | 6 + server/src/chunk_logic.rs | 336 +++++++++----------------------------- server/src/entity/mod.rs | 2 +- server/src/state.rs | 2 +- 6 files changed, 89 insertions(+), 261 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6ccac05f8..67398fad3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,6 +607,7 @@ dependencies = [ "num-derive 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/core/Cargo.toml b/core/Cargo.toml index 3f67ffed3..b13027a10 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -20,7 +20,7 @@ log = "0.4" serde = { version = "1.0", features = ["derive"] } num-traits = "0.2" num-derive = "0.3" -hashbrown = { version = "0.6", features = ["serde"] } +hashbrown = { version = "0.6", features = ["serde", "rayon"] } hematite-nbt = "0.4" byteorder = "1.3" nalgebra-glm = "0.4" @@ -35,3 +35,4 @@ failure = "0.1" bitvec = "0.15" multimap = "0.6" parking_lot = "0.9.0" +rayon = "1.2.0" diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index 39d61812d..c7d2824d1 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -3,6 +3,7 @@ use crate::world::chunk::Chunk; use glm::{DVec3, Vec3}; use hashbrown::HashMap; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use rayon::iter::ParallelIterator; use std::fmt; use std::fmt::{Display, Formatter}; use std::ops::{Add, Sub}; @@ -302,6 +303,11 @@ impl ChunkMap { self.0.iter() } + /// Returns a parallel iterator over chunks. + pub fn par_iter_chunks(&self) -> impl ParallelIterator>> { + self.0.par_iter() + } + /// Inserts a new chunk into the chunk map. pub fn insert(&mut self, chunk: Chunk) { self.0 diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index 9331fc3bc..61ab314ef 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -10,6 +10,7 @@ use feather_core::world::{ChunkMap, ChunkPosition}; use rayon::prelude::*; use crate::config::Config; +use crate::entity::EntityDeleteEvent; use crate::state::State; use crate::worldgen::WorldGenerator; use crate::{chunk_worker, current_time_in_millis, TickCount, TPS}; @@ -21,7 +22,7 @@ use multimap::MultiMap; use std::collections::VecDeque; use std::path::Path; use std::sync::Arc; -use tonks::Trigger; +use tonks::{PreparedQuery, PreparedWorld, Query, Read, Trigger}; /// A handle for interacting with the chunk /// worker thread. @@ -44,13 +45,6 @@ pub struct ChunkLoadFailEvent { pub pos: ChunkPosition, } -/// Event which is triggered when a chunk is unloaded. -#[derive(Clone)] -pub struct ChunkUnloadEvent { - /// The chunk which was unloaded. - pub chunk: Arc, -} - /// System for receiving loaded chunks from the chunk worker thread. #[system] fn chunk_load_system( @@ -166,7 +160,7 @@ pub struct ChunkHolderReleaseEvent { } /// The queue of chunks to be unloaded. -/// See `ChunkUnloadSystem` for details. +/// See `chunk_unload` for details. #[derive(Clone, Debug, Default)] pub struct ChunkUnloadQueue { /// The internal queue. @@ -187,13 +181,7 @@ struct ChunkUnload { const CHUNK_UNLOAD_TIME: u64 = TPS * 5; // 5 seconds - TODO make this configurable /// System for unloading chunks when they have no holders. -/// This system performs multiple actions: -/// -/// * It listens to `ChunkHolderReleaseEvent` and -/// checks if a chunk has no holders. If so, it queues -/// the chunk to be unloaded after some period of time -/// (defined by a constant). -/// * It goes through chunks which are currently +/// This system through chunks which are currently /// queued to be loaded and unloads them if the /// period of time has elapsed. /// @@ -203,91 +191,51 @@ const CHUNK_UNLOAD_TIME: u64 = TPS * 5; // 5 seconds - TODO make this configurab /// could quickly move between chunk boundaries, causing /// chunks at the edge of their view distance /// to be loaded and unloaded at an alarming rate. -#[derive(Default)] -pub struct ChunkUnloadSystem { - reader: Option>, -} - -impl ChunkUnloadSystem { - pub fn new() -> Self { - Self { reader: None } - } -} - -impl<'a> System<'a> for ChunkUnloadSystem { - type SystemData = ( - Write<'a, ChunkMap>, - Write<'a, EventChannel>, - Read<'a, EventChannel>, - Write<'a, ChunkUnloadQueue>, - Read<'a, ChunkHolders>, - Read<'a, TickCount>, - ); - - fn run(&mut self, data: Self::SystemData) { - let ( - mut chunk_map, - mut unload_events, - release_events, - mut unload_queue, - holders, - tick_count, - ) = data; - - // Handle holder release events. - for event in release_events.read(&mut self.reader.as_mut().unwrap()) { - // If the chunk now has zero holders, queue it for unloading. - if !holders.chunk_has_holders(event.chunk) { - let unload = ChunkUnload { - chunk: event.chunk, - time: tick_count.0 + CHUNK_UNLOAD_TIME, - }; - unload_queue.queue.push_back(unload); - } - } - - // Unload chunks which are finished in the queue. - - // Since chunks are queued in the back and taken out - // from the front, the chunks in the front of the vector - // were queued the longest time ago. Because of this, - // we go through the unloads in the front of the queue - // to find which chunks to unload. - while let Some(unload) = unload_queue.queue.front() { - if tick_count.0 >= unload.time { - // Don't unload if new chunk holders have appeared. - if holders.chunk_has_holders(unload.chunk) { - unload_queue.queue.pop_front(); - continue; - } - - // Unload chunk and pop from queue. - if let Some(chunk) = chunk_map.unload_chunk_at(unload.chunk) { - let event = ChunkUnloadEvent { - chunk: Arc::new(chunk), - }; - unload_events.single_write(event); - } - +#[system] +fn chunk_unload(state: &State, unload_queue: &mut ChunkUnloadQueue, holders: &ChunkHolders) { + // Unload chunks which are finished in the queue. + + // Since chunks are queued in the back and taken out + // from the front, the chunks in the front of the vector + // were queued the longest time ago. Because of this, + // we go through the unloads in the front of the queue + // to find which chunks to unload. + while let Some(unload) = unload_queue.queue.front() { + if tick_count.0 >= unload.time { + // Don't unload if new chunk holders have appeared. + if holders.chunk_has_holders(unload.chunk) { unload_queue.queue.pop_front(); - } else { - // We're done - all chunks farther up in - // the queue were queued before this one, - // so it isn't time to unload any of those. - break; + continue; } + + // Unload chunk and pop from queue. + state.lazy_remove_chunk(unload.chunk); + unload_queue.queue.pop_front(); + } else { + // We're done - all chunks farther up in + // the queue were queued before this one, + // so it isn't time to unload any of those. + break; } } +} - fn setup(&mut self, world: &mut World) { - use specs::SystemData; - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); +/// Event handler which handles holder release events. If +/// a chunk has no more holders, then a chunk unload is queued. +#[event_handler] +pub fn chunk_unload_no_holders( + event: &ChunkHolderReleaseEvent, + holders: &ChunkHolders, + unload_queue: &mut ChunkUnloadQueue, +) { + // Handle holder release events. + // If the chunk now has zero holders, queue it for unloading. + if !holders.chunk_has_holders(event.chunk) { + let unload = ChunkUnload { + chunk: event.chunk, + time: tick_count.0 + CHUNK_UNLOAD_TIME, + }; + unload_queue.queue.push_back(unload); } } @@ -299,7 +247,8 @@ impl<'a> System<'a> for ChunkUnloadSystem { /// stored in the `ChunkHolders` resource, /// using this component allows for efficiently /// finding which chunks a given entity has -/// a hold on. +/// a hold on, rather than having +/// to linear search all chunks (obviously ridiculous). #[derive(Default)] pub struct ChunkHolderComponent { pub holds: HashSet, @@ -307,61 +256,24 @@ pub struct ChunkHolderComponent { impl ChunkHolderComponent { pub fn new() -> Self { - Self { - holds: HashSet::new(), - } + Self::default() } } -impl Component for ChunkHolderComponent { - type Storage = BTreeStorage; -} - /// System for removing an entity's chunk holds /// once it is destroyed. -#[derive(Default)] -pub struct ChunkHoldRemoveSystem { - reader: Option>, -} - -impl ChunkHoldRemoveSystem { - pub fn new() -> Self { - Self { reader: None } - } -} - -impl<'a> System<'a> for ChunkHoldRemoveSystem { - type SystemData = ( - Read<'a, EventChannel>, - Write<'a, ChunkHolders>, - ReadStorage<'a, ChunkHolderComponent>, - Write<'a, EventChannel>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (events, mut holders, holder_comps, mut release_events) = data; - - for event in events.read(&mut self.reader.as_mut().unwrap()) { - // If entity had chunk holds, remove them all - if let Some(holder_comp) = holder_comps.get(event.entity) { - debug!("Removing chunk holds for entity {:?}", event.entity); - holder_comp.holds.iter().for_each(|chunk| { - holders.remove_holder(*chunk, event.entity, &mut release_events); - }); - } - } - } - - fn setup(&mut self, world: &mut World) { - use specs::SystemData; - - Self::SystemData::setup(world); - - self.reader = Some( - world - .fetch_mut::>() - .register_reader(), - ); +#[event_handler] +fn chunk_holder_remove( + event: &EntityDeleteEvent, + mut query: PreparedQuery>, + mut world: PreparedWorld, +) { + // If entity had chunk holds, remove them all + if let Ok(holder_comp) = query.find(event.entity, &mut world) { + debug!("Removing chunk holds for entity {:?}", event.entity); + holder_comp.holds.iter().for_each(|chunk| { + holders.remove_holder(*chunk, event.entity, &mut release_events); + }); } } @@ -377,121 +289,29 @@ const CHUNK_OPTIMIZE_INTERVAL: u64 = TPS * 60 * 5; // 5 minutes /// For optimal performance, this system is fully /// concurrent - each chunk optimization is split /// into a separate job and fed into `rayon`. -pub struct ChunkOptimizeSystem; - -impl<'a> System<'a> for ChunkOptimizeSystem { - type SystemData = (Write<'a, ChunkMap>, Read<'a, TickCount>); - - fn run(&mut self, data: Self::SystemData) { - let (mut chunk_map, tick_count) = data; - - // Only run every CHUNK_OPTIMIZE_INTERVAL ticks - if tick_count.0 % CHUNK_OPTIMIZE_INTERVAL != 0 { - return; - } - - let chunks = chunk_map.chunks_mut(); - - // Don't run if there aren't any chunks loaded - if chunks.is_empty() { - return; - } - - debug!("Optimizing chunks"); - - let start_time = current_time_in_millis(); - let count = AtomicU32::new(0); - - chunks.par_iter_mut().for_each(|(_, chunk)| { - count.fetch_add(chunk.optimize(), Ordering::SeqCst); - }); - - let end_time = current_time_in_millis(); - let elapsed = end_time - start_time; - - debug!( - "Optimized {} chunk sections (took {}ms - {:.2}ms/section)", - count.load(Ordering::SeqCst), - elapsed, - elapsed as f64 / f64::from(count.load(Ordering::SeqCst)) - ); +#[system] +fn chunk_optimize(state: &State, tick_count: &TickCount) { + // Only run every CHUNK_OPTIMIZE_INTERVAL ticks + if tick_count.0 % CHUNK_OPTIMIZE_INTERVAL != 0 { + return; } -} -pub fn init_logic(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(ChunkLoadSystem, CHUNK_LOAD, &[]); - dispatcher.add(ChunkOptimizeSystem, CHUNK_OPTIMIZE, &[]); -} - -pub fn init_handlers(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(ChunkUnloadSystem::default(), CHUNK_UNLOAD, &[]); - dispatcher.add(ChunkHoldRemoveSystem::default(), CHUNK_HOLD_REMOVE, &[]); -} + debug!("Optimizing chunks"); -#[cfg(test)] -mod tests { - use specs::{RunNow, World, WorldExt}; + let start_time = current_time_in_millis(); + let count = AtomicU32::new(0); - use feather_core::world::chunk::Chunk; - use feather_core::world::ChunkPosition; + state.chunk_map.par_iter_chunks().for_each(|chunk| { + count.fetch_add(chunk.write().optimize(), Ordering::Relaxed); + }); - use super::*; - - #[test] - fn test_chunk_system() { - let (send1, _recv1) = crossbeam::channel::unbounded(); - let (send2, recv2) = crossbeam::channel::unbounded(); - let handle = ChunkWorkerHandle { - sender: send1, - receiver: recv2, - }; + let end_time = current_time_in_millis(); + let elapsed = end_time - start_time; - let chunk_map = ChunkMap::new(); - let pos = ChunkPosition::new(0, 0); - send2 - .send(chunk_worker::Reply::LoadedChunk( - pos, - Ok((Chunk::new(pos), vec![])), - )) - .unwrap(); - - let load_event_channel = EventChannel::::new(); - let fail_event_channel = EventChannel::::new(); - - let mut system = ChunkLoadSystem; - let mut world = World::new(); - world.insert(chunk_map); - world.insert(handle); - world.insert(load_event_channel); - world.insert(fail_event_channel); - - system.run_now(&world); - - // Confirm that chunk was loaded - let chunk_map = world.read_resource::(); - let chunk = chunk_map.chunk_at(pos); - - assert!(chunk.is_some()); - assert!(chunk.unwrap().position() == pos); - } - - #[test] - fn test_load_chunk() { - let (send1, recv1) = crossbeam::channel::unbounded(); - let (_send2, recv2) = crossbeam::channel::unbounded(); - let handle = ChunkWorkerHandle { - sender: send1, - receiver: recv2, - }; - - let pos = ChunkPosition::new(0, 0); - - load_chunk(&handle, pos); - - let recv = recv1.try_recv().unwrap(); - match recv { - chunk_worker::Request::LoadChunk(recv_pos) => assert_eq!(recv_pos, pos), - _ => panic!(), - } - } + debug!( + "Optimized {} chunk sections (took {}ms - {:.2}ms/section)", + count.load(Ordering::Relaxed), + elapsed, + elapsed as f64 / f64::from(count.load(Ordering::Relaxed)) + ); } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 1efc715d0..010a24ae6 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -9,7 +9,7 @@ use parking_lot::Mutex; /// Event triggered when an entity is removed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EntityDeleteEvent { - entity: Entity, + pub(crate) entity: Entity, } /// The velocity of an entity. diff --git a/server/src/state.rs b/server/src/state.rs index 7310db1a2..9f5418ee1 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -20,8 +20,8 @@ use tonks::Scheduler; /// needed to update blocks.) pub struct State { pub config: Arc, + pub chunk_map: ChunkMap, - chunk_map: ChunkMap, lazy: Lazy, } From 93880f34c52ef498b62dde45bc2d2cdc557c62a0 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 15 Nov 2019 18:28:18 -0700 Subject: [PATCH 09/37] Rename ChunkHolderComponent -> ChunkHolder --- server/src/chunk_logic.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index 61ab314ef..78dfcd4c0 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -250,11 +250,11 @@ pub fn chunk_unload_no_holders( /// a hold on, rather than having /// to linear search all chunks (obviously ridiculous). #[derive(Default)] -pub struct ChunkHolderComponent { +pub struct ChunkHolder { pub holds: HashSet, } -impl ChunkHolderComponent { +impl ChunkHolder { pub fn new() -> Self { Self::default() } @@ -265,7 +265,7 @@ impl ChunkHolderComponent { #[event_handler] fn chunk_holder_remove( event: &EntityDeleteEvent, - mut query: PreparedQuery>, + mut query: PreparedQuery>, mut world: PreparedWorld, ) { // If entity had chunk holds, remove them all From fec8ea3a6b8711c49d4edc5d0eb9fe30f3261e77 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 16 Nov 2019 10:56:45 -0700 Subject: [PATCH 10/37] Convert time sending --- Cargo.lock | 28 ++++++--- server/Cargo.toml | 82 +++++++++++++++--------- server/src/player/mod.rs | 6 ++ server/src/time.rs | 132 +++++++-------------------------------- 4 files changed, 98 insertions(+), 150 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67398fad3..36f30de11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -664,7 +664,6 @@ dependencies = [ "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "derive-new 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)", "derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "evmap 7.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -681,8 +680,8 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "legion 0.1.1 (git+https://github.com/TomGillen/legion?rev=2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mojang-api 0.3.0 (git+https://github.com/caelunshun/mojang-api-rs?rev=6525e910ad53953fa16028f0fce74b1a19855733)", - "multimap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mojang-api 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "multimap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "nalgebra 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)", "nalgebra-glm 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "ncollide3d 0.20.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1271,13 +1270,13 @@ dependencies = [ [[package]] name = "mojang-api" -version = "0.3.0" -source = "git+https://github.com/caelunshun/mojang-api-rs?rev=6525e910ad53953fa16028f0fce74b1a19855733#6525e910ad53953fa16028f0fce74b1a19855733" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "reqwest 0.10.0-alpha.0 (git+https://github.com/seanmonstar/reqwest?rev=5b55aee1a9ddf785f82d9086c8befc50db268cb8)", + "reqwest 0.10.0-alpha.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)", "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1297,6 +1296,14 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "multimap" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nalgebra" version = "0.18.1" @@ -1958,8 +1965,8 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.10.0-alpha.0" -source = "git+https://github.com/seanmonstar/reqwest?rev=5b55aee1a9ddf785f82d9086c8befc50db268cb8#5b55aee1a9ddf785f82d9086c8befc50db268cb8" +version = "0.10.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3030,9 +3037,10 @@ dependencies = [ "checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23" "checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125" "checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" -"checksum mojang-api 0.3.0 (git+https://github.com/caelunshun/mojang-api-rs?rev=6525e910ad53953fa16028f0fce74b1a19855733)" = "" +"checksum mojang-api 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f4f301933426b04d4557b52f7715a1e62dd9c43bc64625c645cbb50de70d5692" "checksum mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a785740271256c230f57462d3b83e52f998433a7062fc18f96d5999474a9f915" "checksum multimap 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "de234f818d54830a7103b9be18ad0861d75aeb5e3c89759bc3f9a004cc39cfa3" +"checksum multimap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "97b1a404699e1fa9fa1665e045c1dd23e6663e8e9ff883faa349c90e71aa7ce9" "checksum nalgebra 0.18.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aaa9fddbc34c8c35dd2108515587b8ce0cab396f17977b8c738568e4edb521a2" "checksum nalgebra-glm 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7a4cd007520d46d2ca24002ddf538fce40ea2a34ed0ffcd3e2ae0b6ba18811d3" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" @@ -3105,7 +3113,7 @@ dependencies = [ "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" "checksum regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)" = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum reqwest 0.10.0-alpha.0 (git+https://github.com/seanmonstar/reqwest?rev=5b55aee1a9ddf785f82d9086c8befc50db268cb8)" = "" +"checksum reqwest 0.10.0-alpha.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3d75dbf305ed1eb54d3c8564e3b746012166b40ec0841381df92b50a2052db71" "checksum rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2089e4031214d129e201f8c3c8c2fe97cd7322478a0d1cdf78e7029b0042efdb" "checksum rsa 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6ad8d3632f6745bb671c8637e2aa44015537c5e384789d2ea3235739301ed1e0" "checksum rsa-der 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1170c86c683547fa781a0e39e6e281ebaedd4515be8a806022984f427ea3d44d" diff --git a/server/Cargo.toml b/server/Cargo.toml index b6d700ba3..e548e9861 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -13,63 +13,85 @@ name = "feather-server" path = "src/main.rs" [dependencies] +# Feather crates feather-blocks = { path = "../blocks" } feather-core = { path = "../core" } feather-item-block = { path = "../item_block" } +feather-codegen = { path = "../codegen" } + +# Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "2eab55dc5a3a84fbc8505cc16eb780ef0cdd019b" } tonks = { git = "https://github.com/feather-rs/tonks", rev = "3fcc5368e6ee072c530762fa2b08d020a542a809" } + +# Concurrency/threading crossbeam = "0.7" +rayon = "1.2" +parking_lot = "0.9" +evmap = "7.1" +thread_local = "1.0" + +# Netorking/IO +tokio = "=0.2.0-alpha.6" +tokio-executor = "=0.2.0-alpha.6" +futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] } +bytes = "0.4" +mojang-api = "0.4" + +# Crypto +rsa = "0.1" +rsa-der = "0.2" +# Match RSA git master +num-bigint = { version = "0.4", features = ["rand", "i128", "u64_digit", "prime", "zeroize"], package = "num-bigint-dig" } + +# Hash functions +ahash = "0.2" +fnv = "1.0" +base64 = "0.10" + +# Math and physics +nalgebra-glm = "0.4" +nalgebra = "0.18" +ncollide3d = "0.20" + +# Other data structures +hashbrown = { version = "0.6", features = ["rayon"] } +bitvec = "0.15" +bitflags = "1.2" +heapless = "0.5" +uuid = { version = "0.7", features = ["v4"] } +multimap = "0.7" +smallvec = "0.6" + +# Logging log = "0.4" simple_logger = "1.3" -uuid = { version = "0.7", features = ["v4"] } -derive-new = "0.5" + +# Serialization/deserialization serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" toml = "0.5" -rsa = "0.1" -# Match RSA git master -num-bigint = { version = "0.4", features = ["rand", "i128", "u64_digit", "prime", "zeroize"], package = "num-bigint-dig" } -rsa-der = "0.2" +hematite-nbt = "0.4" + +# RNGs rand = "0.7" rand_xorshift = "0.2" rand-legacy = { path = "../util/rand-legacy" } -bytes = "0.4" -hashbrown = { version = "0.6", features = ["rayon"] } -mojang-api = { git = "https://github.com/caelunshun/mojang-api-rs", rev = "6525e910ad53953fa16028f0fce74b1a19855733" } -multimap = "0.6" -hematite-nbt = "0.4" -rayon = "1.2" + +# Other failure = "0.1" num-derive = "0.3" num-traits = "0.2" -smallvec = "0.6" lazy_static = "1.4" -nalgebra-glm = "0.4" -nalgebra = "0.18" -ncollide3d = "0.20" derive_deref = "1.1" -feather-codegen = { path = "../codegen" } -bitflags = "1.2" -fnv = "1.0" -base64 = "0.10" bumpalo = "2.6" -thread_local = "1.0" -parking_lot = "0.9" -heapless = "0.5" strum = "0.16" simdnoise = "3.1" simdeez = "0.6" -bitvec = "0.15" -tokio = "=0.2.0-alpha.6" -tokio-executor = "=0.2.0-alpha.6" -futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] } humantime-serde = "0.1" ctrlc = "3.1" -evmap = "7.1" -ahash = "0.2" [dev-dependencies] -criterion = "0.3.0" +criterion = "0.3" [[bench]] name = "worldgen" diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index 7d6e26b0b..41887c563 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -11,6 +11,12 @@ use mojang_api::ProfileProperty; #[derive(Debug, Clone)] pub struct ProfileProperties(pub Vec); +/// Event triggered when a player joins. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PlayerJoinEvent { + pub player: Entity, +} + /// Tag used to mark a player. /// /// Note that this is a _tag_, not a component. diff --git a/server/src/time.rs b/server/src/time.rs index 450fc47e6..7333c6b77 100644 --- a/server/src/time.rs +++ b/server/src/time.rs @@ -1,12 +1,10 @@ //! Handles world time. -use crate::joinhandler::PlayerJoinEvent; -use crate::network::{send_packet_to_player, NetworkComponent}; -use crate::systems::{TIME_INCREMENT, TIME_SEND}; +use crate::network::Network; +use crate::player::PlayerJoinEvent; use feather_core::level::LevelData; use feather_core::packet::TimeUpdate; -use shrev::EventChannel; -use specs::{DispatcherBuilder, Read, ReadStorage, ReaderId, System, World, Write}; +use tonks::{PreparedQuery, PreparedWorld, Read}; /// The current time of the world. #[derive(Debug, Clone, Copy, PartialEq, Eq, Deref, DerefMut, Default)] @@ -25,112 +23,26 @@ impl Time { } } -/// Initializes systems for this module. -pub fn init_logic(dispatcher: &mut DispatcherBuilder) { - dispatcher.add(TimeIncrementSystem, TIME_INCREMENT, &[]); - dispatcher.add(TimeSendSystem::default(), TIME_SEND, &[]); -} - -/// Initializes the time for the world, given the -/// level file. -pub fn init_time(world: &mut World, level: &LevelData) { - world.insert(Time(level.time as u64)) -} - /// System for incrementing time each tick. -pub struct TimeIncrementSystem; - -impl<'a> System<'a> for TimeIncrementSystem { - type SystemData = Write<'a, Time>; - - fn run(&mut self, mut time: Self::SystemData) { - time.0 += 1; - } -} - -/// System for sending world time to players -/// upon joining. -/// -/// This system listens to `PlayerJoinEvent`s. -#[derive(Default)] -pub struct TimeSendSystem { - reader: Option>, -} - -impl<'a> System<'a> for TimeSendSystem { - type SystemData = ( - ReadStorage<'a, NetworkComponent>, - Read<'a, EventChannel>, - Read<'a, Time>, - ); - - fn run(&mut self, data: Self::SystemData) { - let (networks, join_events, time) = data; - - for event in join_events.read(self.reader.as_mut().unwrap()) { - let network = networks.get(event.player).unwrap(); - - // Send time to player. - let packet = TimeUpdate { - world_age: time.world_age() as i64, - time_of_day: time.time_of_day() as i64, - }; - - send_packet_to_player(network, packet); - } - } - - setup_impl!(reader); +#[system] +pub fn time_increment(time: &mut Time) { + time.0 += 1; } -#[cfg(test)] -mod tests { - use super::*; - use crate::testframework as t; - use feather_core::{network::cast_packet, PacketType}; - use shrev::EventChannel; - use specs::WorldExt; - - #[test] - fn test_time_init() { - let mut level = LevelData::default(); - let time = 29456; - level.time = time as i64; - - let mut world = World::new(); - init_time(&mut world, &level); - - assert_eq!(*world.fetch::

(&self, packet: P) { + pub fn send

(&self, packet: P) + where + P: Packet, + { self.send_boxed(Box::new(packet)); } diff --git a/server/src/physics/entity.rs b/server/src/physics/entity.rs index 74ff0ebd1..652666693 100644 --- a/server/src/physics/entity.rs +++ b/server/src/physics/entity.rs @@ -33,7 +33,7 @@ fn physics( // Go through entities and update their positions according // to their velocities. - query.par_entities_for_each(world, |(entity, position, velocity, physics)| { + query.par_entities_for_each(world, |(entity, (position, velocity, physics))| { let mut pending_position = position.current + velocity.0; // Check for blocks along path between old position and pending position. diff --git a/server/src/physics/math.rs b/server/src/physics/math.rs index 6a8160b0b..34f846467 100644 --- a/server/src/physics/math.rs +++ b/server/src/physics/math.rs @@ -255,7 +255,7 @@ pub fn nearby_entities( .iter() .copied() .filter(|e| { - let epos = query.find_immutable(e, world).unwrap(); + let epos = query.find_immutable(*e, world).unwrap(); if let Some(epos) = epos { let epos = epos.current; (epos.x - pos.x).abs() <= radius.x @@ -265,7 +265,7 @@ pub fn nearby_entities( false } }) - .for_each(|e| result.push(e)); + .for_each(|e| result.push(*e)); } result From f5048d1f4a0792b0c08445a73305689beb174d7d Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 20 Dec 2019 12:57:59 -0700 Subject: [PATCH 18/37] Reduce error count from 49 to 12! --- Cargo.lock | 105 +++++++++++---- codegen/src/lib.rs | 4 + core/src/network/packet/implementation.rs | 149 +++++++++++++++++++++- core/src/network/packet/mod.rs | 3 + server/Cargo.toml | 5 +- server/src/chunk_entities.rs | 35 ++--- server/src/entity/mod.rs | 23 +++- server/src/lazy.rs | 21 +-- server/src/network.rs | 56 ++++++-- server/src/physics/entity.rs | 16 +-- server/src/state.rs | 2 +- 11 files changed, 343 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7f2be3a7a..4b396461c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -431,6 +431,16 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "csv" version = "1.1.1" @@ -469,6 +479,26 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dashmap" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "dashmap-shard 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "dashmap-shard" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "derivative" version = "1.0.3" @@ -535,14 +565,6 @@ dependencies = [ "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "evmap" -version = "7.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "failure" version = "0.1.5" @@ -673,8 +695,8 @@ dependencies = [ "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "dashmap 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "evmap 7.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "feather-blocks 0.5.0", "feather-codegen 0.5.0", @@ -689,6 +711,7 @@ dependencies = [ "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=cfb31727d9f4c8f069c559c0cdf05f56547fd9d4)", + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mojang-api 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "multimap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -716,7 +739,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -812,7 +835,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1000,6 +1023,14 @@ dependencies = [ "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "hermit-abi" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "http" version = "0.1.18" @@ -1542,9 +1573,10 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1598,6 +1630,15 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.6.2" @@ -1612,6 +1653,19 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot_core" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "paste" version = "0.1.6" @@ -1964,7 +2018,7 @@ dependencies = [ "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -2437,7 +2491,7 @@ dependencies = [ "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", "futures-sink-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-fs 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2473,7 +2527,7 @@ dependencies = [ "futures-core-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", "futures-util-preview 0.3.0-alpha.19 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-sync 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2528,7 +2582,7 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)", "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-codec 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2582,7 +2636,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671#98a5b3a3a68a49027da04fb8f112cdd1e555f671" +source = "git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35#726498c53d4f4ea4814e9935f2df4412b2c01b35" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2599,13 +2653,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671#98a5b3a3a68a49027da04fb8f112cdd1e555f671" +source = "git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35#726498c53d4f4ea4814e9935f2df4412b2c01b35" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3012,10 +3066,13 @@ dependencies = [ "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" "checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" +"checksum dashmap 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb7ebab7705baa489c3f36e494b59bc16edb4fc5b3341921197270a11259d6a" +"checksum dashmap-shard 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c94b718728139bf8d0f822d63e0b65f4ed781562d5c28f6b84d8e1a66938ae70" "checksum derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" "checksum derive-new 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" "checksum derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11554fdb0aa42363a442e0c4278f51c9621e20c1ce3bac51d79e60646f3b8b8f" @@ -3024,7 +3081,6 @@ dependencies = [ "checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" "checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" "checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" -"checksum evmap 7.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4115e31301bb9dafa8ea8549432b14a51e19eecaa9e5c6041f099545e955c225" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" "checksum fixedbitset 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33" @@ -3058,6 +3114,7 @@ dependencies = [ "checksum heapless 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f339aa7d51777fc0af6aa7cbeb277dfc6e6c029cbdeda48d0fbb92c2337f0e69" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hematite-nbt 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "98b407a33bb1715a4cf0276edfe8df52352c55b2a3703c5079adedf398b92932" +"checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" "checksum http 0.1.18 (registry+https://github.com/rust-lang/crates.io-index)" = "372bcb56f939e449117fb0869c2e8fd8753a8223d92a172c6e808cf123a5b6e4" "checksum http-body 0.2.0-alpha.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1f3aef6f3de2bd8585f5b366f3f550b5774500b4764d00cf00f903c95749eec3" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" @@ -3113,14 +3170,16 @@ dependencies = [ "checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" "checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" -"checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76dac5ed2a876980778b8b85f75a71b6cbf0db0b1232ee12f826bccb00d09d72" "checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" "checksum openssl 0.10.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2f372b2b53ce10fb823a337aaa674e3a7d072b957c6264d0f4ff0bd86e657449" "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" +"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" +"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" "checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" @@ -3228,8 +3287,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=98a5b3a3a68a49027da04fb8f112cdd1e555f671)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index ab6f68331..ed4a27415 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -210,6 +210,10 @@ pub fn derive_packet(_item: TokenStream) -> TokenStream { PacketType::#ident } + fn ty_sized() -> PacketType where Self: Sized { + PacketType::#ident + } + fn box_clone(&self) -> Box { Box::new((*self).clone()) } diff --git a/core/src/network/packet/implementation.rs b/core/src/network/packet/implementation.rs index ed9261cfd..6b127460b 100644 --- a/core/src/network/packet/implementation.rs +++ b/core/src/network/packet/implementation.rs @@ -138,6 +138,13 @@ impl Packet for Handshake { PacketType::Handshake } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::Handshake + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -197,6 +204,13 @@ impl Packet for EncryptionResponse { PacketType::EncryptionResponse } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::EncryptionResponse + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -302,6 +316,13 @@ impl Packet for PluginMessageServerbound { PacketType::PluginMessageServerbound } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::PluginMessageServerbound + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -355,6 +376,13 @@ impl Packet for UseEntity { PacketType::UseEntity } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::UseEntity + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -479,6 +507,13 @@ impl Packet for PlayerDigging { PacketType::PlayerDigging } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::PlayerDigging + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -528,6 +563,13 @@ impl Packet for EntityAction { PacketType::EntityAction } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::EntityAction + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -672,6 +714,13 @@ impl Packet for AnimationServerbound { PacketType::AnimationServerbound } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::AnimationServerbound + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -741,6 +790,13 @@ impl Packet for PlayerBlockPlacement { PacketType::PlayerBlockPlacement } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::PlayerBlockPlacement + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -783,6 +839,13 @@ impl Packet for EncryptionRequest { PacketType::EncryptionRequest } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::EncryptionRequest + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -906,6 +969,13 @@ impl Packet for SpawnPlayer { PacketType::SpawnPlayer } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::SpawnPlayer + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -931,6 +1001,13 @@ impl Packet for AnimationClientbound { PacketType::AnimationClientbound } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::AnimationClientbound + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -960,6 +1037,13 @@ impl Packet for Statistics { PacketType::Statistics } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::Statistics + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1037,6 +1121,13 @@ impl Packet for BossBar { PacketType::BossBar } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::BossBar + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1158,6 +1249,13 @@ impl Packet for WindowItems { PacketType::WindowItems } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::WindowItems + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1203,6 +1301,13 @@ impl Packet for PluginMessageClientbound { PacketType::PluginMessageClientbound } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::PluginMessageClientbound + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1277,6 +1382,13 @@ impl Packet for Explosion { PacketType::Explosion } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::Explosion + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1387,6 +1499,13 @@ impl Packet for ChunkData { PacketType::ChunkData } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::ChunkData + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1509,7 +1628,14 @@ impl Packet for CombatEvent { } fn ty(&self) -> PacketType { - unimplemented!() + PacketType::CombatEvent + } + + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::CombatEvent } fn box_clone(&self) -> Box { @@ -1581,6 +1707,13 @@ impl Packet for PlayerInfo { PacketType::PlayerInfo } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::PlayerInfo + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1656,6 +1789,13 @@ impl Packet for DestroyEntities { PacketType::DestroyEntities } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::DestroyEntities + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } @@ -1707,6 +1847,13 @@ impl Packet for PacketEntityMetadata { PacketType::EntityMetadata } + fn ty_sized() -> PacketType + where + Self: Sized, + { + PacketType::EntityMetadata + } + fn box_clone(&self) -> Box { box_clone_impl!(self); } diff --git a/core/src/network/packet/mod.rs b/core/src/network/packet/mod.rs index fd045889f..538d5cb30 100644 --- a/core/src/network/packet/mod.rs +++ b/core/src/network/packet/mod.rs @@ -27,6 +27,9 @@ pub trait Packet: AsAny + IntoAny + Send + Sync + Any { fn read_from(&mut self, buf: &mut Cursor<&[u8]>) -> Result<(), failure::Error>; fn write_to(&self, buf: &mut BytesMut); fn ty(&self) -> PacketType; + fn ty_sized() -> PacketType + where + Self: Sized; /// Returns a clone of this packet in a dynamic box. fn box_clone(&self) -> Box; diff --git a/server/Cargo.toml b/server/Cargo.toml index 753c13985..e9bb4e4e5 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,13 +21,13 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "98a5b3a3a68a49027da04fb8f112cdd1e555f671", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "726498c53d4f4ea4814e9935f2df4412b2c01b35", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" rayon = "1.2" parking_lot = "0.9" -evmap = "7.1" +lock_api = "0.3" thread_local = "1.0" # Netorking/IO @@ -61,6 +61,7 @@ heapless = "0.5" uuid = { version = "0.7", features = ["v4"] } multimap = "0.7" smallvec = "0.6" +dashmap = "2.1" # Logging log = "0.4" diff --git a/server/src/chunk_entities.rs b/server/src/chunk_entities.rs index 63382abb8..30b0b1271 100644 --- a/server/src/chunk_entities.rs +++ b/server/src/chunk_entities.rs @@ -1,11 +1,11 @@ -use ahash::ABuildHasher; -use evmap::shallow_copy::CopyValue; -use evmap::{ReadHandle, ReadHandleFactory, WriteHandle}; +use dashmap::DashMap; use feather_core::ChunkPosition; use legion::entity::Entity; use parking_lot::Mutex; use thread_local::ThreadLocal; +static EMPTY_VEC: Vec = Vec::new(); + /// Stores which entities belong to every given chunk. /// /// This data structure can be used to accelerate certain @@ -15,33 +15,26 @@ use thread_local::ThreadLocal; /// to a player. /// /// This structure is internally stored in `State`, using -/// `evmap` for concurrent map access. +/// `dashmap` for concurrent access. /// /// Do note that the information in this structure is not necessarily up to date, /// although a best effort is made to update the data. #[derive(Resource)] -pub struct ChunkEntities { - writer: Mutex, (), ABuildHasher>>, - factory: ReadHandleFactory, (), ABuildHasher>, - readers: ThreadLocal, (), ABuildHasher>>, -} +pub struct ChunkEntities(DashMap>); impl ChunkEntities { pub fn new() -> Self { - let (reader, writer) = evmap::with_hasher((), ABuildHasher::default()); - Self { - writer, - factory: reader.factory(), - readers: ThreadLocal::new(), - } + Self(DashMap::default()) } + /// Returns a slice of entities in the given chunk. - pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &[CopyValue] { - unimplemented!(); - self.readers - .get_or(|| self.factory.handle()) - .get_and(&chunk, |slice| slice) - .unwrap_or(&[]) + pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &[Entity] { + todo!("implement chunk entities properly"); + if let Some(vec) = self.0.get(&chunk) { + vec.as_slice() + } else { + &EMPTY_VEC + } } } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 13e051a49..524f18038 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -7,6 +7,7 @@ use legion::prelude::Entity; use legion::query::{Read, Write}; use parking_lot::Mutex; use rayon::prelude::*; +use std::ops::{Deref, DerefMut}; use tonks::{PreparedWorld, Query}; /// Event triggered when an entity is removed. @@ -23,9 +24,29 @@ pub struct EntityMoveEvent { } /// The velocity of an entity. -#[derive(Default, Debug, PartialEq, Clone, Copy)] +#[derive(Debug, PartialEq, Clone, Copy)] pub struct Velocity(pub glm::DVec3); +impl Default for Velocity { + fn default() -> Self { + Self(glm::vec3(0.0, 0.0, 0.0)) + } +} + +impl Deref for Velocity { + type Target = glm::DVec3; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Velocity { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + /// The display name of the entity. /// /// Note that unnamed entities do not have this component. diff --git a/server/src/lazy.rs b/server/src/lazy.rs index 1b7c8e583..296fd6717 100644 --- a/server/src/lazy.rs +++ b/server/src/lazy.rs @@ -24,13 +24,13 @@ pub struct Lazy { impl Lazy { /// Lazily executes a closure with world access. - pub fn exec(&self, f: impl LazyFn) { + pub fn exec(&self, f: impl FnOnce(&mut World) + Send + 'static) { self.exec_with_scheduler(move |world, _| f(world)); } /// Lazily executes a closure with world and scheduler (resource) /// access. - pub fn exec_with_scheduler(&self, f: impl LazyFnWithScheduler) { + pub fn exec_with_scheduler(&self, f: impl FnOnce(&mut World, &mut Scheduler) + Send + 'static) { self.queue.push(Action::Exec(Box::new(f))); } @@ -66,24 +66,27 @@ pub struct EntityBuilder<'a> { impl<'a> EntityBuilder<'a> { pub fn with_component(mut self, component: C) -> Self { - self.fns.push(move |world, entity| { - world.add_component(entity, component); - }); + self.fns + .push(Box::new(move |world: &mut World, entity: Entity| { + world.add_component(entity, component); + })); self } pub fn with_tag(mut self, tag: T) -> Self { - self.fns.push(move |world, entity| { - world.add_tag(entity, tag); - }); + self.fns + .push(Box::new(move |world: &mut World, entity: Entity| { + world.add_tag(entity, tag); + })); self } pub fn build(self) { + let fns = self.fns; self.lazy.exec(move |world| { let entity = world.insert((), [()].iter().copied())[0]; - for f in self.fns { + for f in fns { f(world, entity); } }) diff --git a/server/src/network.rs b/server/src/network.rs index fc970d195..fdf4490e1 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -17,13 +17,44 @@ use feather_core::{Packet, PacketType}; use futures::channel::mpsc::UnboundedSender; use legion::entity::Entity; use legion::query::Read; +use lock_api::RawMutex; use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; +use serde::de::value::MapAccessDeserializer; use std::iter; use std::vec::Drain; +use strum::EnumCount; use tonks::{PreparedWorld, Query}; type QueuedPackets = Vec<(Entity, Box)>; +pub struct DrainedPackets<'a, I> { + mutex: &'a parking_lot::RawMutex, + value: I, +} + +impl<'a, I> DrainedPackets<'a, I> { + unsafe fn new(mutex: &'a parking_lot::RawMutex, value: I) -> Self { + Self { mutex, value } + } +} + +impl<'a, I> Iterator for DrainedPackets<'a, I> +where + I: Iterator, +{ + type Item = I::Item; + + fn next(&mut self) -> Option { + self.value.next() + } +} + +impl<'a, I> Drop for DrainedPackets<'a, I> { + fn drop(&mut self) { + self.mutex.unlock(); + } +} + /// The packet queue. This type allows systems to poll for /// received packets of a given type. /// @@ -53,14 +84,19 @@ impl PacketQueue { } /// Returns an iterator over packets of a given type. - pub fn received(&self) -> MappedMutexGuard> { - let queue = self.queue[P::ty().ordinal()].lock(); - - MutexGuard::map(queue, |queue| { - queue - .drain(..) - .map(|(entity, packet)| (entity, cast_packet::

(packet))) - }) + pub fn received(&self) -> impl Iterator + '_ { + let mut queue = self.queue[P::ty_sized().ordinal()].lock(); + + // Hack to map to draining iterator. + unsafe { + let raw = MutexGuard::mutex(&queue).raw(); + DrainedPackets::new( + raw, + queue + .drain(..) + .map(|(entity, packet)| (entity, cast_packet::

(packet))), + ) + } } /// Adds a packet to the queue. @@ -105,7 +141,7 @@ impl Network { /// * Pushing received packets to the packet queue. /// * Accepting new clients and creating entities for them. #[system] -pub fn network( +pub fn network_( state: &State, io: &NetworkIoManager, packet_queue: &PacketQueue, @@ -113,7 +149,7 @@ pub fn network( world: &mut PreparedWorld, ) { // For each `Network`, handle any disconnects and received packets. - query.par_entities_for_each(world, |(entity, network): (Entity, Network)| { + query.par_entities_for_each(world, |(entity, network)| { while let Ok(msg) = network.receiver.try_recv() { match msg { ServerToWorkerMessage::NotifyDisconnect(reason) => { diff --git a/server/src/physics/entity.rs b/server/src/physics/entity.rs index 652666693..fec635ee3 100644 --- a/server/src/physics/entity.rs +++ b/server/src/physics/entity.rs @@ -21,7 +21,7 @@ pub struct EntityPhysicsLandEvent { /// System for updating all entities' positions and velocities /// each tick. #[system] -fn physics( +fn entity_physics( state: &State, query: &mut Query<(Write, Write, Read)>, world: &mut PreparedWorld, @@ -34,14 +34,14 @@ fn physics( // Go through entities and update their positions according // to their velocities. query.par_entities_for_each(world, |(entity, (position, velocity, physics))| { - let mut pending_position = position.current + velocity.0; + let mut pending_position = *position + velocity.0; // Check for blocks along path between old position and pending position. // This prevents entities from flying through blocks when their // velocity is sufficiently high. - let origin = position.current.into(); - let direction = (pending_position - position.current).into(); - let distance_squared = pending_position.distance_squared(position.current); + let origin = (*position).into(); + let direction = (pending_position - *position).into(); + let distance_squared = pending_position.distance_squared(*position); if let Some(impacted) = block_impacted_by_ray(&state, origin, direction, distance_squared) { // Set velocities along correct axis to 0 and then set position @@ -69,7 +69,7 @@ fn physics( // Check for blocks around the bbox and apply offset // to position to stop the bbox from intersecting blocks. let intersect = - blocks_intersecting_bbox(&state, position.current, pending_position, &physics.bbox); + blocks_intersecting_bbox(&state, *position, pending_position, &physics.bbox); intersect.apply_to(&mut pending_position); if intersect.x_affected() { @@ -108,7 +108,7 @@ fn physics( Some(block) => block.is_solid(), None => false, }; - if pending_position.on_ground && !position.current.on_ground { + if pending_position.on_ground && !position.on_ground { land_events.lock().trigger(EntityPhysicsLandEvent { entity, pos: pending_position, @@ -142,6 +142,6 @@ fn physics( } // Set new position. - position.current = pending_position; + *position = pending_position; }); } diff --git a/server/src/state.rs b/server/src/state.rs index 53c69a542..5eb2afa7b 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -36,7 +36,7 @@ impl State { } /// See `Lazy::exec()`. - pub fn exec(&self, f: impl LazyFn) { + pub fn exec(&self, f: impl FnOnce(&mut World) + Send + 'static) { self.lazy.exec(f) } From 798f09b282c7aa5ced7df8f6eebaf24df634199d Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 20 Dec 2019 15:39:18 -0700 Subject: [PATCH 19/37] Fix unused imports; fix 6 more errors' --- Cargo.lock | 12 ++++++------ server/Cargo.toml | 2 +- server/src/chunk_entities.rs | 7 ------- server/src/chunk_logic.rs | 9 +++------ server/src/entity/mod.rs | 11 ++++++----- server/src/lib.rs | 12 +++--------- server/src/network.rs | 6 +----- server/src/physics/math.rs | 11 ++++------- server/src/state.rs | 2 +- server/src/time.rs | 5 ++--- server/src/view.rs | 8 +++++--- 11 files changed, 32 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4b396461c..743e495e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,7 +739,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2636,7 +2636,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35#726498c53d4f4ea4814e9935f2df4412b2c01b35" +source = "git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464#066ec3001870596ee45524549985610b1a11f464" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2653,13 +2653,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35#726498c53d4f4ea4814e9935f2df4412b2c01b35" +source = "git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464#066ec3001870596ee45524549985610b1a11f464" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3287,8 +3287,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=726498c53d4f4ea4814e9935f2df4412b2c01b35)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/server/Cargo.toml b/server/Cargo.toml index e9bb4e4e5..b3587c75f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,7 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "726498c53d4f4ea4814e9935f2df4412b2c01b35", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "066ec3001870596ee45524549985610b1a11f464", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" diff --git a/server/src/chunk_entities.rs b/server/src/chunk_entities.rs index 30b0b1271..3cf9399dc 100644 --- a/server/src/chunk_entities.rs +++ b/server/src/chunk_entities.rs @@ -1,8 +1,6 @@ use dashmap::DashMap; use feather_core::ChunkPosition; use legion::entity::Entity; -use parking_lot::Mutex; -use thread_local::ThreadLocal; static EMPTY_VEC: Vec = Vec::new(); @@ -30,11 +28,6 @@ impl ChunkEntities { /// Returns a slice of entities in the given chunk. pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &[Entity] { todo!("implement chunk entities properly"); - if let Some(vec) = self.0.get(&chunk) { - vec.as_slice() - } else { - &EMPTY_VEC - } } } diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index aec7f344d..e28d8acba 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -5,14 +5,12 @@ use crossbeam::channel::{Receiver, Sender}; use std::sync::atomic::{AtomicU32, Ordering}; -use feather_core::world::{ChunkMap, ChunkPosition}; +use feather_core::world::ChunkPosition; use rayon::prelude::*; -use crate::config::Config; use crate::entity::EntityDeleteEvent; use crate::state::State; -use crate::worldgen::WorldGenerator; use crate::{chunk_worker, current_time_in_millis, TickCount, TPS}; use feather_core::entity::EntityData; use feather_core::Chunk; @@ -21,7 +19,6 @@ use legion::entity::Entity; use legion::query::Read; use multimap::MultiMap; use std::collections::VecDeque; -use std::path::Path; use std::sync::Arc; use tonks::{PreparedWorld, Query, Trigger}; @@ -272,13 +269,13 @@ impl ChunkHolder { #[event_handler] fn chunk_holder_remove( event: &EntityDeleteEvent, - query: &mut Query>, + _query: &mut Query>, world: &mut PreparedWorld, holders: &mut ChunkHolders, release_events: &mut Trigger, ) { // If entity had chunk holds, remove them all - if let Ok(holder_comp) = query.find(event.entity, world) { + if let Some(holder_comp) = world.get_component::(event.entity) { debug!("Removing chunk holds for entity {:?}", event.entity); holder_comp.holds.iter().for_each(|chunk| { holders.remove_holder(*chunk, event.entity, release_events); diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 524f18038..4c1240c29 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -2,11 +2,9 @@ use crate::lazy::EntityBuilder; use crate::state::State; -use feather_core::{ChunkPosition, Position}; +use feather_core::Position; use legion::prelude::Entity; use legion::query::{Read, Write}; -use parking_lot::Mutex; -use rayon::prelude::*; use std::ops::{Deref, DerefMut}; use tonks::{PreparedWorld, Query}; @@ -62,11 +60,14 @@ pub struct PreviousPosition(pub Position); #[event_handler] pub fn position_reset( events: &[EntityMoveEvent], - query: &mut Query<(Read, Write)>, + _query: &mut Query<(Read, Write)>, world: &mut PreparedWorld, ) { events.iter().for_each(|event| { - let (pos, mut prev_pos) = query.find(event.entity).unwrap(); + let pos = *world.get_component::(event.entity).unwrap(); + let prev_pos = world + .get_component_mut::(event.entity) + .unwrap(); prev_pos.0 = pos; }); diff --git a/server/src/lib.rs b/server/src/lib.rs index 60f9402ea..82f0054cb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -100,33 +100,27 @@ extern crate serde_json; #[macro_use] extern crate failure; #[macro_use] -extern crate num_derive; -#[macro_use] extern crate smallvec; #[macro_use] extern crate lazy_static; #[macro_use] extern crate derive_deref; #[macro_use] -extern crate feather_codegen; +extern crate feather_core; #[macro_use] extern crate bitflags; #[macro_use] -extern crate feather_core; -#[macro_use] extern crate tonks; extern crate nalgebra_glm as glm; use crossbeam::Receiver; use std::alloc::System; -use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::atomic::AtomicUsize; use std::sync::Arc; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use feather_core::network::packet::implementation::DisconnectPlay; - -use crate::chunk_logic::{ChunkHolders, ChunkWorkerHandle}; +use crate::chunk_logic::ChunkWorkerHandle; use crate::config::Config; use crate::state::State; use crate::worldgen::{ diff --git a/server/src/network.rs b/server/src/network.rs index fdf4490e1..71b3fe53d 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -7,9 +7,7 @@ //! received of a given type. use crate::io::{ListenerToServerMessage, NetworkIoManager, ServerToWorkerMessage}; -use crate::lazy::Lazy; use crate::player; -use crate::player::Player; use crate::state::State; use crossbeam::Receiver; use feather_core::network::cast_packet; @@ -18,10 +16,8 @@ use futures::channel::mpsc::UnboundedSender; use legion::entity::Entity; use legion::query::Read; use lock_api::RawMutex; -use parking_lot::{MappedMutexGuard, Mutex, MutexGuard}; -use serde::de::value::MapAccessDeserializer; +use parking_lot::{Mutex, MutexGuard}; use std::iter; -use std::vec::Drain; use strum::EnumCount; use tonks::{PreparedWorld, Query}; diff --git a/server/src/physics/math.rs b/server/src/physics/math.rs index 34f846467..bae308495 100644 --- a/server/src/physics/math.rs +++ b/server/src/physics/math.rs @@ -6,12 +6,11 @@ use crate::physics::block_bboxes::bbox_for_block; use crate::physics::AABBExt; use crate::state::State; use feather_blocks::Block; -use feather_core::world::{BlockPosition, ChunkMap, Position}; +use feather_core::world::{BlockPosition, Position}; use feather_core::{BlockExt, ChunkPosition}; use glm::{vec3, DVec3, Vec3}; use heapless::consts::*; use legion::entity::Entity; -use legion::query::Read; use nalgebra::{Isometry3, Point3}; use ncollide3d::bounding_volume::AABB; use ncollide3d::query; @@ -20,7 +19,7 @@ use ncollide3d::shape::{Compound, Cuboid, ShapeHandle}; use smallvec::SmallVec; use std::cmp::Ordering; use std::f64::INFINITY; -use tonks::{PreparedWorld, Query}; +use tonks::PreparedWorld; // TODO is a bitflag really the most // idiomatic way to do this? @@ -238,7 +237,6 @@ pub fn block_impacted_by_ray( /// Panics if either coordinate of the radius is negative. pub fn nearby_entities( chunk_entities: &ChunkEntities, - query: &Query>, world: &PreparedWorld, pos: Position, radius: DVec3, @@ -255,9 +253,8 @@ pub fn nearby_entities( .iter() .copied() .filter(|e| { - let epos = query.find_immutable(*e, world).unwrap(); + let epos = world.get_component::(*e); if let Some(epos) = epos { - let epos = epos.current; (epos.x - pos.x).abs() <= radius.x && (epos.y - pos.y).abs() <= radius.y && (epos.z - pos.z).abs() <= radius.z @@ -265,7 +262,7 @@ pub fn nearby_entities( false } }) - .for_each(|e| result.push(*e)); + .for_each(|e| result.push(e)); } result diff --git a/server/src/state.rs b/server/src/state.rs index 5eb2afa7b..ae74abe54 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,5 +1,5 @@ use crate::config::Config; -use crate::lazy::{EntityBuilder, Lazy, LazyFn}; +use crate::lazy::{EntityBuilder, Lazy}; use feather_blocks::Block; use feather_core::world::ChunkMap; use feather_core::{BlockPosition, Chunk, ChunkPosition}; diff --git a/server/src/time.rs b/server/src/time.rs index 5190d76e5..de0d503a8 100644 --- a/server/src/time.rs +++ b/server/src/time.rs @@ -2,7 +2,6 @@ use crate::network::Network; use crate::player::PlayerJoinEvent; -use feather_core::level::LevelData; use feather_core::packet::TimeUpdate; use legion::query::Read; use tonks::{PreparedWorld, Query}; @@ -35,10 +34,10 @@ pub fn time_increment(time: &mut Time) { pub fn time_send( time: &Time, event: &PlayerJoinEvent, - query: &mut Query>, + _query: &mut Query>, world: &mut PreparedWorld, ) { - let network = query.find(event.player, &mut world).unwrap(); + let network = world.get_component::(event.player).unwrap(); // Send time to player. let packet = TimeUpdate { diff --git a/server/src/view.rs b/server/src/view.rs index c62161983..85fe39a6b 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -18,11 +18,9 @@ //! This includes systems to load/unload chunks and send entities. use crate::entity::{EntityMoveEvent, PreviousPosition}; -use crate::player::Player; use feather_core::{ChunkPosition, Position}; use legion::entity::Entity; use legion::query::Read; -use rayon::prelude::*; use tonks::{PreparedWorld, Query, Trigger}; /// Event triggered when a player's view is updated, i.e. when they @@ -46,7 +44,11 @@ fn view_update( trigger: &mut Trigger, ) { events.iter().for_each(|event| { - let (pos, prev_pos) = query.find_immutable(event.entity, &world).unwrap(); + let pos = *world.get_component::(event.entity).unwrap(); + let prev_pos = world + .get_component::(event.entity) + .unwrap() + .0; if pos.chunk_pos() != prev_pos.chunk_pos() { // New chunk: trigger view update. From e3d405f34973b8cbef2ab855828b927c16995194 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 20 Dec 2019 16:05:11 -0700 Subject: [PATCH 20/37] Reimplement player data loading --- core/src/save/player_data.rs | 14 +++++++++----- server/src/io/mod.rs | 4 ++++ server/src/io/worker.rs | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/core/src/save/player_data.rs b/core/src/save/player_data.rs index ca331ce2c..30bf8d81c 100644 --- a/core/src/save/player_data.rs +++ b/core/src/save/player_data.rs @@ -10,6 +10,8 @@ use feather_items::Item; use std::fs; use std::io::{Read, Write}; use std::path::{Path, PathBuf}; +use tokio::io::AsyncReadExt; +use tokio::prelude::AsyncRead; use uuid::Uuid; /// Represents the contents of a player data file. @@ -92,14 +94,16 @@ impl InventorySlot { } } -fn load_from_file(reader: R) -> Result { - nbt::from_gzip_reader::<_, PlayerData>(reader) +async fn load_from_file(mut reader: R) -> Result { + let mut buf = vec![]; + reader.read(&mut buf).await?; + nbt::from_gzip_reader(buf.as_slice()) } -pub fn load_player_data(world_dir: &Path, uuid: Uuid) -> Result { +pub async fn load_player_data(world_dir: &Path, uuid: Uuid) -> Result { let file_path = file_path(world_dir, uuid); - let file = File::open(file_path)?; - let data = load_from_file(file)?; + let file = tokio::fs::File::open(file_path).await?; + let data = load_from_file(file).await?; Ok(data) } diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index db57a4320..291d49ab3 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -1,6 +1,8 @@ use crate::config::Config; use crate::PlayerCount; use feather_core::network::packet::Packet; +use feather_core::player_data::PlayerData; +use feather_core::Position; use std::net::SocketAddr; use std::sync::Arc; use uuid::Uuid; @@ -28,6 +30,8 @@ pub struct NewClientInfo { pub username: String, pub profile: Vec, pub uuid: Uuid, + pub data: PlayerData, + pub position: Position, pub sender: futures::channel::mpsc::UnboundedSender, pub receiver: crossbeam::Receiver, diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index d9a966f04..5963f2614 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -12,14 +12,23 @@ use crate::io::{ListenerToServerMessage, NewClientInfo, ServerToWorkerMessage}; use crate::PlayerCount; use feather_core::network::codec::MinecraftCodec; use feather_core::network::packet::PacketDirection; +use feather_core::player_data::PlayerData; use futures::{select, StreamExt}; use futures::{FutureExt, SinkExt}; use std::net::SocketAddr; +use std::path::Path; use std::sync::Arc; use std::time::Duration; use tokio::codec::Framed; use tokio::net::TcpStream; use tokio::timer::Timeout; +use uuid::Uuid; + +#[derive(Debug, Fail)] +pub enum Error { + #[fail(display = "failed to read player data")] + PlayerData, +} /// Runs a worker task for the given client. pub async fn run_worker( @@ -119,6 +128,7 @@ async fn _run_worker( } Action::SetStage(stage) => framed.codec_mut().set_stage(stage), Action::JoinGame(res) => { + let data = load_player_data(&config, res.uuid).await?; let info = NewClientInfo { ip, username: res.username, @@ -126,6 +136,11 @@ async fn _run_worker( uuid: res.uuid, sender: tx_server_to_worker.clone(), receiver: rx_worker_to_server.take().unwrap(), + position: data + .entity + .read_position() + .ok_or_else(|| Error::PlayerData)?, + data, }; global_sender .send(ListenerToServerMessage::NewClient(info))?; @@ -144,3 +159,7 @@ async fn _run_worker( } } } + +async fn load_player_data(config: &Config, uuid: Uuid) -> Result { + feather_core::player_data::load_player_data(Path::new(&config.world.name), uuid).await +} From d751bc4fda3173f5a6c60c13308cd9eb1386026f Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 20 Dec 2019 17:59:46 -0700 Subject: [PATCH 21/37] Fix ALL errors and warnings. I'm scared to run this... --- Cargo.lock | 12 +++++------ core/src/save/player_data.rs | 2 +- server/Cargo.toml | 2 +- server/src/chunk_entities.rs | 4 +--- server/src/entity/mod.rs | 2 +- server/src/io/worker.rs | 6 +++++- server/src/lib.rs | 9 +++++--- server/src/network.rs | 42 ++++++++++++++++++++++++++++++------ server/src/physics/entity.rs | 2 +- server/src/shutdown.rs | 6 +++--- server/src/state.rs | 8 +++---- server/src/time.rs | 2 +- server/src/view.rs | 2 +- 13 files changed, 66 insertions(+), 33 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 743e495e7..64d7e2cd9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,7 +739,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2636,7 +2636,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464#066ec3001870596ee45524549985610b1a11f464" +source = "git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658#a090d8631232751f97f07d4ee7e15d1028afb658" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2653,13 +2653,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464#066ec3001870596ee45524549985610b1a11f464" +source = "git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658#a090d8631232751f97f07d4ee7e15d1028afb658" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3287,8 +3287,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=066ec3001870596ee45524549985610b1a11f464)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/core/src/save/player_data.rs b/core/src/save/player_data.rs index 30bf8d81c..d9cae5f0b 100644 --- a/core/src/save/player_data.rs +++ b/core/src/save/player_data.rs @@ -8,7 +8,7 @@ use crate::inventory::{ use crate::ItemStack; use feather_items::Item; use std::fs; -use std::io::{Read, Write}; +use std::io::Write; use std::path::{Path, PathBuf}; use tokio::io::AsyncReadExt; use tokio::prelude::AsyncRead; diff --git a/server/Cargo.toml b/server/Cargo.toml index b3587c75f..56a597c36 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,7 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "066ec3001870596ee45524549985610b1a11f464", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "a090d8631232751f97f07d4ee7e15d1028afb658", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" diff --git a/server/src/chunk_entities.rs b/server/src/chunk_entities.rs index 3cf9399dc..8edd8831c 100644 --- a/server/src/chunk_entities.rs +++ b/server/src/chunk_entities.rs @@ -2,8 +2,6 @@ use dashmap::DashMap; use feather_core::ChunkPosition; use legion::entity::Entity; -static EMPTY_VEC: Vec = Vec::new(); - /// Stores which entities belong to every given chunk. /// /// This data structure can be used to accelerate certain @@ -26,7 +24,7 @@ impl ChunkEntities { } /// Returns a slice of entities in the given chunk. - pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> &[Entity] { + pub fn entities_in_chunk(&self, _chunk: ChunkPosition) -> &[Entity] { todo!("implement chunk entities properly"); } } diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 4c1240c29..124c814d2 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -65,7 +65,7 @@ pub fn position_reset( ) { events.iter().for_each(|event| { let pos = *world.get_component::(event.entity).unwrap(); - let prev_pos = world + let mut prev_pos = world .get_component_mut::(event.entity) .unwrap(); diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 5963f2614..8978cd0bc 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -75,7 +75,11 @@ async fn _run_worker( let mut framed = Framed::new(stream, codec); - let mut initial_handler = Some(InitialHandler::new(config, player_count, server_icon)); + let mut initial_handler = Some(InitialHandler::new( + Arc::clone(&config), + player_count, + server_icon, + )); let (tx_server_to_worker, mut rx_server_to_worker) = futures::channel::mpsc::unbounded(); let mut rx_worker_to_server = Some(rx_worker_to_server); diff --git a/server/src/lib.rs b/server/src/lib.rs index 82f0054cb..f892fa705 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -122,6 +122,7 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use crate::chunk_logic::ChunkWorkerHandle; use crate::config::Config; +use crate::io::NetworkIoManager; use crate::state::State; use crate::worldgen::{ ComposableGenerator, EmptyWorldGenerator, SuperflatWorldGenerator, WorldGenerator, @@ -209,9 +210,9 @@ pub fn main() { exit(1) }); - let chunk_worker_handle = init_chunk_worker(&config, world_dir, &level); + let chunk_worker_handle = init_chunk_worker(world_dir, &level); - let mut scheduler = init_scheduler(Arc::clone(&config), chunk_worker_handle, level); + let mut scheduler = init_scheduler(Arc::clone(&config), chunk_worker_handle, level, io_manager); let mut world = World::new(); // Channel used by the shutdown handler to notify the server thread. @@ -280,6 +281,7 @@ fn init_scheduler( config: Arc, chunk_worker_handle: ChunkWorkerHandle, level: LevelData, + io_manager: NetworkIoManager, ) -> Scheduler { // Insert resources which don't have a `Default` impl. let mut resources = Resources::new(); @@ -287,12 +289,13 @@ fn init_scheduler( resources.insert(State::new(config, chunk_map)); resources.insert(chunk_worker_handle); resources.insert(level); + resources.insert(io_manager); tonks::build_scheduler().build(Resources::default()) } /// Initializes the chunk worker. -fn init_chunk_worker(config: &Config, world_dir: &Path, level: &LevelData) -> ChunkWorkerHandle { +fn init_chunk_worker(world_dir: &Path, level: &LevelData) -> ChunkWorkerHandle { let generator: Arc = match level.generator_type() { LevelGeneratorType::Flat => Arc::new(SuperflatWorldGenerator { options: level.clone().generator_options.unwrap_or_default(), diff --git a/server/src/network.rs b/server/src/network.rs index 71b3fe53d..b2d9dd7fd 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -23,6 +23,26 @@ use tonks::{PreparedWorld, Query}; type QueuedPackets = Vec<(Entity, Box)>; +struct UnsafeDrain { + ptr: *const T, + len: usize, + pos: usize, +} + +impl Iterator for UnsafeDrain { + type Item = T; + + fn next(&mut self) -> Option { + if self.pos == self.len { + return None; + } + + let value = unsafe { std::ptr::read(self.ptr.offset(self.pos as isize)) }; + self.pos += 1; + Some(value) + } +} + pub struct DrainedPackets<'a, I> { mutex: &'a parking_lot::RawMutex, value: I, @@ -86,12 +106,20 @@ impl PacketQueue { // Hack to map to draining iterator. unsafe { let raw = MutexGuard::mutex(&queue).raw(); - DrainedPackets::new( - raw, - queue - .drain(..) - .map(|(entity, packet)| (entity, cast_packet::

(packet))), - ) + + let drain = UnsafeDrain { + ptr: queue.as_ptr(), + len: queue.len(), + pos: 0, + }; + + // Safety: the vector cannot be accessed as long as the returned `UnsafeDrain` + // has not been dropped, since the mutex is acquired. + queue.set_len(0); + + let iter = drain.map(|(entity, packet)| (entity, cast_packet::

(packet))); + + DrainedPackets::new(raw, iter) } } @@ -148,7 +176,7 @@ pub fn network_( query.par_entities_for_each(world, |(entity, network)| { while let Ok(msg) = network.receiver.try_recv() { match msg { - ServerToWorkerMessage::NotifyDisconnect(reason) => { + ServerToWorkerMessage::NotifyDisconnect(_) => { state.exec(move |world| { debug_assert!(world.delete(entity), "player already deleted"); }); diff --git a/server/src/physics/entity.rs b/server/src/physics/entity.rs index fec635ee3..4a372e4d6 100644 --- a/server/src/physics/entity.rs +++ b/server/src/physics/entity.rs @@ -33,7 +33,7 @@ fn entity_physics( // Go through entities and update their positions according // to their velocities. - query.par_entities_for_each(world, |(entity, (position, velocity, physics))| { + query.par_entities_for_each(world, |(entity, (mut position, mut velocity, physics))| { let mut pending_position = *position + velocity.0; // Check for blocks along path between old position and pending position. diff --git a/server/src/shutdown.rs b/server/src/shutdown.rs index 56da66895..53ba1f5e2 100644 --- a/server/src/shutdown.rs +++ b/server/src/shutdown.rs @@ -9,14 +9,14 @@ pub fn init(tx: Sender<()>) { .unwrap(); } -pub fn save_chunks(world: &mut World) { +pub fn save_chunks(_world: &mut World) { unimplemented!() } -pub fn save_level(world: &World) { +pub fn save_level(_world: &World) { unimplemented!() } -pub fn save_player_data(world: &World) { +pub fn save_player_data(_world: &World) { unimplemented!() } diff --git a/server/src/state.rs b/server/src/state.rs index ae74abe54..264b4fa06 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -72,10 +72,10 @@ impl State { /// Lazily inserts the given chunk into the chunk map. pub fn lazy_insert_chunk(&self, chunk: Chunk) { - self.lazy.exec_with_scheduler(move |_, scheduler| { + self.lazy.exec_with_scheduler(move |_, scheduler| unsafe { scheduler .resources() - .get_mut::() + .get_mut_unchecked::(tonks::resource_id_for::()) .chunk_map .insert(chunk); }); @@ -84,10 +84,10 @@ impl State { /// Lazily removes the given chunk from the chunk map. pub fn lazy_remove_chunk(&self, pos: ChunkPosition) { self.lazy - .exec_with_scheduler(move |_: &mut World, scheduler: &mut Scheduler| { + .exec_with_scheduler(move |_: &mut World, scheduler: &mut Scheduler| unsafe { scheduler .resources() - .get_mut::() + .get_mut_unchecked::(tonks::resource_id_for::()) .chunk_map .remove(pos); }); diff --git a/server/src/time.rs b/server/src/time.rs index de0d503a8..8b591271c 100644 --- a/server/src/time.rs +++ b/server/src/time.rs @@ -32,8 +32,8 @@ pub fn time_increment(time: &mut Time) { /// Event handler for sending world time to players. #[event_handler] pub fn time_send( - time: &Time, event: &PlayerJoinEvent, + time: &Time, _query: &mut Query>, world: &mut PreparedWorld, ) { diff --git a/server/src/view.rs b/server/src/view.rs index 85fe39a6b..a45b37dd0 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -39,7 +39,7 @@ pub struct ViewUpdateEvent { #[event_handler] fn view_update( events: &[EntityMoveEvent], - query: &mut Query<(Read, Read)>, + _query: &mut Query<(Read, Read)>, world: &mut PreparedWorld, trigger: &mut Trigger, ) { From de0f3ffb94f74af09a27d86c680c3866a99dd6e2 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Fri, 20 Dec 2019 18:52:57 -0700 Subject: [PATCH 22/37] Get server to start without panic --- Cargo.lock | 12 ++++++------ Cargo.toml | 2 +- server/Cargo.toml | 3 ++- server/src/io/mod.rs | 6 ------ server/src/lib.rs | 8 +++++++- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64d7e2cd9..c2c4cde1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,7 +739,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2636,7 +2636,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658#a090d8631232751f97f07d4ee7e15d1028afb658" +source = "git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b#226d04de294008a1f615bae40f4d19a890583f0b" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2653,13 +2653,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658#a090d8631232751f97f07d4ee7e15d1028afb658" +source = "git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b#226d04de294008a1f615bae40f4d19a890583f0b" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3287,8 +3287,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=a090d8631232751f97f07d4ee7e15d1028afb658)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/Cargo.toml b/Cargo.toml index 4c9bd66dd..5fa8c4a44 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,4 @@ members = [ "codegen", "generator", "util/rand-legacy", -] \ No newline at end of file +] diff --git a/server/Cargo.toml b/server/Cargo.toml index 56a597c36..5760f6455 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,8 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "a090d8631232751f97f07d4ee7e15d1028afb658", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "226d04de294008a1f615bae40f4d19a890583f0b", features = ["system-registry"] } +# tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" diff --git a/server/src/io/mod.rs b/server/src/io/mod.rs index 291d49ab3..fd506d6d8 100644 --- a/server/src/io/mod.rs +++ b/server/src/io/mod.rs @@ -72,12 +72,6 @@ impl NetworkIoManager { } } -impl Default for NetworkIoManager { - fn default() -> Self { - panic!("Don't try this"); - } -} - /// Initializes certain static variables. pub fn init() { lazy_static::initialize(&initial_handler::RSA_KEY); diff --git a/server/src/lib.rs b/server/src/lib.rs index f892fa705..6f5b0ebcb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -170,6 +170,12 @@ pub struct PlayerCount(AtomicUsize); #[derive(Default, Debug, Resource)] pub struct TickCount(u64); +/// System to increment tick count each tick. +#[system] +fn tick_count_increment(tick: &mut TickCount) { + tick.0 += 1; +} + pub fn main() { let config = Arc::new(load_config()); init_log(&config); @@ -291,7 +297,7 @@ fn init_scheduler( resources.insert(level); resources.insert(io_manager); - tonks::build_scheduler().build(Resources::default()) + tonks::build_scheduler().build(resources) } /// Initializes the chunk worker. From 74bb4f07e5bd93f6bc4590b43d4f1b097b148929 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 12:55:31 -0700 Subject: [PATCH 23/37] Work on getting chunk sending + view updates to work --- .gitignore | 3 +- Cargo.lock | 8 +- core/src/world/mod.rs | 11 ++ server/Cargo.toml | 4 +- server/src/chunk_logic.rs | 79 +++++++++++---- server/src/io/worker.rs | 3 +- server/src/lazy.rs | 31 ++++-- server/src/lib.rs | 6 ++ server/src/network.rs | 1 + server/src/player/mod.rs | 5 + server/src/state.rs | 5 + server/src/view.rs | 207 ++++++++++++++++++++++++++++++++++++-- 12 files changed, 313 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index c132a9511..85d7f7da6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ **/*.rs.bk **/.idea feather.toml -massif.out* \ No newline at end of file +massif.out* +.cargo diff --git a/Cargo.lock b/Cargo.lock index c2c4cde1f..5c2c76dea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -739,7 +739,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)", + "tonks 0.1.0", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2636,7 +2636,6 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b#226d04de294008a1f615bae40f4d19a890583f0b" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2653,13 +2652,12 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)", + "tonks-macros 0.1.0", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b#226d04de294008a1f615bae40f4d19a890583f0b" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3287,8 +3285,6 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=226d04de294008a1f615bae40f4d19a890583f0b)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/core/src/world/mod.rs b/core/src/world/mod.rs index a13c125ec..2192e447d 100644 --- a/core/src/world/mod.rs +++ b/core/src/world/mod.rs @@ -222,6 +222,17 @@ impl Display for ChunkPosition { } } +impl Add for ChunkPosition { + type Output = ChunkPosition; + + fn add(self, rhs: ChunkPosition) -> Self::Output { + ChunkPosition { + x: self.x + rhs.x, + z: self.z + rhs.z, + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Hash32, Default, new)] pub struct BlockPosition { pub x: i32, diff --git a/server/Cargo.toml b/server/Cargo.toml index 5760f6455..42dc10294 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,8 +21,8 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "226d04de294008a1f615bae40f4d19a890583f0b", features = ["system-registry"] } -# tonks = { path = "../../../dev/tonks", features = ["system-registry"] } +# tonks = { git = "https://github.com/feather-rs/tonks", rev = "226d04de294008a1f615bae40f4d19a890583f0b", features = ["system-registry"] } +tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index e28d8acba..2c4ac1115 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -73,28 +73,6 @@ fn chunk_load_system( } } -/// Asynchronously loads the chunk at the given position. -/// At some point in time after this function is called, -/// the chunk will appear in the chunk map. -/// -/// In the event that the requested chunk does not exist -/// in the world save, it will be generated asynchronously. -pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { - // Send request to chunk worker thread - handle - .sender - .send(chunk_worker::Request::LoadChunk(pos)) - .unwrap(); -} - -/// Asynchronously saves the chunk at the given position. -pub fn save_chunk(handle: &ChunkWorkerHandle, chunk: Arc, entities: Vec) { - handle - .sender - .send(chunk_worker::Request::SaveChunk(chunk, entities)) - .unwrap(); -} - /// The chunk holder map contains a mapping /// of chunk positions to any number of entities, called "holders." /// When a chunk position has no holders, it will be queued @@ -321,3 +299,60 @@ fn chunk_optimize(state: &State, tick_count: &TickCount) { elapsed as f64 / f64::from(count.load(Ordering::Relaxed)) ); } + +/// Adds a hold for a chunk for the given entity. +pub fn hold_chunk( + entity: Entity, + holder: &mut ChunkHolder, + holders: &mut ChunkHolders, + chunk: ChunkPosition, +) { + holder.holds.insert(chunk); + holders.inner.insert(chunk, entity); +} + +/// Releases a hold for a chunk for the given entity. +pub fn release_chunk( + entity: Entity, + holder: &mut ChunkHolder, + holders: &mut ChunkHolders, + chunk: ChunkPosition, + trigger: &mut Trigger, +) { + holder.holds.remove(&chunk); + if let Some(vec) = holders.inner.get_vec_mut(&chunk) { + let mut index = None; + for (i, e) in vec.iter().enumerate() { + if *e == entity { + index = Some(i); + } + } + + if let Some(index) = index { + vec.swap_remove(index); + } + } + trigger.trigger(ChunkHolderReleaseEvent { entity, chunk }) +} + +/// Asynchronously loads the chunk at the given position. +/// At some point in time after this function is called, +/// the chunk will appear in the chunk map. +/// +/// In the event that the requested chunk does not exist +/// in the world save, it will be generated asynchronously. +pub fn load_chunk(handle: &ChunkWorkerHandle, pos: ChunkPosition) { + // Send request to chunk worker thread + handle + .sender + .send(chunk_worker::Request::LoadChunk(pos)) + .unwrap(); +} + +/// Asynchronously saves the chunk at the given position. +pub fn save_chunk(handle: &ChunkWorkerHandle, chunk: Arc, entities: Vec) { + handle + .sender + .send(chunk_worker::Request::SaveChunk(chunk, entities)) + .unwrap(); +} diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 8978cd0bc..7bbdfeeef 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -132,7 +132,8 @@ async fn _run_worker( } Action::SetStage(stage) => framed.codec_mut().set_stage(stage), Action::JoinGame(res) => { - let data = load_player_data(&config, res.uuid).await?; + // let data = load_player_data(&config, res.uuid).await?; + let data = PlayerData::default(); let info = NewClientInfo { ip, username: res.username, diff --git a/server/src/lazy.rs b/server/src/lazy.rs index 296fd6717..385f09422 100644 --- a/server/src/lazy.rs +++ b/server/src/lazy.rs @@ -11,8 +11,8 @@ impl LazyFnWithScheduler for F where F: FnOnce(&mut World, &mut Scheduler) + pub trait LazyFn: FnOnce(&mut World) + Send {} impl LazyFn for F where F: FnOnce(&mut World) + Send {} -pub trait LazyEntityFn: FnOnce(&mut World, Entity) + Send {} -impl LazyEntityFn for F where F: FnOnce(&mut World, Entity) + Send {} +pub trait LazyEntityFn: FnOnce(&mut World, &mut Scheduler, Entity) + Send {} +impl LazyEntityFn for F where F: FnOnce(&mut World, &mut Scheduler, Entity) + Send {} /// Resource which allows lazy creation of entities /// or execution of functions with world access. @@ -66,28 +66,39 @@ pub struct EntityBuilder<'a> { impl<'a> EntityBuilder<'a> { pub fn with_component(mut self, component: C) -> Self { - self.fns - .push(Box::new(move |world: &mut World, entity: Entity| { + self.fns.push(Box::new( + move |world: &mut World, _: &mut Scheduler, entity: Entity| { world.add_component(entity, component); - })); + }, + )); self } pub fn with_tag(mut self, tag: T) -> Self { - self.fns - .push(Box::new(move |world: &mut World, entity: Entity| { + self.fns.push(Box::new( + move |world: &mut World, _: &mut Scheduler, entity: Entity| { world.add_tag(entity, tag); - })); + }, + )); + self + } + + /// Executes a function with the entity after it is created. + pub fn with_exec( + mut self, + f: impl FnOnce(&mut World, &mut Scheduler, Entity) + Send + 'static, + ) -> Self { + self.fns.push(Box::new(f)); self } pub fn build(self) { let fns = self.fns; - self.lazy.exec(move |world| { + self.lazy.exec_with_scheduler(move |world, scheduler| { let entity = world.insert((), [()].iter().copied())[0]; for f in fns { - f(world, entity); + f(world, scheduler, entity); } }) } diff --git a/server/src/lib.rs b/server/src/lib.rs index 6f5b0ebcb..d2e4d9cbb 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -263,6 +263,12 @@ fn run_loop(world: &mut World, scheduler: &mut Scheduler, shutdown_rx: Receiver< scheduler.execute(world); world.defrag(None); // TODO: do this at interval rate? + // Run lazily-executed closures. TODO: remove unsafe + unsafe { + let state = scheduler.resources().get::() as *const State; + (&*state).flush(world, scheduler); + } + // Sleep correct amount let end_time = current_time_in_millis(); let elapsed = end_time - start_time; diff --git a/server/src/network.rs b/server/src/network.rs index b2d9dd7fd..bd7699fe1 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -193,6 +193,7 @@ pub fn network_( while let Ok(msg) = io.receiver.try_recv() { match msg { ListenerToServerMessage::NewClient(info) => { + debug!("Server received connection from {}", info.username); player::create(state, info); } } diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index d575ce438..1aee5cae9 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -25,6 +25,8 @@ pub struct PlayerJoinEvent { pub struct Player; /// Creates a new player from the given `NewClientInfo`. +/// +/// This function also triggers the `PlayerJoinEvent` for this player. pub fn create(state: &State, info: NewClientInfo) { entity::base(state, info.position) .with_tag(Player) @@ -36,5 +38,8 @@ pub fn create(state: &State, info: NewClientInfo) { .with_component(info.ip) .with_component(ProfileProperties(info.profile)) .with_component(NameComponent(info.username)) + .with_exec(|world, scheduler, player| { + scheduler.trigger(PlayerJoinEvent { player }, world); + }) .build(); } diff --git a/server/src/state.rs b/server/src/state.rs index 264b4fa06..1b75b0e5f 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -40,6 +40,11 @@ impl State { self.lazy.exec(f) } + /// See `Lazy::exec_with_scheduler()`. + pub fn exec_with_scheduler(&self, f: impl FnOnce(&mut World, &mut Scheduler) + Send + 'static) { + self.lazy.exec_with_scheduler(f) + } + /// See `Lazy::create_entity()`. pub fn create_entity(&self) -> EntityBuilder { self.lazy.create_entity() diff --git a/server/src/view.rs b/server/src/view.rs index a45b37dd0..b42c27473 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -17,21 +17,36 @@ //! * Various systems listen to `ViewUpdateEvent` and send necessary packets. //! This includes systems to load/unload chunks and send entities. +use crate::chunk_logic; +use crate::chunk_logic::{ + ChunkHolder, ChunkHolderReleaseEvent, ChunkHolders, ChunkLoadEvent, ChunkWorkerHandle, +}; +use crate::config::Config; use crate::entity::{EntityMoveEvent, PreviousPosition}; -use feather_core::{ChunkPosition, Position}; +use crate::network::Network; +use crate::player::PlayerJoinEvent; +use crate::state::State; +use dashmap::DashMap; +use feather_core::network::packet::implementation::{ChunkData, UnloadChunk}; +use feather_core::{Chunk, ChunkPosition, Position}; +use hashbrown::HashSet; use legion::entity::Entity; -use legion::query::Read; +use legion::query::{Read, Write}; +use parking_lot::Mutex; +use rayon::prelude::*; +use smallvec::SmallVec; use tonks::{PreparedWorld, Query, Trigger}; /// Event triggered when a player's view is updated, i.e. when they -/// cross into a new chunk. +/// cross into a new chunk or when they join. pub struct ViewUpdateEvent { /// The player whose view was updated. pub player: Entity, /// The new chunk. pub new_chunk: ChunkPosition, - /// The old chunk. - pub old_chunk: ChunkPosition, + /// The old chunk, or `None` if there was no old chunk + /// (i.e. this player just joined). + pub old_chunk: Option, } /// System which checks for players crossing chunk boundaries @@ -43,7 +58,8 @@ fn view_update( world: &mut PreparedWorld, trigger: &mut Trigger, ) { - events.iter().for_each(|event| { + let trigger = Mutex::new(trigger); + events.par_iter().for_each(|event| { let pos = *world.get_component::(event.entity).unwrap(); let prev_pos = world .get_component::(event.entity) @@ -55,9 +71,184 @@ fn view_update( let event = ViewUpdateEvent { player: event.entity, new_chunk: pos.chunk_pos(), - old_chunk: prev_pos.chunk_pos(), + old_chunk: Some(prev_pos.chunk_pos()), }; - trigger.trigger(event); + trigger.lock().trigger(event); } }); } + +/// System which triggers `ViewUpdateEvent`s on player join. +#[event_handler] +fn view_update_on_join( + event: &PlayerJoinEvent, + _query: &mut Query>, + world: &mut PreparedWorld, + trigger: &mut Trigger, +) { + dbg!(); + let position = *world.get_component::(event.player).unwrap(); + + trigger.trigger(ViewUpdateEvent { + player: event.player, + new_chunk: position.chunk_pos(), + old_chunk: None, + }); +} + +/// System which sends new chunks and unloads old chunks on the client +/// when the view is updated. +#[event_handler] +fn view_handle_chunks( + events: &[ViewUpdateEvent], + _query: &mut Query<(Read, Write)>, + world: &mut PreparedWorld, + holders: &mut ChunkHolders, + state: &State, + chunks_to_send: &ChunksToSend, + handle: &ChunkWorkerHandle, + trigger: &mut Trigger, +) { + events.iter().for_each(|event| { + // Find the old chunks and new chunks. + let new_chunks = chunks_within_view_distance(&state.config, event.new_chunk); + let old_chunks = match event.old_chunk { + Some(chunk) => chunks_within_view_distance(&state.config, chunk), + None => HashSet::new(), + }; + + let to_send = new_chunks.difference(&old_chunks); + let to_unload = old_chunks.difference(&new_chunks); + + let network = world.get_component::(event.player).unwrap(); + let mut holder = + unsafe { world.get_component_mut_unchecked::(event.player) }.unwrap(); + + to_send.into_iter().for_each(|chunk| { + send_chunk_to_player( + state, + event.player, + &network, + &mut holder, + holders, + *chunk, + chunks_to_send, + handle, + ); + }); + + to_unload.into_iter().for_each(|chunk| { + unload_chunk_for_player( + event.player, + &network, + trigger, + &mut holder, + holders, + *chunk, + ); + }); + }); +} + +/// Resource containing a mapping from chunks -> sets of players indicating +/// which chunks are pending to send to a given player. +#[derive(Default, Resource)] +pub struct ChunksToSend(DashMap>); + +/// Asynchronously sends a chunk to a player. +fn send_chunk_to_player( + state: &State, + player: Entity, + network: &Network, + holder: &mut ChunkHolder, + holders: &mut ChunkHolders, + chunk: ChunkPosition, + chunks_to_send: &ChunksToSend, + handle: &ChunkWorkerHandle, +) { + // Ensure that the chunk isn't unloaded while the player has it loaded. + chunk_logic::hold_chunk(player, holder, holders, chunk); + + // If the chunk is already loaded, send it. Otherwise, we need to + // queue it for loading. + if let Some(chunk) = state.chunk_at(chunk) { + network.send(create_chunk_data(&chunk)); + } else { + let contains = chunks_to_send.0.contains_key(&chunk); + chunks_to_send + .0 + .entry(chunk) + .or_insert_with(|| smallvec![]) + .push(player); + + if !contains { + // Queue chunk for loading if it isn't already. + chunk_logic::load_chunk(handle, chunk); + } + } +} + +/// Unloads a chunk on a client. +fn unload_chunk_for_player( + player: Entity, + network: &Network, + trigger: &mut Trigger, + holder: &mut ChunkHolder, + holders: &mut ChunkHolders, + chunk: ChunkPosition, +) { + // Release hold on chunk so it can be unloaded on the server + chunk_logic::release_chunk(player, holder, holders, chunk, trigger); + + // Send Unload Chunk packet. + network.send(UnloadChunk { + chunk_x: chunk.x, + chunk_z: chunk.z, + }); +} + +/// System which sends chunks to pending players when a chunk is loaded. +#[event_handler] +fn chunk_send( + event: &ChunkLoadEvent, + state: &State, + to_send: &ChunksToSend, + _query: &mut Query>, + world: &mut PreparedWorld, +) { + if let Some(players) = to_send.0.get(&event.pos) { + let chunk = state + .chunk_at(event.pos) + .expect("chunk not loaded, but load event was triggered"); + players.value().par_iter().for_each(|player| { + let network = world.get_component::(*player).unwrap(); + network.send(create_chunk_data(&chunk)); + }); + } + + to_send.0.remove(&event.pos); +} + +/// Creates a chunk data packet for the given chunk. +fn create_chunk_data(chunk: &Chunk) -> ChunkData { + ChunkData { + chunk: chunk.clone(), // TODO: optimize + } +} + +/// Finds all chunks within the view distance of a given chunk. +fn chunks_within_view_distance(config: &Config, position: ChunkPosition) -> HashSet { + let view_distance = config.server.view_distance as i32; + + let dimensions = view_distance * 2 + 1; + + let mut set = HashSet::with_capacity((dimensions * dimensions) as usize); + + for x in -view_distance..=view_distance { + for z in -view_distance..=view_distance { + set.insert(position + ChunkPosition::new(x, z)); + } + } + + set +} From 8ab3ebec7686b4ed8353534a6348fdce0b94951f Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 14:46:56 -0700 Subject: [PATCH 24/37] Wrestle through a number of Legion bugs to get chunk sending to work properly --- Cargo.lock | 107 +++++++++++++++++--------------------- server/Cargo.toml | 3 +- server/src/chunk_logic.rs | 7 +-- server/src/lib.rs | 3 +- server/src/player/mod.rs | 2 + server/src/view.rs | 20 ++++--- 6 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c2c76dea..f85b8180e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -255,6 +255,15 @@ dependencies = [ "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "chashmap" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "chrono" version = "0.4.9" @@ -692,6 +701,7 @@ dependencies = [ "bitvec 0.15.2 (registry+https://github.com/rust-lang/crates.io-index)", "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "chashmap 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -710,7 +720,7 @@ dependencies = [ "humantime-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=cfb31727d9f4c8f069c559c0cdf05f56547fd9d4)", + "legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)", "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mojang-api 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1159,14 +1169,6 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "itertools" -version = "0.7.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "itertools" version = "0.8.0" @@ -1208,7 +1210,7 @@ dependencies = [ [[package]] name = "legion" version = "0.2.1" -source = "git+https://github.com/TomGillen/legion?rev=cfb31727d9f4c8f069c559c0cdf05f56547fd9d4#cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" +source = "git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280#15d2b5c47b2a935dbf238698b9d7c299f3824280" dependencies = [ "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1219,7 +1221,6 @@ dependencies = [ "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "shrinkwraprs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1620,6 +1621,23 @@ name = "ordermap" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "owning_ref" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "parking_lot" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.9.0" @@ -1639,6 +1657,17 @@ dependencies = [ "parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot_core" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.6.2" @@ -1662,7 +1691,7 @@ dependencies = [ "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1748,14 +1777,6 @@ name = "proc-macro-nested" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "proc-macro2" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "proc-macro2" version = "0.3.8" @@ -1785,14 +1806,6 @@ name = "quick-error" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "quote" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "quote" version = "0.5.2" @@ -2246,17 +2259,6 @@ name = "sha1" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "shrinkwraprs" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "simdeez" version = "0.6.4" @@ -2310,7 +2312,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "smallvec" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -2375,16 +2377,6 @@ name = "subtle" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "syn" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "syn" version = "0.13.11" @@ -2645,7 +2637,7 @@ dependencies = [ "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=cfb31727d9f4c8f069c559c0cdf05f56547fd9d4)", + "legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)", "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3047,6 +3039,7 @@ dependencies = [ "checksum cfb8 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1b310afa67a25a8d5189eacaf5b14418c8dc3d8bcc5755619d89cab87871260d" "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" "checksum cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c" +"checksum chashmap 2.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ff41a3c2c1e39921b9003de14bf0439c7b63a9039637c291e1a64925d8ddfa45" "checksum chrono 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e8493056968583b0193c1bb04d6f7684586f3726992d6c573261941a895dbd68" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" @@ -3125,13 +3118,12 @@ dependencies = [ "checksum inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4cece20baea71d9f3435e7bbe9adf4765f091c5fe404975f844006964a71299" "checksum inventory-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2869bf972e998977b1cb87e60df70341d48e48dca0823f534feb91ea44adaf9" "checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" -"checksum itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0d47946d458e94a1b7bcabbf6521ea7c037062c81f534615abcad76e84d4970d" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=cfb31727d9f4c8f069c559c0cdf05f56547fd9d4)" = "" +"checksum legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)" = "" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" @@ -3174,8 +3166,11 @@ dependencies = [ "checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" "checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" +"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" "checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" +"checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" +"checksum parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" "checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" @@ -3189,12 +3184,10 @@ dependencies = [ "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" "checksum proc-macro-hack 0.5.10 (registry+https://github.com/rust-lang/crates.io-index)" = "114cdf1f426eb7f550f01af5f53a33c0946156f6814aec939b3bd77e844f9a9d" "checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e" -"checksum proc-macro2 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum proc-macro2 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1b06e2f335f48d24442b35a19df506a835fb3547bc3c06ef27340da9acf5cae7" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "90cf5f418035b98e655e9cdb225047638296b862b42411c4e45bb88d700f7fc0" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum quote 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9949cfe66888ffe1d53e6ec9d9f3b70714083854be20fd5e271b232a017401e8" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" @@ -3244,7 +3237,6 @@ dependencies = [ "checksum serde_json 1.0.41 (registry+https://github.com/rust-lang/crates.io-index)" = "2f72eb2a68a7dc3f9a691bfda9305a1c017a6215e5a4545c258500d2099a37c2" "checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97" "checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" -"checksum shrinkwraprs 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7d5f047b90b2ca2d1526ff73d67cba61f86f4cf9a8afddc99dd96702ded8e684" "checksum simdeez 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4204ae48b2a871f428dc20426f005be250413fa6263e6f3d93094a36b29504dc" "checksum simdnoise 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "86a9e4c1c3369eab7105ac7e1582a601942fed0a63877cb2e1afcf57f34ed7b3" "checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618" @@ -3252,7 +3244,7 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slotmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "759fd553261805f128e2900bf69ab3d034260bc338caf7f0ee54dbf035c85acd" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" -"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" +"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" @@ -3263,7 +3255,6 @@ dependencies = [ "checksum strum 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6138f8f88a16d90134763314e3fc76fa3ed6a7db4725d6acf9a3ef95a3188d22" "checksum strum_macros 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0054a7df764039a6cd8592b9de84be4bec368ff081d203a7d5371cbfa8e65c81" "checksum subtle 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab3af2eb31c42e8f0ccf43548232556c42737e01a96db6e1777b0be108e79799" -"checksum syn 0.12.15 (registry+https://github.com/rust-lang/crates.io-index)" = "c97c05b8ebc34ddd6b967994d5c6e9852fa92f8b82b3858c39451f97346dcce5" "checksum syn 0.13.11 (registry+https://github.com/rust-lang/crates.io-index)" = "14f9bf6292f3a61d2c716723fdb789a41bbe104168e6f496dc6497e531ea1b9b" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "66850e97125af79138385e9b88339cbcd037e3f28ceab8c5ad98e64f0f1f80bf" diff --git a/server/Cargo.toml b/server/Cargo.toml index 42dc10294..15fd121a4 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,7 +20,7 @@ feather-item-block = { path = "../item_block" } feather-codegen = { path = "../codegen" } # Core ECS + systems -legion = { git = "https://github.com/TomGillen/legion", rev = "cfb31727d9f4c8f069c559c0cdf05f56547fd9d4" } +legion = { git = "https://github.com/feather-rs/legion", rev = "15d2b5c47b2a935dbf238698b9d7c299f3824280" } # tonks = { git = "https://github.com/feather-rs/tonks", rev = "226d04de294008a1f615bae40f4d19a890583f0b", features = ["system-registry"] } tonks = { path = "../../../dev/tonks", features = ["system-registry"] } @@ -62,6 +62,7 @@ heapless = "0.5" uuid = { version = "0.7", features = ["v4"] } multimap = "0.7" smallvec = "0.6" +chashmap = "2.2" dashmap = "2.1" # Logging diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index 2c4ac1115..3066a48b0 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -48,7 +48,6 @@ pub struct ChunkLoadFailEvent { fn chunk_load_system( state: &State, handle: &ChunkWorkerHandle, - load_events: &mut Trigger, fail_events: &mut Trigger, ) { while let Ok(reply) = handle.receiver.try_recv() { @@ -57,9 +56,11 @@ fn chunk_load_system( Ok((chunk, entities)) => { state.lazy_insert_chunk(chunk); - // Trigger event + // Trigger event - lazily so it happens after the chunk is inserted into the chunk map let event = ChunkLoadEvent { pos, entities }; - load_events.trigger(event); + state.exec_with_scheduler(move |world, scheduler| { + scheduler.trigger(event, world); + }); trace!("Loaded chunk at {:?}", pos); } diff --git a/server/src/lib.rs b/server/src/lib.rs index d2e4d9cbb..5642f4d6d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -261,7 +261,8 @@ fn run_loop(world: &mut World, scheduler: &mut Scheduler, shutdown_rx: Receiver< let start_time = current_time_in_millis(); scheduler.execute(world); - world.defrag(None); // TODO: do this at interval rate? + // https://github.com/TomGillen/legion/issues/60 + // world.defrag(None); // TODO: do this at interval rate? // Run lazily-executed closures. TODO: remove unsafe unsafe { diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index 1aee5cae9..e3dcc76d1 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -1,5 +1,6 @@ //! Systems and components specific to player entities. +use crate::chunk_logic::ChunkHolder; use crate::entity; use crate::entity::NameComponent; use crate::io::NewClientInfo; @@ -38,6 +39,7 @@ pub fn create(state: &State, info: NewClientInfo) { .with_component(info.ip) .with_component(ProfileProperties(info.profile)) .with_component(NameComponent(info.username)) + .with_component(ChunkHolder::default()) .with_exec(|world, scheduler, player| { scheduler.trigger(PlayerJoinEvent { player }, world); }) diff --git a/server/src/view.rs b/server/src/view.rs index b42c27473..0789023f0 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -26,7 +26,7 @@ use crate::entity::{EntityMoveEvent, PreviousPosition}; use crate::network::Network; use crate::player::PlayerJoinEvent; use crate::state::State; -use dashmap::DashMap; +use chashmap::CHashMap; use feather_core::network::packet::implementation::{ChunkData, UnloadChunk}; use feather_core::{Chunk, ChunkPosition, Position}; use hashbrown::HashSet; @@ -153,7 +153,7 @@ fn view_handle_chunks( /// Resource containing a mapping from chunks -> sets of players indicating /// which chunks are pending to send to a given player. #[derive(Default, Resource)] -pub struct ChunksToSend(DashMap>); +pub struct ChunksToSend(CHashMap>); /// Asynchronously sends a chunk to a player. fn send_chunk_to_player( @@ -175,11 +175,15 @@ fn send_chunk_to_player( network.send(create_chunk_data(&chunk)); } else { let contains = chunks_to_send.0.contains_key(&chunk); - chunks_to_send - .0 - .entry(chunk) - .or_insert_with(|| smallvec![]) - .push(player); + + let mut vec = match chunks_to_send.0.get_mut(&chunk) { + Some(vec) => vec, + None => { + chunks_to_send.0.insert(chunk, smallvec![]); + chunks_to_send.0.get_mut(&chunk).unwrap() + } + }; + vec.push(player); if !contains { // Queue chunk for loading if it isn't already. @@ -220,7 +224,7 @@ fn chunk_send( let chunk = state .chunk_at(event.pos) .expect("chunk not loaded, but load event was triggered"); - players.value().par_iter().for_each(|player| { + players.par_iter().for_each(|player| { let network = world.get_component::(*player).unwrap(); network.send(create_chunk_data(&chunk)); }); From edec45b399efe9c88bb2a67f078f9e404f840e88 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 16:40:07 -0700 Subject: [PATCH 25/37] Player can now join and see chunks. --- Cargo.lock | 22 ++++++++- server/Cargo.toml | 2 +- server/src/chunk_logic.rs | 4 +- server/src/entity/mod.rs | 12 ++++- server/src/io/worker.rs | 9 ++-- server/src/join.rs | 100 ++++++++++++++++++++++++++++++++++++++ server/src/lib.rs | 4 +- server/src/network.rs | 2 +- server/src/player/mod.rs | 6 ++- server/src/state.rs | 5 +- server/src/view.rs | 35 +++++++++++-- 11 files changed, 182 insertions(+), 19 deletions(-) create mode 100644 server/src/join.rs diff --git a/Cargo.lock b/Cargo.lock index f85b8180e..93cc34629 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -193,6 +193,11 @@ name = "bumpalo" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bumpalo" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "byteorder" version = "1.3.2" @@ -1673,12 +1678,15 @@ name = "parking_lot_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2439,6 +2447,16 @@ dependencies = [ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "thread-id" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -2631,7 +2649,7 @@ version = "0.1.0" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bumpalo 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3030,6 +3048,7 @@ dependencies = [ "checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" "checksum bstr 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8d6c2c5b58ab920a4f5aeaaca34b4488074e8cc7596af94e6f8c6ff247c60245" "checksum bumpalo 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ad807f2fc2bf185eeb98ff3a901bd46dc5ad58163d0fa4577ba0d25674d71708" +"checksum bumpalo 3.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c2636342f603744010322b36bc245a35d1a9eecadc29fbbb532be90415bdc35d" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" "checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" @@ -3261,6 +3280,7 @@ dependencies = [ "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +"checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88ddf1ad580c7e3d1efff877d972bcc93f995556b9087a5a259630985c88ceab" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" diff --git a/server/Cargo.toml b/server/Cargo.toml index 15fd121a4..c55173a89 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -27,7 +27,7 @@ tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" rayon = "1.2" -parking_lot = "0.9" +parking_lot = { version = "0.9", features = ["deadlock_detection"] } lock_api = "0.3" thread_local = "1.0" diff --git a/server/src/chunk_logic.rs b/server/src/chunk_logic.rs index 3066a48b0..134a31482 100644 --- a/server/src/chunk_logic.rs +++ b/server/src/chunk_logic.rs @@ -58,8 +58,8 @@ fn chunk_load_system( // Trigger event - lazily so it happens after the chunk is inserted into the chunk map let event = ChunkLoadEvent { pos, entities }; - state.exec_with_scheduler(move |world, scheduler| { - scheduler.trigger(event, world); + state.exec_with_scheduler(move |_, scheduler| { + scheduler.trigger(event); }); trace!("Loaded chunk at {:?}", pos); diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 124c814d2..9ac618301 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -1,4 +1,4 @@ -//! Dealing with entities. +//! Dealing with entities, including associated components and events. use crate::lazy::EntityBuilder; use crate::state::State; @@ -6,8 +6,16 @@ use feather_core::Position; use legion::prelude::Entity; use legion::query::{Read, Write}; use std::ops::{Deref, DerefMut}; +use std::sync::atomic::{AtomicI32, Ordering}; use tonks::{PreparedWorld, Query}; +/// ID of an entity. This value is generally unique. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct EntityId(pub i32); + +/// Entity ID counter, used to create new entity IDs. +pub static ENTITY_ID_COUNTER: AtomicI32 = AtomicI32::new(0); + /// Event triggered when an entity is removed. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EntityDeleteEvent { @@ -79,8 +87,10 @@ pub fn position_reset( /// * Position /// * Velocity (0) pub fn base(state: &State, position: Position) -> EntityBuilder { + let id = ENTITY_ID_COUNTER.fetch_add(1, Ordering::Relaxed); state .create_entity() + .with_component(EntityId(id)) .with_component(position) .with_component(PreviousPosition(position)) .with_component(Velocity::default()) diff --git a/server/src/io/worker.rs b/server/src/io/worker.rs index 7bbdfeeef..546038598 100644 --- a/server/src/io/worker.rs +++ b/server/src/io/worker.rs @@ -141,10 +141,11 @@ async fn _run_worker( uuid: res.uuid, sender: tx_server_to_worker.clone(), receiver: rx_worker_to_server.take().unwrap(), - position: data - .entity - .read_position() - .ok_or_else(|| Error::PlayerData)?, + /*position: data + .entity + .read_position() + .ok_or_else(|| Error::PlayerData)?,*/ + position: position!(0.0, 80.0, 0.0), data, }; global_sender diff --git a/server/src/join.rs b/server/src/join.rs new file mode 100644 index 000000000..f6ed07d35 --- /dev/null +++ b/server/src/join.rs @@ -0,0 +1,100 @@ +//! After chunks are sent to a client, we complete the login sequence +//! by sending Spawn Position, Player Position and Look, and inventory, +//! among others. This is handled by the event handler `join`. + +use crate::entity::EntityId; +use crate::network::Network; +use crate::player::PlayerJoinEvent; +use crate::state::State; +use crate::view::ChunkSendEvent; +use feather_core::network::packet::implementation::{ + JoinGame, PlayerPositionAndLookClientbound, SpawnPosition, +}; +use feather_core::{BlockPosition, Gamemode, Position}; +use legion::query::{Read, Write}; +use parking_lot::RwLock; +use rayon::prelude::*; +use tonks::{PreparedWorld, Query}; + +/// Component indicating whether a player has completed the join sequence. +#[derive(Default, Debug)] +pub struct Joined(pub bool); + +/// System to run the join sequence. To determine when a player is ready to join, +/// we wait for the chunk that the player is in to be sent—this appears to work +/// well with the client. +#[event_handler] +fn join( + events: &[ChunkSendEvent], + _query: &mut Query<(Write, Read, Read)>, + world: &mut PreparedWorld, + state: &State, +) { + let world = RwLock::new(world); + events.par_iter().for_each(|event| { + let pos = { + let world = world.read(); + + let pos = world.get_component::(event.player).unwrap(); + let joined = world.get_component::(event.player).unwrap(); + + if pos.chunk_pos() != event.chunk || joined.0 { + return; + } + + *pos + }; + + // Run the join sequence. TODO: inventory. + world + .write() + .get_component_mut::(event.player) + .unwrap() + .0 = true; + + let world = world.read(); + let network = world.get_component::(event.player).unwrap(); + + let packet = SpawnPosition { + location: BlockPosition::new( + state.level.spawn_x, + state.level.spawn_y, + state.level.spawn_z, + ), + }; + network.send(packet); + + let packet = PlayerPositionAndLookClientbound { + x: pos.x, + y: pos.y, + z: pos.z, + yaw: pos.yaw, + pitch: pos.pitch, + flags: 0, + teleport_id: 0, + }; + network.send(packet); + }); +} + +#[event_handler] +fn send_join_game( + event: &PlayerJoinEvent, + _query: &mut Query<(Read, Read)>, + world: &mut PreparedWorld, +) { + let network = world.get_component::(event.player).unwrap(); + let id = world.get_component::(event.player).unwrap(); + + // TODO + let packet = JoinGame { + entity_id: id.0, + gamemode: Gamemode::Creative.get_id(), + dimension: 0, + difficulty: 0, + max_players: 0, + level_type: "default".to_string(), + reduced_debug_info: false, + }; + network.send(packet); +} diff --git a/server/src/lib.rs b/server/src/lib.rs index 5642f4d6d..4abed94d5 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -149,6 +149,7 @@ pub mod chunk_worker; pub mod config; pub mod entity; pub mod io; +pub mod join; pub mod lazy; pub mod network; pub mod physics; @@ -299,9 +300,8 @@ fn init_scheduler( // Insert resources which don't have a `Default` impl. let mut resources = Resources::new(); let chunk_map = ChunkMap::new(); - resources.insert(State::new(config, chunk_map)); + resources.insert(State::new(config, chunk_map, level)); resources.insert(chunk_worker_handle); - resources.insert(level); resources.insert(io_manager); tonks::build_scheduler().build(resources) diff --git a/server/src/network.rs b/server/src/network.rs index bd7699fe1..fb0e77b65 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -181,7 +181,7 @@ pub fn network_( debug_assert!(world.delete(entity), "player already deleted"); }); } - ServerToWorkerMessage::SendPacket(packet) => { + ServerToWorkerMessage::NotifyPacketReceived(packet) => { packet_queue.push(packet, entity); } _ => unreachable!(), diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index e3dcc76d1..d7877b524 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -4,6 +4,7 @@ use crate::chunk_logic::ChunkHolder; use crate::entity; use crate::entity::NameComponent; use crate::io::NewClientInfo; +use crate::join::Joined; use crate::network::Network; use crate::state::State; use legion::entity::Entity; @@ -40,8 +41,9 @@ pub fn create(state: &State, info: NewClientInfo) { .with_component(ProfileProperties(info.profile)) .with_component(NameComponent(info.username)) .with_component(ChunkHolder::default()) - .with_exec(|world, scheduler, player| { - scheduler.trigger(PlayerJoinEvent { player }, world); + .with_component(Joined(false)) + .with_exec(|_, scheduler, player| { + scheduler.trigger(PlayerJoinEvent { player }); }) .build(); } diff --git a/server/src/state.rs b/server/src/state.rs index 1b75b0e5f..ad2df82da 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,6 +1,7 @@ use crate::config::Config; use crate::lazy::{EntityBuilder, Lazy}; use feather_blocks::Block; +use feather_core::level::LevelData; use feather_core::world::ChunkMap; use feather_core::{BlockPosition, Chunk, ChunkPosition}; use legion::world::World; @@ -22,15 +23,17 @@ use tonks::Scheduler; pub struct State { pub config: Arc, pub chunk_map: ChunkMap, + pub level: LevelData, lazy: Lazy, } impl State { - pub fn new(config: Arc, chunk_map: ChunkMap) -> Self { + pub fn new(config: Arc, chunk_map: ChunkMap, level: LevelData) -> Self { Self { config, chunk_map, + level, lazy: Lazy::default(), } } diff --git a/server/src/view.rs b/server/src/view.rs index 0789023f0..cd90392d7 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -49,6 +49,13 @@ pub struct ViewUpdateEvent { pub old_chunk: Option, } +/// Event triggered when a chunk is sent to a player. +#[derive(Debug)] +pub struct ChunkSendEvent { + pub chunk: ChunkPosition, + pub player: Entity, +} + /// System which checks for players crossing chunk boundaries /// and triggers `ViewUpdateEvent`s. #[event_handler] @@ -107,7 +114,8 @@ fn view_handle_chunks( state: &State, chunks_to_send: &ChunksToSend, handle: &ChunkWorkerHandle, - trigger: &mut Trigger, + holder_release_trigger: &mut Trigger, + chunk_send_trigger: &mut Trigger, ) { events.iter().for_each(|event| { // Find the old chunks and new chunks. @@ -124,6 +132,13 @@ fn view_handle_chunks( let mut holder = unsafe { world.get_component_mut_unchecked::(event.player) }.unwrap(); + // Sort sent chunks so that closer chunks are sent first. + let mut to_send = to_send.into_iter().copied().collect::>(); + to_send.sort_unstable_by_key(|chunk| { + chunk.manhattan_distance(event.new_chunk); + }); + + // Send new chunks. to_send.into_iter().for_each(|chunk| { send_chunk_to_player( state, @@ -131,17 +146,19 @@ fn view_handle_chunks( &network, &mut holder, holders, - *chunk, + chunk, chunks_to_send, handle, + chunk_send_trigger, ); }); + // Unload old chunks on client. to_unload.into_iter().for_each(|chunk| { unload_chunk_for_player( event.player, &network, - trigger, + holder_release_trigger, &mut holder, holders, *chunk, @@ -165,6 +182,7 @@ fn send_chunk_to_player( chunk: ChunkPosition, chunks_to_send: &ChunksToSend, handle: &ChunkWorkerHandle, + trigger: &mut Trigger, ) { // Ensure that the chunk isn't unloaded while the player has it loaded. chunk_logic::hold_chunk(player, holder, holders, chunk); @@ -173,6 +191,10 @@ fn send_chunk_to_player( // queue it for loading. if let Some(chunk) = state.chunk_at(chunk) { network.send(create_chunk_data(&chunk)); + trigger.trigger(ChunkSendEvent { + chunk: chunk.position(), + player, + }); } else { let contains = chunks_to_send.0.contains_key(&chunk); @@ -219,14 +241,19 @@ fn chunk_send( to_send: &ChunksToSend, _query: &mut Query>, world: &mut PreparedWorld, + trigger: &mut Trigger, ) { if let Some(players) = to_send.0.get(&event.pos) { let chunk = state .chunk_at(event.pos) .expect("chunk not loaded, but load event was triggered"); - players.par_iter().for_each(|player| { + players.iter().for_each(|player| { let network = world.get_component::(*player).unwrap(); network.send(create_chunk_data(&chunk)); + trigger.trigger(ChunkSendEvent { + chunk: chunk.position(), + player: *player, + }); }); } From 8cada32be0b3dd0604ac38520ffce30ddf1444b5 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 17:08:56 -0700 Subject: [PATCH 26/37] Reimplement movement packets --- Cargo.lock | 22 +++------ server/Cargo.toml | 6 +-- server/src/lib.rs | 1 + server/src/network.rs | 2 + server/src/packet_handlers/mod.rs | 3 ++ server/src/packet_handlers/movement.rs | 68 ++++++++++++++++++++++++++ server/src/view.rs | 1 - 7 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 server/src/packet_handlers/mod.rs create mode 100644 server/src/packet_handlers/movement.rs diff --git a/Cargo.lock b/Cargo.lock index 93cc34629..38169a2f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -754,7 +754,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1678,15 +1678,12 @@ name = "parking_lot_core" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "backtrace 0.3.38 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "petgraph 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2447,16 +2444,6 @@ dependencies = [ "unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "thread-id" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "thread_local" version = "0.3.6" @@ -2646,6 +2633,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" +source = "git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2#f91a0639e55feb25a70c67b0679903868575f2d2" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2662,12 +2650,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)", ] [[package]] name = "tonks-macros" version = "0.1.0" +source = "git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2#f91a0639e55feb25a70c67b0679903868575f2d2" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3280,7 +3269,6 @@ dependencies = [ "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -"checksum thread-id 3.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "88ddf1ad580c7e3d1efff877d972bcc93f995556b9087a5a259630985c88ceab" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" @@ -3296,6 +3284,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/server/Cargo.toml b/server/Cargo.toml index c55173a89..ef1d0eada 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,13 +21,13 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/feather-rs/legion", rev = "15d2b5c47b2a935dbf238698b9d7c299f3824280" } -# tonks = { git = "https://github.com/feather-rs/tonks", rev = "226d04de294008a1f615bae40f4d19a890583f0b", features = ["system-registry"] } -tonks = { path = "../../../dev/tonks", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "f91a0639e55feb25a70c67b0679903868575f2d2", features = ["system-registry"] } +# tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading crossbeam = "0.7" rayon = "1.2" -parking_lot = { version = "0.9", features = ["deadlock_detection"] } +parking_lot = "0.9" lock_api = "0.3" thread_local = "1.0" diff --git a/server/src/lib.rs b/server/src/lib.rs index 4abed94d5..feb10d04d 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -152,6 +152,7 @@ pub mod io; pub mod join; pub mod lazy; pub mod network; +pub mod packet_handlers; pub mod physics; pub mod player; pub mod shutdown; diff --git a/server/src/network.rs b/server/src/network.rs index fb0e77b65..c15b43767 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -113,6 +113,8 @@ impl PacketQueue { pos: 0, }; + // Ensure mutex is not released; we will do it manually in `UnsafeDrain` + std::mem::forget(queue); // Safety: the vector cannot be accessed as long as the returned `UnsafeDrain` // has not been dropped, since the mutex is acquired. queue.set_len(0); diff --git a/server/src/packet_handlers/mod.rs b/server/src/packet_handlers/mod.rs new file mode 100644 index 000000000..f25137873 --- /dev/null +++ b/server/src/packet_handlers/mod.rs @@ -0,0 +1,3 @@ +//! Systems which handle packets through `crate::network::PacketQueue`. + +mod movement; diff --git a/server/src/packet_handlers/movement.rs b/server/src/packet_handlers/movement.rs new file mode 100644 index 000000000..ecd230a32 --- /dev/null +++ b/server/src/packet_handlers/movement.rs @@ -0,0 +1,68 @@ +use crate::entity::EntityMoveEvent; +use crate::network::PacketQueue; +use feather_core::network::packet::implementation::{ + PlayerLook, PlayerPosition, PlayerPositionAndLookServerbound, +}; +use feather_core::Position; +use legion::entity::Entity; +use legion::query::Write; +use tonks::{PreparedWorld, Query, Trigger}; + +#[derive(Default, Resource)] +struct Buf(Vec<(Entity, Position)>); + +/// Handles player movement packets. +#[system] +fn movement( + queue: &PacketQueue, + _query: &mut Query>, + world: &mut PreparedWorld, + buf: &mut Buf, + trigger: &mut Trigger, +) { + let positions = queue.received::().map(|(player, packet)| { + let old = *world.get_component::(player).unwrap(); + ( + player, + position!( + packet.x, + packet.feet_y, + packet.z, + old.pitch, + old.yaw, + packet.on_ground + ), + ) + }); + + let looks = queue.received::().map(|(player, packet)| { + let mut old = *world.get_component::(player).unwrap(); + old.pitch = packet.pitch; + old.yaw = packet.yaw; + old.on_ground = packet.on_ground; + (player, old) + }); + + let pos_looks = queue + .received::() + .map(|(player, packet)| { + ( + player, + position!( + packet.x, + packet.feet_y, + packet.z, + packet.pitch, + packet.yaw, + packet.on_ground + ), + ) + }); + + buf.0.extend(positions.chain(looks).chain(pos_looks)); + + buf.0.drain(..).for_each(|(player, new_pos)| { + *world.get_component_mut::(player).unwrap() = new_pos; + trigger.trigger(EntityMoveEvent { entity: player }); + }); +} diff --git a/server/src/view.rs b/server/src/view.rs index cd90392d7..3e119ead0 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -93,7 +93,6 @@ fn view_update_on_join( world: &mut PreparedWorld, trigger: &mut Trigger, ) { - dbg!(); let position = *world.get_component::(event.player).unwrap(); trigger.trigger(ViewUpdateEvent { From 4254453ac3f3da45b6060e901ea88f139ba2659a Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 22:28:31 -0700 Subject: [PATCH 27/37] Fix compilation --- server/src/network.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/src/network.rs b/server/src/network.rs index c15b43767..534b5de63 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -113,11 +113,11 @@ impl PacketQueue { pos: 0, }; - // Ensure mutex is not released; we will do it manually in `UnsafeDrain` - std::mem::forget(queue); // Safety: the vector cannot be accessed as long as the returned `UnsafeDrain` // has not been dropped, since the mutex is acquired. queue.set_len(0); + // Ensure mutex is not released; we will do it manually in `UnsafeDrain` + std::mem::forget(queue); let iter = drain.map(|(entity, packet)| (entity, cast_packet::

(packet))); From de214dd7b3d0e08a0ff644752ab5f9e24c9a5295 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 21 Dec 2019 22:35:24 -0700 Subject: [PATCH 28/37] Fix assorted Clippy errors --- server/src/network.rs | 4 ++-- server/src/view.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/src/network.rs b/server/src/network.rs index 534b5de63..8f2deee72 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -37,7 +37,7 @@ impl Iterator for UnsafeDrain { return None; } - let value = unsafe { std::ptr::read(self.ptr.offset(self.pos as isize)) }; + let value = unsafe { std::ptr::read(self.ptr.add(self.pos)) }; self.pos += 1; Some(value) } @@ -180,7 +180,7 @@ pub fn network_( match msg { ServerToWorkerMessage::NotifyDisconnect(_) => { state.exec(move |world| { - debug_assert!(world.delete(entity), "player already deleted"); + assert!(world.delete(entity), "player already deleted"); }); } ServerToWorkerMessage::NotifyPacketReceived(packet) => { diff --git a/server/src/view.rs b/server/src/view.rs index 3e119ead0..298123a59 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -132,7 +132,7 @@ fn view_handle_chunks( unsafe { world.get_component_mut_unchecked::(event.player) }.unwrap(); // Sort sent chunks so that closer chunks are sent first. - let mut to_send = to_send.into_iter().copied().collect::>(); + let mut to_send = to_send.copied().collect::>(); to_send.sort_unstable_by_key(|chunk| { chunk.manhattan_distance(event.new_chunk); }); @@ -153,7 +153,7 @@ fn view_handle_chunks( }); // Unload old chunks on client. - to_unload.into_iter().for_each(|chunk| { + to_unload.for_each(|chunk| { unload_chunk_for_player( event.player, &network, @@ -172,6 +172,7 @@ fn view_handle_chunks( pub struct ChunksToSend(CHashMap>); /// Asynchronously sends a chunk to a player. +#[allow(clippy::too_many_arguments)] fn send_chunk_to_player( state: &State, player: Entity, From 96d6895f8ecb9646d24c01004f220e3c38a3fbd0 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sun, 22 Dec 2019 13:39:14 -0700 Subject: [PATCH 29/37] Implement ChunkEntities cache --- Cargo.lock | 76 +++--------------------------- server/Cargo.toml | 3 +- server/src/chunk_entities.rs | 90 +++++++++++++++++++++++++++++++++--- server/src/entity/mod.rs | 12 ++++- server/src/lib.rs | 2 + server/src/network.rs | 10 +++- server/src/state.rs | 3 ++ 7 files changed, 113 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 38169a2f6..75079414b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -445,16 +445,6 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "crossbeam-utils" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "csv" version = "1.1.1" @@ -493,26 +483,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "dashmap" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dashmap-shard 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num_cpus 1.11.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "dashmap-shard" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "derivative" version = "1.0.3" @@ -710,7 +680,6 @@ dependencies = [ "criterion 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)", - "dashmap 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "feather-blocks 0.5.0", @@ -754,7 +723,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1653,15 +1622,6 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parking_lot" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "parking_lot_core" version = "0.2.14" @@ -1687,19 +1647,6 @@ dependencies = [ "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "parking_lot_core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "paste" version = "0.1.6" @@ -2315,11 +2262,6 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "smallvec" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "sourcefile" version = "0.1.4" @@ -2633,7 +2575,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2#f91a0639e55feb25a70c67b0679903868575f2d2" +source = "git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6#15f8aa965a667a6b7160e60828f935b17c6ac7e6" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2650,13 +2592,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2#f91a0639e55feb25a70c67b0679903868575f2d2" +source = "git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6#15f8aa965a667a6b7160e60828f935b17c6ac7e6" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3065,13 +3007,10 @@ dependencies = [ "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" "checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" "checksum ctrlc 3.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7dfd2d8b4c82121dfdff120f818e09fc4380b0b7e17a742081a89b94853e87f" -"checksum dashmap 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1cb7ebab7705baa489c3f36e494b59bc16edb4fc5b3341921197270a11259d6a" -"checksum dashmap-shard 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c94b718728139bf8d0f822d63e0b65f4ed781562d5c28f6b84d8e1a66938ae70" "checksum derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "942ca430eef7a3806595a6737bc388bf51adb888d3fc0dd1b50f1c170167ee3a" "checksum derive-new 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)" = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9" "checksum derive_deref 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "11554fdb0aa42363a442e0c4278f51c9621e20c1ce3bac51d79e60646f3b8b8f" @@ -3175,12 +3114,10 @@ dependencies = [ "checksum openssl-sys 0.9.50 (registry+https://github.com/rust-lang/crates.io-index)" = "2c42dcccb832556b5926bc9ae61e8775f2a61e725ab07ab3d1e7fcf8ae62c3b6" "checksum ordermap 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063" "checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" -"checksum parking_lot 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "92e98c49ab0b7ce5b222f2cc9193fc4efe11c6d0bd4f648e374684a6857b1cfc" "checksum parking_lot 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "149d8f5b97f3c1133e3cfcd8886449959e856b557ff281e292b733d7c69e005e" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "4db1a8ccf734a7bce794cc19b3df06ed87ab2f3907036b693c68f56b4d4537fa" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -"checksum parking_lot_core 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7582838484df45743c8434fbff785e8edf260c28748353d44bc0da32e0ceabf1" "checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" "checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" "checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" @@ -3252,7 +3189,6 @@ dependencies = [ "checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum slotmap 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "759fd553261805f128e2900bf69ab3d034260bc338caf7f0ee54dbf035c85acd" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" -"checksum smallvec 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44e59e0c9fa00817912ae6e4e6e3c4fe04455e75699d06eedc7d85917ed8e8f4" "checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3" "checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" @@ -3284,8 +3220,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=f91a0639e55feb25a70c67b0679903868575f2d2)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/server/Cargo.toml b/server/Cargo.toml index ef1d0eada..afdf8899f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,7 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/feather-rs/legion", rev = "15d2b5c47b2a935dbf238698b9d7c299f3824280" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "f91a0639e55feb25a70c67b0679903868575f2d2", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "15f8aa965a667a6b7160e60828f935b17c6ac7e6", features = ["system-registry"] } # tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading @@ -63,7 +63,6 @@ uuid = { version = "0.7", features = ["v4"] } multimap = "0.7" smallvec = "0.6" chashmap = "2.2" -dashmap = "2.1" # Logging log = "0.4" diff --git a/server/src/chunk_entities.rs b/server/src/chunk_entities.rs index 8edd8831c..75a2bd88d 100644 --- a/server/src/chunk_entities.rs +++ b/server/src/chunk_entities.rs @@ -1,6 +1,14 @@ -use dashmap::DashMap; -use feather_core::ChunkPosition; +use crate::entity::{EntityCreateEvent, EntityDeleteEvent, EntityMoveEvent, PreviousPosition}; +use crate::state::State; +use feather_core::{ChunkPosition, Position}; +use hashbrown::HashMap; use legion::entity::Entity; +use legion::query::Read; +use parking_lot::{MappedRwLockReadGuard, RwLock, RwLockReadGuard}; +use rayon::prelude::*; +use tonks::{PreparedWorld, Query}; + +static EMPTY_VEC: Vec = Vec::new(); /// Stores which entities belong to every given chunk. /// @@ -11,21 +19,29 @@ use legion::entity::Entity; /// to a player. /// /// This structure is internally stored in `State`, using -/// `dashmap` for concurrent access. +/// a `RwLock` for concurrent access. (TODO: remove lock.) /// /// Do note that the information in this structure is not necessarily up to date, /// although a best effort is made to update the data. #[derive(Resource)] -pub struct ChunkEntities(DashMap>); +pub struct ChunkEntities(RwLock>>); impl ChunkEntities { pub fn new() -> Self { - Self(DashMap::default()) + Self(RwLock::new(HashMap::new())) } /// Returns a slice of entities in the given chunk. - pub fn entities_in_chunk(&self, _chunk: ChunkPosition) -> &[Entity] { - todo!("implement chunk entities properly"); + pub fn entities_in_chunk(&self, chunk: ChunkPosition) -> MappedRwLockReadGuard<[Entity]> { + let map = self.0.read(); + + RwLockReadGuard::map(map, move |map| { + if let Some(vec) = map.get(&chunk) { + vec.as_slice() + } else { + &EMPTY_VEC + } + }) } } @@ -34,3 +50,63 @@ impl Default for ChunkEntities { Self::new() } } + +/// System to update ChunkEntities when entities move into new chunks. +#[event_handler] +fn chunk_entities_handle_movement( + events: &[EntityMoveEvent], + state: &State, + _query: &mut Query<(Read, Read)>, + world: &mut PreparedWorld, +) { + events.par_iter().for_each(|event| { + let old_pos = world + .get_component::(event.entity) + .unwrap() + .0; + let new_pos = *world.get_component::(event.entity).unwrap(); + + let old_chunk = old_pos.chunk_pos(); + let new_chunk = new_pos.chunk_pos(); + + if old_chunk != new_chunk { + // Update chunk entities + let mut map = state.chunk_entities.0.write(); + map.entry(new_chunk) + .or_insert_with(|| vec![]) + .push(event.entity); + map.entry(old_chunk).and_modify(|vec| { + vec.remove_item(&event.entity); + }); + } + }) +} + +#[event_handler] +fn chunk_entities_insert( + event: &EntityCreateEvent, + state: &State, + _query: &mut Query>, + world: &mut PreparedWorld, +) { + if let Some(position) = world.get_component::(event.entity) { + let chunk = position.chunk_pos(); + let mut map = state.chunk_entities.0.write(); + + map.entry(chunk) + .or_insert_with(|| vec![]) + .push(event.entity); + } +} + +#[event_handler] +fn chunk_entities_remove(event: &EntityDeleteEvent, state: &State) { + if let Some(position) = event.position { + let chunk = position.chunk_pos(); + let mut map = state.chunk_entities.0.write(); + + map.entry(chunk) + .or_insert_with(|| vec![]) + .remove_item(&event.entity); + } +} diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index 9ac618301..ceaf88f98 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -16,10 +16,17 @@ pub struct EntityId(pub i32); /// Entity ID counter, used to create new entity IDs. pub static ENTITY_ID_COUNTER: AtomicI32 = AtomicI32::new(0); -/// Event triggered when an entity is removed. +/// Event triggered when an entity is created. #[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct EntityCreateEvent { + pub entity: Entity, +} + +/// Event triggered when an entity is removed. +#[derive(Debug, Clone, Copy, PartialEq)] pub struct EntityDeleteEvent { - pub(crate) entity: Entity, + pub entity: Entity, + pub position: Option, } /// Event triggered when an entity moves. @@ -94,4 +101,5 @@ pub fn base(state: &State, position: Position) -> EntityBuilder { .with_component(position) .with_component(PreviousPosition(position)) .with_component(Velocity::default()) + .with_exec(|_, scheduler, entity| scheduler.trigger(EntityCreateEvent { entity })) } diff --git a/server/src/lib.rs b/server/src/lib.rs index feb10d04d..c51ed7241 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -91,6 +91,8 @@ //! chunk packets, inventory, time, nearby entities, etc. `PlayerJoinEvent` //! is used to send this data. +#![feature(vec_remove_item)] + #[macro_use] extern crate log; #[macro_use] diff --git a/server/src/network.rs b/server/src/network.rs index 8f2deee72..f45c4faaa 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -6,12 +6,13 @@ //! from players and allows systems to poll for packets //! received of a given type. +use crate::entity::EntityDeleteEvent; use crate::io::{ListenerToServerMessage, NetworkIoManager, ServerToWorkerMessage}; use crate::player; use crate::state::State; use crossbeam::Receiver; use feather_core::network::cast_packet; -use feather_core::{Packet, PacketType}; +use feather_core::{Packet, PacketType, Position}; use futures::channel::mpsc::UnboundedSender; use legion::entity::Entity; use legion::query::Read; @@ -179,8 +180,13 @@ pub fn network_( while let Ok(msg) = network.receiver.try_recv() { match msg { ServerToWorkerMessage::NotifyDisconnect(_) => { - state.exec(move |world| { + state.exec_with_scheduler(move |world, scheduler| { assert!(world.delete(entity), "player already deleted"); + let position = *world.get_component::(entity).unwrap(); + scheduler.trigger(EntityDeleteEvent { + entity, + position: Some(position), + }); }); } ServerToWorkerMessage::NotifyPacketReceived(packet) => { diff --git a/server/src/state.rs b/server/src/state.rs index ad2df82da..92f893bc4 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,3 +1,4 @@ +use crate::chunk_entities::ChunkEntities; use crate::config::Config; use crate::lazy::{EntityBuilder, Lazy}; use feather_blocks::Block; @@ -24,6 +25,7 @@ pub struct State { pub config: Arc, pub chunk_map: ChunkMap, pub level: LevelData, + pub chunk_entities: ChunkEntities, lazy: Lazy, } @@ -34,6 +36,7 @@ impl State { config, chunk_map, level, + chunk_entities: ChunkEntities::default(), lazy: Lazy::default(), } } From b51e2d4046ea9f70c7c5358ed489dcdc95858320 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sun, 22 Dec 2019 13:48:26 -0700 Subject: [PATCH 30/37] Implement State broadcast methods --- server/src/state.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/server/src/state.rs b/server/src/state.rs index 92f893bc4..1db31a49f 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,10 +1,14 @@ use crate::chunk_entities::ChunkEntities; +use crate::chunk_logic::ChunkHolders; use crate::config::Config; use crate::lazy::{EntityBuilder, Lazy}; +use crate::network::Network; use feather_blocks::Block; use feather_core::level::LevelData; use feather_core::world::ChunkMap; -use feather_core::{BlockPosition, Chunk, ChunkPosition}; +use feather_core::{BlockPosition, Chunk, ChunkPosition, Packet, Position}; +use legion::entity::Entity; +use legion::query::{IntoQuery, Read}; use legion::world::World; use parking_lot::RwLockReadGuard; use std::sync::Arc; @@ -56,6 +60,50 @@ impl State { self.lazy.create_entity() } + /// Lazily broadcasts a packet to all clients able to see the given entity. + /// + /// The packet will not be sent to `neq`. + pub fn broadcast_entity_update( + &self, + entity: Entity, + packet: P, + neq: Option, + ) { + self.exec_with_scheduler(move |world, scheduler| { + // Use ChunkHolders to determine which players have a hold on the entity's + // chunk, which would allow them to see the entity. + let chunk_holders = scheduler.resources().get::(); + + if let Some(position) = world.get_component::(entity) { + let holders = chunk_holders.holders_for(position.chunk_pos()); + + holders.map(|entities| { + for entity in entities { + if let Some(network) = world.get_component::(*entity) { + if neq.map_or(true, |neq| *entity != neq) { + network.send(packet.clone()); + } + } + } + }); + } + }); + } + + /// Lazily broadcasts a packet to all clients. + pub fn broadcast_global(&self, packet: P, neq: Option) { + self.exec(move |world| { + // Standard Legion queries! How rare. + let query = >::query(); + + query.par_entities_for_each(world, |(entity, network)| { + if neq.map_or(true, |neq| entity != neq) { + network.send(packet.clone()); + } + }); + }); + } + /// See `Lazy::flush()`. pub fn flush(&self, world: &mut World, scheduler: &mut Scheduler) { self.lazy.flush(world, scheduler); From 9562e4f4ece2f17350998549dedf07043f22f130 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 23 Dec 2019 11:57:19 -0700 Subject: [PATCH 31/37] Implement movement broadcaster --- Cargo.lock | 52 +++++++-- server/Cargo.toml | 4 +- server/src/broadcasters/mod.rs | 7 ++ server/src/broadcasters/movement.rs | 159 ++++++++++++++++++++++++++++ server/src/lib.rs | 1 + 5 files changed, 210 insertions(+), 13 deletions(-) create mode 100644 server/src/broadcasters/mod.rs create mode 100644 server/src/broadcasters/movement.rs diff --git a/Cargo.lock b/Cargo.lock index 75079414b..1bd82fba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -406,6 +406,14 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-deque" version = "0.7.1" @@ -436,6 +444,14 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-queue" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.6.6" @@ -445,6 +461,16 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "csv" version = "1.1.1" @@ -694,7 +720,7 @@ dependencies = [ "humantime-serde 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)", + "legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=0f67adc237af35799df173f31a2c238b3d8010a2)", "lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", "mojang-api 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -723,7 +749,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1184,10 +1210,11 @@ dependencies = [ [[package]] name = "legion" version = "0.2.1" -source = "git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280#15d2b5c47b2a935dbf238698b9d7c299f3824280" +source = "git+https://github.com/TomGillen/legion?rev=0f67adc237af35799df173f31a2c238b3d8010a2#0f67adc237af35799df173f31a2c238b3d8010a2" dependencies = [ "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "derivative 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "downcast-rs 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2575,7 +2602,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6#15f8aa965a667a6b7160e60828f935b17c6ac7e6" +source = "git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90#8f9cae2b2787406f28b5a4c4ef874228f018af90" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2585,20 +2612,20 @@ dependencies = [ "hashbrown 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)", + "legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=0f67adc237af35799df173f31a2c238b3d8010a2)", "mopa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "rayon 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6#15f8aa965a667a6b7160e60828f935b17c6ac7e6" +source = "git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90#8f9cae2b2787406f28b5a4c4ef874228f018af90" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3003,10 +3030,13 @@ dependencies = [ "checksum criterion-plot 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccdc6ce8bbe352ca89025bee672aa6d24f4eb8c53e3a8b5d1bc58011da072a2" "checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c" "checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa" +"checksum crossbeam-channel 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "acec9a3b0b3559f15aee4f90746c4e5e293b701c0f7d3925d24e01645267b68c" "checksum crossbeam-deque 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b18cd2e169ad86297e6bc0ad9aa679aee9daa4f19e8163860faf7c164e4f5a71" "checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" "checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" @@ -3070,7 +3100,7 @@ dependencies = [ "checksum js-sys 0.3.28 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc9a97d7cec30128fd8b28a7c1f9df1c001ceb9b441e2b755e24130a6b43c79" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" -"checksum legion 0.2.1 (git+https://github.com/feather-rs/legion?rev=15d2b5c47b2a935dbf238698b9d7c299f3824280)" = "" +"checksum legion 0.2.1 (git+https://github.com/TomGillen/legion?rev=0f67adc237af35799df173f31a2c238b3d8010a2)" = "" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" "checksum libm 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" @@ -3220,8 +3250,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=15f8aa965a667a6b7160e60828f935b17c6ac7e6)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/server/Cargo.toml b/server/Cargo.toml index afdf8899f..81d8f766f 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -20,8 +20,8 @@ feather-item-block = { path = "../item_block" } feather-codegen = { path = "../codegen" } # Core ECS + systems -legion = { git = "https://github.com/feather-rs/legion", rev = "15d2b5c47b2a935dbf238698b9d7c299f3824280" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "15f8aa965a667a6b7160e60828f935b17c6ac7e6", features = ["system-registry"] } +legion = { git = "https://github.com/TomGillen/legion", rev = "0f67adc237af35799df173f31a2c238b3d8010a2" } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "8f9cae2b2787406f28b5a4c4ef874228f018af90", features = ["system-registry"] } # tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading diff --git a/server/src/broadcasters/mod.rs b/server/src/broadcasters/mod.rs new file mode 100644 index 000000000..d792cd490 --- /dev/null +++ b/server/src/broadcasters/mod.rs @@ -0,0 +1,7 @@ +//! Systems which broadcast packets. +//! +//! There are two types of broadcasters: +//! * Those which broadcast packets to all online clients through `State::broadcast_global()`. +//! * Those which broadcast packets to all clients who can see a given entity through `State::broadcast_entity_update()`. + +pub mod movement; diff --git a/server/src/broadcasters/movement.rs b/server/src/broadcasters/movement.rs new file mode 100644 index 000000000..cb258dbb3 --- /dev/null +++ b/server/src/broadcasters/movement.rs @@ -0,0 +1,159 @@ +//! Broadcasting of movement updates. + +use crate::chunk_logic::ChunkHolders; +use crate::entity::{EntityId, EntityMoveEvent}; +use crate::network::Network; +use crossbeam::atomic::AtomicCell; +use feather_core::network::packet::implementation::{ + EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, +}; +use feather_core::{Packet, Position}; +use hashbrown::HashMap; +use legion::entity::Entity; +use legion::query::{Read, Write}; +use rayon::prelude::*; +use smallvec::SmallVec; +use tonks::{PreparedWorld, Query}; + +/// Component containing the last sent positions of all entities for a given client. +/// This component is used to determine +/// the relative movement for an entity. +pub struct LastKnownPositions(HashMap>); + +/// System to broadcast when an entity moves. +#[event_handler] +fn broadcast_move( + events: &[EntityMoveEvent], + _query: &mut Query<( + Read, + Read, + Write, + Read, + )>, + world: &mut PreparedWorld, + chunk_holders: &ChunkHolders, +) { + events.par_iter().for_each(|event: &EntityMoveEvent| { + // Find position of entity. + let pos = *world.get_component::(event.entity).unwrap(); + + // Find clients which can see the entity. + let chunk = pos.chunk_pos(); + let clients = chunk_holders.holders_for(chunk).unwrap_or(&[]); + + let entity_id = world.get_component::(event.entity).unwrap().0; + + // For each client, send the position update relative to the client's last known + // position for the entity. If no `LastKnownPositions` entry exists for the entity, + // then the entity has not yet been sent to the client, so we do not send a position + // update. (When an entity is spawned on a client, the `LastKnownPositions` entry + // is inserted with the starting position.) + clients.par_iter().copied().for_each(|client: Entity| { + // Don't sent player's position to themself + if client == event.entity { + return; + } + + let last_known_positions = world.get_component::(client).unwrap(); + if let Some(last_position) = last_known_positions.0.get(&event.entity) { + let old_pos = last_position.load(); + + let packets = packets_for_movement_update(entity_id, old_pos, pos); + + let network = world.get_component::(client).unwrap(); + + packets.into_iter().for_each(|packet| { + network.send_boxed(packet); + }); + + // Update last known position. + last_position.store(pos); + } + }); + }); +} + +/// Returns the packet needed to notify a client +/// of a position update, from the old position to the new one. +#[allow(clippy::float_cmp)] +fn packets_for_movement_update( + entity_id: i32, + old_pos: Position, + new_pos: Position, +) -> SmallVec<[Box; 2]> { + if old_pos == new_pos { + return smallvec![]; + } + + let mut packets = smallvec![]; + + let has_moved = old_pos.x != new_pos.x || old_pos.y != new_pos.y || old_pos.z != new_pos.z; + let has_looked = old_pos.pitch != new_pos.pitch || old_pos.yaw != new_pos.yaw; + + if has_moved { + let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); + + if (rx == 0 && ry == 0 && rz == 0) && !has_looked { + // Because of floating point errors, + // the physics system may trigger an + // event when the distance moved is minuscule, + // which causes jittering on the client. + // Don't send the packet if it has no effect. + return smallvec![]; + } + + if has_looked { + let packet: Box = Box::new(EntityLookAndRelativeMove::new( + entity_id, + rx, + ry, + rz, + degrees_to_stops(new_pos.yaw), + degrees_to_stops(new_pos.pitch), + new_pos.on_ground, + )); + packets.push(packet); + } else { + let packet: Box = Box::new(EntityRelativeMove::new( + entity_id, + rx, + ry, + rz, + new_pos.on_ground, + )); + packets.push(packet); + } + } else { + let packet: Box = Box::new(EntityLook::new( + entity_id, + degrees_to_stops(new_pos.yaw), + degrees_to_stops(new_pos.pitch), + new_pos.on_ground, + )); + packets.push(packet); + } + + // Entity Head Look also needs to be sent if the entity turned its head + if has_looked { + let packet: Box = Box::new(EntityHeadLook::new( + entity_id, + degrees_to_stops(new_pos.yaw), + )); + packets.push(packet); + } + + packets +} + +/// Calculates the relative move fields +/// as used in the Entity Relative Move packets. +fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { + let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; + let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; + let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; + (x, y, z) +} + +fn degrees_to_stops(degs: f32) -> u8 { + ((degs / 360.0) * 256.0) as u8 +} diff --git a/server/src/lib.rs b/server/src/lib.rs index c51ed7241..908ef372c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -145,6 +145,7 @@ use tonks::{Resources, Scheduler}; #[global_allocator] static ALLOC: System = System; +pub mod broadcasters; pub mod chunk_entities; pub mod chunk_logic; pub mod chunk_worker; From 195544f97dcb6d58a2f477c834a1fbbe94ec4190 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Mon, 23 Dec 2019 13:33:02 -0700 Subject: [PATCH 32/37] Start on broadcasting entities --- Cargo.lock | 12 +-- core/src/network/packet/implementation.rs | 4 +- server/Cargo.toml | 2 +- server/src/broadcasters/entity_creation.rs | 24 +++++ server/src/broadcasters/mod.rs | 3 +- server/src/broadcasters/movement.rs | 15 +-- server/src/entity/mod.rs | 53 ++++++++++- server/src/lib.rs | 1 + server/src/player/mod.rs | 67 +++++++++++++- server/src/state.rs | 44 +++++++++ server/src/util.rs | 17 ++++ server/src/view.rs | 101 ++++++++++++++++++--- 12 files changed, 303 insertions(+), 40 deletions(-) create mode 100644 server/src/broadcasters/entity_creation.rs create mode 100644 server/src/util.rs diff --git a/Cargo.lock b/Cargo.lock index 1bd82fba4..d33272ac8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,7 +749,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2602,7 +2602,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90#8f9cae2b2787406f28b5a4c4ef874228f018af90" +source = "git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e#39793d2bf2643802df006725ebff2f71795cd41e" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2619,13 +2619,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90#8f9cae2b2787406f28b5a4c4ef874228f018af90" +source = "git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e#39793d2bf2643802df006725ebff2f71795cd41e" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3250,8 +3250,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=8f9cae2b2787406f28b5a4c4ef874228f018af90)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/core/src/network/packet/implementation.rs b/core/src/network/packet/implementation.rs index 6b127460b..72a7a5ca0 100644 --- a/core/src/network/packet/implementation.rs +++ b/core/src/network/packet/implementation.rs @@ -1658,8 +1658,8 @@ impl Default for CombatEventType { #[derive(AsAny, new, Clone)] pub struct PlayerInfo { - action: PlayerInfoAction, - uuid: Uuid, + pub action: PlayerInfoAction, + pub uuid: Uuid, } impl Packet for PlayerInfo { diff --git a/server/Cargo.toml b/server/Cargo.toml index 81d8f766f..039eaf9e0 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,7 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "0f67adc237af35799df173f31a2c238b3d8010a2" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "8f9cae2b2787406f28b5a4c4ef874228f018af90", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "39793d2bf2643802df006725ebff2f71795cd41e", features = ["system-registry"] } # tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading diff --git a/server/src/broadcasters/entity_creation.rs b/server/src/broadcasters/entity_creation.rs new file mode 100644 index 000000000..6f48dc1e2 --- /dev/null +++ b/server/src/broadcasters/entity_creation.rs @@ -0,0 +1,24 @@ +use crate::entity::{CreationPacketCreator, EntityCreateEvent}; +use crate::state::State; +use legion::query::Read; +use rayon::prelude::*; +use tonks::{PreparedWorld, QueryAccessor}; + +/// When an entity is created and has a `CreationPacketCreator`, +/// broadcasts the packet to all online clients. +#[event_handler] +fn broadcast_entity_creation( + events: &[EntityCreateEvent], + state: &State, + accessor: &QueryAccessor>, + world: &mut PreparedWorld, +) { + events.par_iter().for_each(|event: &EntityCreateEvent| { + if let Some(accessor) = accessor.find(event.entity) { + if let Some(packet_creator) = accessor.get_component::(world) { + let packet = packet_creator.get(&accessor, world); + state.broadcast_global_boxed(packet, None); + } + } + }); +} diff --git a/server/src/broadcasters/mod.rs b/server/src/broadcasters/mod.rs index d792cd490..643b5e2fb 100644 --- a/server/src/broadcasters/mod.rs +++ b/server/src/broadcasters/mod.rs @@ -1,7 +1,8 @@ -//! Systems which broadcast packets. +//! Systems which broadcast packets based on events. //! //! There are two types of broadcasters: //! * Those which broadcast packets to all online clients through `State::broadcast_global()`. //! * Those which broadcast packets to all clients who can see a given entity through `State::broadcast_entity_update()`. +pub mod entity_creation; pub mod movement; diff --git a/server/src/broadcasters/movement.rs b/server/src/broadcasters/movement.rs index cb258dbb3..0393486d8 100644 --- a/server/src/broadcasters/movement.rs +++ b/server/src/broadcasters/movement.rs @@ -3,6 +3,7 @@ use crate::chunk_logic::ChunkHolders; use crate::entity::{EntityId, EntityMoveEvent}; use crate::network::Network; +use crate::util::{calculate_relative_move, degrees_to_stops}; use crossbeam::atomic::AtomicCell; use feather_core::network::packet::implementation::{ EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, @@ -18,6 +19,7 @@ use tonks::{PreparedWorld, Query}; /// Component containing the last sent positions of all entities for a given client. /// This component is used to determine /// the relative movement for an entity. +#[derive(Default)] pub struct LastKnownPositions(HashMap>); /// System to broadcast when an entity moves. @@ -144,16 +146,3 @@ fn packets_for_movement_update( packets } - -/// Calculates the relative move fields -/// as used in the Entity Relative Move packets. -fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { - let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; - let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; - let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; - (x, y, z) -} - -fn degrees_to_stops(degs: f32) -> u8 { - ((degs / 360.0) * 256.0) as u8 -} diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index ceaf88f98..d83c9d4a2 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -2,12 +2,12 @@ use crate::lazy::EntityBuilder; use crate::state::State; -use feather_core::Position; +use feather_core::{Packet, Position}; use legion::prelude::Entity; use legion::query::{Read, Write}; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicI32, Ordering}; -use tonks::{PreparedWorld, Query}; +use tonks::{EntityAccessor, PreparedWorld, Query}; /// ID of an entity. This value is generally unique. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -64,7 +64,7 @@ impl DerefMut for Velocity { /// /// Note that unnamed entities do not have this component. #[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct NameComponent(pub String); +pub struct Name(pub String); /// Position of an entity on the last tick. /// @@ -72,6 +72,53 @@ pub struct NameComponent(pub String); #[derive(Debug, Clone, Copy)] pub struct PreviousPosition(pub Position); +pub trait PacketCreatorFn: + Fn(&EntityAccessor, &PreparedWorld) -> Box + Send + Sync + 'static +{ +} +impl PacketCreatorFn for F where + F: Fn(&EntityAccessor, &PreparedWorld) -> Box + Send + Sync + 'static +{ +} + +/// Component which defines a function returning a packet to send +/// to clients when the entity comes within range. This packet +/// spawns the entity on the client. +pub struct SpawnPacketCreator(pub &'static dyn PacketCreatorFn); + +impl SpawnPacketCreator { + /// Returns the packet to send to clients when the entity is to be + /// sent to the client. + pub fn get(&self, accessor: &EntityAccessor, world: &PreparedWorld) -> Box { + let f = self.0; + + f(accessor, world) + } +} + +/// Component which defines a function returning a packet to send +/// to _all_ clients when the entity is created or the client joins. +/// This packet is sent before that returned by `SpawnPacketCreator`, +/// and it differs in that the packet is broadcasted globally +/// rather than to nearby clients. +/// +/// Another difference is that the packet from `SpawnPacketCreator` is not sent +/// to its own entity, while that from `CreationPacketCreator` is. +/// +/// An example of a use case for this packet is the `PlayerInfo` packet +/// sent when a player joins—it is sent to all players, not just those +/// that are able to see the player. +pub struct CreationPacketCreator(pub &'static dyn PacketCreatorFn); + +impl CreationPacketCreator { + /// Returns the packet to send to clients when the entity is created. + pub fn get(&self, accessor: &EntityAccessor, world: &PreparedWorld) -> Box { + let f = self.0; + + f(accessor, world) + } +} + #[event_handler] pub fn position_reset( events: &[EntityMoveEvent], diff --git a/server/src/lib.rs b/server/src/lib.rs index 908ef372c..480452e3e 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -161,6 +161,7 @@ pub mod player; pub mod shutdown; pub mod state; pub mod time; +pub mod util; pub mod view; pub mod worldgen; diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index d7877b524..4e6f1a41f 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -1,14 +1,20 @@ //! Systems and components specific to player entities. +use crate::broadcasters::movement::LastKnownPositions; use crate::chunk_logic::ChunkHolder; use crate::entity; -use crate::entity::NameComponent; +use crate::entity::{CreationPacketCreator, EntityId, Name, SpawnPacketCreator}; use crate::io::NewClientInfo; use crate::join::Joined; use crate::network::Network; use crate::state::State; +use crate::util::degrees_to_stops; +use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction, SpawnPlayer}; +use feather_core::{Gamemode, Packet, Position}; use legion::entity::Entity; use mojang_api::ProfileProperty; +use tonks::{EntityAccessor, PreparedWorld}; +use uuid::Uuid; /// Profile properties of a player. #[derive(Debug, Clone)] @@ -39,11 +45,68 @@ pub fn create(state: &State, info: NewClientInfo) { }) .with_component(info.ip) .with_component(ProfileProperties(info.profile)) - .with_component(NameComponent(info.username)) + .with_component(Name(info.username)) .with_component(ChunkHolder::default()) .with_component(Joined(false)) + .with_component(LastKnownPositions::default()) + .with_component(SpawnPacketCreator(&create_spawn_packet)) + .with_component(CreationPacketCreator(&create_initialization_packet)) .with_exec(|_, scheduler, player| { scheduler.trigger(PlayerJoinEvent { player }); }) .build(); } + +/// Function to create a `SpawnPlayer` packet to spawn the player. +fn create_spawn_packet(accessor: &EntityAccessor, world: &PreparedWorld) -> Box { + let entity_id = accessor.get_component::(world).unwrap().0; + let player_uuid = *accessor.get_component::(world).unwrap(); + let pos = *accessor.get_component::(world).unwrap(); + + // TODO: metadata + + let packet = SpawnPlayer { + entity_id, + player_uuid, + x: pos.x, + y: pos.y, + z: pos.z, + yaw: degrees_to_stops(pos.yaw), + pitch: degrees_to_stops(pos.pitch), + metadata: Default::default(), + }; + Box::new(packet) +} + +/// Function to create a `PlayerInfo` packet to broadcast when the player joins. +fn create_initialization_packet( + accessor: &EntityAccessor, + world: &PreparedWorld, +) -> Box { + let name = accessor.get_component::(world).unwrap(); + let props = accessor.get_component::(world).unwrap(); + let uuid = *accessor.get_component::(world).unwrap(); + + let props = props + .0 + .iter() + .map(|prop| { + ( + prop.name.clone(), + prop.value.clone(), + prop.signature.clone(), + ) + }) + .collect::>(); + + let display_name = json!({ + "text": name.0 + }) + .to_string(); + + let action = + PlayerInfoAction::AddPlayer(name.0.clone(), props, Gamemode::Creative, 50, display_name); + + let packet = PlayerInfo { action, uuid }; + Box::new(packet) +} diff --git a/server/src/state.rs b/server/src/state.rs index 1db31a49f..7fc3252e1 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -90,6 +90,36 @@ impl State { }); } + /// Lazily broadcasts a boxed packet to all clients able to see the given entity. + /// + /// The packet will not be sent to `neq`. + pub fn broadcast_entity_update_boxed( + &self, + entity: Entity, + packet: Box, + neq: Option, + ) { + self.exec_with_scheduler(move |world, scheduler| { + // Use ChunkHolders to determine which players have a hold on the entity's + // chunk, which would allow them to see the entity. + let chunk_holders = scheduler.resources().get::(); + + if let Some(position) = world.get_component::(entity) { + let holders = chunk_holders.holders_for(position.chunk_pos()); + + holders.map(|entities| { + for entity in entities { + if let Some(network) = world.get_component::(*entity) { + if neq.map_or(true, |neq| *entity != neq) { + network.send_boxed(packet.box_clone()); + } + } + } + }); + } + }); + } + /// Lazily broadcasts a packet to all clients. pub fn broadcast_global(&self, packet: P, neq: Option) { self.exec(move |world| { @@ -104,6 +134,20 @@ impl State { }); } + /// Lazily broadcasts a boxed packet to all clients. + pub fn broadcast_global_boxed(&self, packet: Box, neq: Option) { + self.exec(move |world| { + // Standard Legion queries! How rare. + let query = >::query(); + + query.par_entities_for_each(world, |(entity, network)| { + if neq.map_or(true, |neq| entity != neq) { + network.send_boxed(packet.box_clone()); + } + }); + }); + } + /// See `Lazy::flush()`. pub fn flush(&self, world: &mut World, scheduler: &mut Scheduler) { self.lazy.flush(world, scheduler); diff --git a/server/src/util.rs b/server/src/util.rs new file mode 100644 index 000000000..6567e5d57 --- /dev/null +++ b/server/src/util.rs @@ -0,0 +1,17 @@ +//! Assorted utility functions. + +use feather_core::Position; + +/// Calculates the relative move fields +/// as used in the Entity Relative Move packets. +pub fn calculate_relative_move(old: Position, current: Position) -> (i16, i16, i16) { + let x = ((current.x * 32.0 - old.x * 32.0) * 128.0) as i16; + let y = ((current.y * 32.0 - old.y * 32.0) * 128.0) as i16; + let z = ((current.z * 32.0 - old.z * 32.0) * 128.0) as i16; + (x, y, z) +} + +/// Converts degrees to stops as used in the protocol. +pub fn degrees_to_stops(degs: f32) -> u8 { + ((degs / 360.0) * 256.0) as u8 +} diff --git a/server/src/view.rs b/server/src/view.rs index 298123a59..510e93c0e 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -22,12 +22,12 @@ use crate::chunk_logic::{ ChunkHolder, ChunkHolderReleaseEvent, ChunkHolders, ChunkLoadEvent, ChunkWorkerHandle, }; use crate::config::Config; -use crate::entity::{EntityMoveEvent, PreviousPosition}; +use crate::entity::{EntityId, EntityMoveEvent, PreviousPosition, SpawnPacketCreator}; use crate::network::Network; use crate::player::PlayerJoinEvent; use crate::state::State; use chashmap::CHashMap; -use feather_core::network::packet::implementation::{ChunkData, UnloadChunk}; +use feather_core::network::packet::implementation::{ChunkData, DestroyEntities, UnloadChunk}; use feather_core::{Chunk, ChunkPosition, Position}; use hashbrown::HashSet; use legion::entity::Entity; @@ -35,7 +35,7 @@ use legion::query::{Read, Write}; use parking_lot::Mutex; use rayon::prelude::*; use smallvec::SmallVec; -use tonks::{PreparedWorld, Query, Trigger}; +use tonks::{PreparedWorld, Query, QueryAccessor, Trigger}; /// Event triggered when a player's view is updated, i.e. when they /// cross into a new chunk or when they join. @@ -47,6 +47,10 @@ pub struct ViewUpdateEvent { /// The old chunk, or `None` if there was no old chunk /// (i.e. this player just joined). pub old_chunk: Option, + /// Old visible chunks. + pub visible_old: HashSet, + /// New visible chunks. + pub visible_new: HashSet, } /// Event triggered when a chunk is sent to a player. @@ -63,6 +67,7 @@ fn view_update( events: &[EntityMoveEvent], _query: &mut Query<(Read, Read)>, world: &mut PreparedWorld, + state: &State, trigger: &mut Trigger, ) { let trigger = Mutex::new(trigger); @@ -73,12 +78,18 @@ fn view_update( .unwrap() .0; + // Find the old chunks and new chunks. + let visible_new = chunks_within_view_distance(&state.config, pos.chunk_pos()); + let visible_old = chunks_within_view_distance(&state.config, prev_pos.chunk_pos()); + if pos.chunk_pos() != prev_pos.chunk_pos() { // New chunk: trigger view update. let event = ViewUpdateEvent { player: event.entity, new_chunk: pos.chunk_pos(), old_chunk: Some(prev_pos.chunk_pos()), + visible_old, + visible_new, }; trigger.lock().trigger(event); } @@ -92,13 +103,19 @@ fn view_update_on_join( _query: &mut Query>, world: &mut PreparedWorld, trigger: &mut Trigger, + state: &State, ) { let position = *world.get_component::(event.player).unwrap(); + // Find the visible chunks. + let visible_new = chunks_within_view_distance(&state.config, position.chunk_pos()); + trigger.trigger(ViewUpdateEvent { player: event.player, new_chunk: position.chunk_pos(), old_chunk: None, + visible_new, + visible_old: HashSet::new(), // No chunks were previously visible, since the player just joined }); } @@ -117,15 +134,8 @@ fn view_handle_chunks( chunk_send_trigger: &mut Trigger, ) { events.iter().for_each(|event| { - // Find the old chunks and new chunks. - let new_chunks = chunks_within_view_distance(&state.config, event.new_chunk); - let old_chunks = match event.old_chunk { - Some(chunk) => chunks_within_view_distance(&state.config, chunk), - None => HashSet::new(), - }; - - let to_send = new_chunks.difference(&old_chunks); - let to_unload = old_chunks.difference(&new_chunks); + let to_send = event.visible_new.difference(&event.visible_old); + let to_unload = event.visible_old.difference(&event.visible_new); let network = world.get_component::(event.player).unwrap(); let mut holder = @@ -166,6 +176,73 @@ fn view_handle_chunks( }); } +/// System which sends new entities and removes +/// old entities on the client when the player's +/// view is updated. +/// +/// Before this event handler is run, `crate::broadcast::entity_creation::broadcast_entity_creation` +/// will run, sending entity initialization packets before spawn packets as dictated +/// by the protocol. +#[event_handler] +fn view_handle_entities( + events: &[ViewUpdateEvent], + state: &State, + _query: &mut Query<(Read, Read)>, + accessor: &QueryAccessor>, + world: &mut PreparedWorld, +) { + events.par_iter().for_each(|event: &ViewUpdateEvent| { + let to_send = event.visible_new.difference(&event.visible_old); + let to_unload = event.visible_old.difference(&event.visible_new); + + let network = world.get_component::(event.player).unwrap(); + + // Send new entities. + to_send.copied().for_each(|chunk| { + let entities = state.chunk_entities.entities_in_chunk(chunk); + + entities.iter().copied().for_each(|entity| { + // Don't send client to themself. + if entity == event.player { + return; + } + + // Attempt to create spawn packet for this entity. + if let Some(accessor) = accessor.find(entity) { + if let Some(packet_creator) = + accessor.get_component::(world) + { + // Send packet. + let packet = packet_creator.get(&accessor, world); + network.send_boxed(packet); + } + } + }); + }); + + // Remove old entities. + let mut to_delete = vec![]; + for chunk in to_unload.copied() { + for entity in state + .chunk_entities + .entities_in_chunk(chunk) + .iter() + .copied() + { + let id = world.get_component::(entity).unwrap().0; + to_delete.push(id); + } + } + + if !to_delete.is_empty() { + let packet = DestroyEntities { + entity_ids: to_delete, + }; + network.send(packet); + } + }); +} + /// Resource containing a mapping from chunks -> sets of players indicating /// which chunks are pending to send to a given player. #[derive(Default, Resource)] From a78e2830e793d22e6912509a34daa0fb2ddebcac Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 4 Jan 2020 20:03:20 -0700 Subject: [PATCH 33/37] Implement keepalives; fix movement + entity broadcasting --- server/src/broadcasters/entity_creation.rs | 63 +++++++++++++++++++--- server/src/broadcasters/keepalive.rs | 12 +++++ server/src/broadcasters/mod.rs | 1 + server/src/broadcasters/movement.rs | 2 +- server/src/state.rs | 20 +++++++ server/src/view.rs | 6 +++ 6 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 server/src/broadcasters/keepalive.rs diff --git a/server/src/broadcasters/entity_creation.rs b/server/src/broadcasters/entity_creation.rs index 6f48dc1e2..88751b5d7 100644 --- a/server/src/broadcasters/entity_creation.rs +++ b/server/src/broadcasters/entity_creation.rs @@ -1,24 +1,75 @@ -use crate::entity::{CreationPacketCreator, EntityCreateEvent}; +use crate::chunk_logic::ChunkHolders; +use crate::entity::{CreationPacketCreator, EntityCreateEvent, SpawnPacketCreator}; +use crate::network::Network; +use crate::player::PlayerJoinEvent; use crate::state::State; +use feather_core::Position; use legion::query::Read; use rayon::prelude::*; -use tonks::{PreparedWorld, QueryAccessor}; +use tonks::{PreparedWorld, Query, QueryAccessor}; -/// When an entity is created and has a `CreationPacketCreator`, -/// broadcasts the packet to all online clients. +/// When an entity is created and has a `CreationPacketCreator` and/or `SpawnPacketCreator`, +/// broadcasts the packets to all online clients. #[event_handler] fn broadcast_entity_creation( events: &[EntityCreateEvent], state: &State, - accessor: &QueryAccessor>, + accessor1: &QueryAccessor>, + accessor2: &QueryAccessor>, + _query: &mut Query>, world: &mut PreparedWorld, + holders: &ChunkHolders, ) { events.par_iter().for_each(|event: &EntityCreateEvent| { - if let Some(accessor) = accessor.find(event.entity) { + if let Some(accessor) = accessor1.find(event.entity) { if let Some(packet_creator) = accessor.get_component::(world) { let packet = packet_creator.get(&accessor, world); state.broadcast_global_boxed(packet, None); } } + + if let Some(accessor) = accessor2.find(event.entity) { + if let Some(packet_creator) = accessor.get_component::(world) { + let packet = packet_creator.get(&accessor, world); + state.broadcast_entity_update_boxed(event.entity, packet, Some(event.entity)); + } + } + + // Register entity sends + let chunk = world + .get_component::(event.entity) + .unwrap() + .chunk_pos(); + for entity in holders.holders_for(chunk).unwrap_or(&[]) { + state.register_entity_send(event.entity, *entity); + } + }); +} + +/// Wehn a player joins, sends existing entities to the player. +/// +/// This only handles init packets (PlayerInfo, etc.)—spawn packets +/// are handled by the view update mechanism. +#[event_handler] +fn broadcast_existing_entities( + events: &[PlayerJoinEvent], + accessor: &QueryAccessor>, + query: &mut Query>, + _query2: &mut Query>, + world: &mut PreparedWorld, + state: &State, +) { + // TODO: change to par_iter when legion implements immutable queries + events.iter().for_each(|event: &PlayerJoinEvent| { + // Send init packets for all entities with a `CreationPacketCreator`. + let network = world.get_component::(event.player).unwrap(); + + query.par_entities_for_each_immutable(world, |(entity, packet_creator)| { + if let Some(accessor) = accessor.find(entity) { + let packet = packet_creator.get(&accessor, world); + network.send_boxed(packet); + state.register_entity_send(entity, event.player); + } + }); }); } diff --git a/server/src/broadcasters/keepalive.rs b/server/src/broadcasters/keepalive.rs new file mode 100644 index 000000000..8a10a8a7d --- /dev/null +++ b/server/src/broadcasters/keepalive.rs @@ -0,0 +1,12 @@ +use crate::state::State; +use crate::{TickCount, TPS}; +use feather_core::network::packet::implementation::KeepAliveClientbound; + +/// Broadcasts keep alives every second. +#[system] +fn broadcast_keepalive(state: &State, tick: &TickCount) { + if tick.0 % TPS == 0 { + let packet = KeepAliveClientbound { keep_alive_id: 0 }; + state.broadcast_global(packet, None); + } +} diff --git a/server/src/broadcasters/mod.rs b/server/src/broadcasters/mod.rs index 643b5e2fb..a5238912f 100644 --- a/server/src/broadcasters/mod.rs +++ b/server/src/broadcasters/mod.rs @@ -5,4 +5,5 @@ //! * Those which broadcast packets to all clients who can see a given entity through `State::broadcast_entity_update()`. pub mod entity_creation; +pub mod keepalive; pub mod movement; diff --git a/server/src/broadcasters/movement.rs b/server/src/broadcasters/movement.rs index 0393486d8..d43870d8e 100644 --- a/server/src/broadcasters/movement.rs +++ b/server/src/broadcasters/movement.rs @@ -20,7 +20,7 @@ use tonks::{PreparedWorld, Query}; /// This component is used to determine /// the relative movement for an entity. #[derive(Default)] -pub struct LastKnownPositions(HashMap>); +pub struct LastKnownPositions(pub HashMap>); /// System to broadcast when an entity moves. #[event_handler] diff --git a/server/src/state.rs b/server/src/state.rs index 7fc3252e1..3b81d4c57 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -1,8 +1,10 @@ +use crate::broadcasters::movement::LastKnownPositions; use crate::chunk_entities::ChunkEntities; use crate::chunk_logic::ChunkHolders; use crate::config::Config; use crate::lazy::{EntityBuilder, Lazy}; use crate::network::Network; +use crossbeam::atomic::AtomicCell; use feather_blocks::Block; use feather_core::level::LevelData; use feather_core::world::ChunkMap; @@ -195,4 +197,22 @@ impl State { .remove(pos); }); } + + /// Registers that an entity was sent to a player, updating some + /// data structures, such as LastKnownPositions. + pub fn register_entity_send(&self, entity: Entity, to: Entity) { + self.exec(move |world| { + let pos = *world.get_component(entity).unwrap(); + let mut positions = world.get_component_mut::(to).unwrap(); + positions.0.insert(entity, AtomicCell::new(pos)); + }); + } + + /// The opposite of `register_entity_send`. + pub fn register_entity_unload(&self, entity: Entity, on: Entity) { + self.exec(move |world| { + let mut positions = world.get_component_mut::(on).unwrap(); + positions.0.remove(&entity); + }) + } } diff --git a/server/src/view.rs b/server/src/view.rs index 510e93c0e..eec0f42f7 100644 --- a/server/src/view.rs +++ b/server/src/view.rs @@ -201,6 +201,10 @@ fn view_handle_entities( to_send.copied().for_each(|chunk| { let entities = state.chunk_entities.entities_in_chunk(chunk); + if !entities.is_empty() { + dbg!(event.player, &entities); + } + entities.iter().copied().for_each(|entity| { // Don't send client to themself. if entity == event.player { @@ -215,6 +219,7 @@ fn view_handle_entities( // Send packet. let packet = packet_creator.get(&accessor, world); network.send_boxed(packet); + state.register_entity_send(entity, event.player); } } }); @@ -231,6 +236,7 @@ fn view_handle_entities( { let id = world.get_component::(entity).unwrap().0; to_delete.push(id); + state.register_entity_unload(entity, event.player); } } From f90ce5592e2cfd53b80b43a26f209971c4e764f6 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 4 Jan 2020 20:38:12 -0700 Subject: [PATCH 34/37] Broadcast entity deletion; fix a bunch of panics --- Cargo.lock | 12 +++--- server/Cargo.toml | 2 +- server/src/broadcasters/entity_deletion.rs | 46 ++++++++++++++++++++++ server/src/broadcasters/mod.rs | 1 + server/src/broadcasters/movement.rs | 4 ++ server/src/entity/mod.rs | 3 ++ server/src/network.rs | 9 ++++- server/src/state.rs | 10 +++-- 8 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 server/src/broadcasters/entity_deletion.rs diff --git a/Cargo.lock b/Cargo.lock index d33272ac8..460225941 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -749,7 +749,7 @@ dependencies = [ "tokio 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "tokio-executor 0.2.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)", + "tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f)", "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2602,7 +2602,7 @@ dependencies = [ [[package]] name = "tonks" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e#39793d2bf2643802df006725ebff2f71795cd41e" +source = "git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f#ccdecae02e11fc6694bd252d48cce5794499736f" dependencies = [ "arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2619,13 +2619,13 @@ dependencies = [ "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)", + "tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f)", ] [[package]] name = "tonks-macros" version = "0.1.0" -source = "git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e#39793d2bf2643802df006725ebff2f71795cd41e" +source = "git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f#ccdecae02e11fc6694bd252d48cce5794499736f" dependencies = [ "proc-macro2 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3250,8 +3250,8 @@ dependencies = [ "checksum tokio-timer 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b97c1587fe71018eb245a4a9daa13a5a3b681bbc1f7fdadfe24720e141472c13" "checksum tokio-tls 0.3.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "566b4086589c7eebb86aa625d302ab80720ef2aa088649dcae18ec4d754cbd16" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" -"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)" = "" -"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=39793d2bf2643802df006725ebff2f71795cd41e)" = "" +"checksum tonks 0.1.0 (git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f)" = "" +"checksum tonks-macros 0.1.0 (git+https://github.com/feather-rs/tonks?rev=ccdecae02e11fc6694bd252d48cce5794499736f)" = "" "checksum tower-make 0.3.0-alpha.2a (registry+https://github.com/rust-lang/crates.io-index)" = "316d47dd40cde4ac5d88110eaf9a10a4e2a68612d9c056cd2aa24e37dcb484cd" "checksum tower-service 0.3.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "63ff37396cd966ce43bea418bfa339f802857495f797dafa00bea5b7221ebdfa" "checksum tracing 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c21ff9457accc293386c20e8f754d0b059e67e325edf2284f04230d125d7e5ff" diff --git a/server/Cargo.toml b/server/Cargo.toml index 039eaf9e0..c39e4e0ed 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -21,7 +21,7 @@ feather-codegen = { path = "../codegen" } # Core ECS + systems legion = { git = "https://github.com/TomGillen/legion", rev = "0f67adc237af35799df173f31a2c238b3d8010a2" } -tonks = { git = "https://github.com/feather-rs/tonks", rev = "39793d2bf2643802df006725ebff2f71795cd41e", features = ["system-registry"] } +tonks = { git = "https://github.com/feather-rs/tonks", rev = "ccdecae02e11fc6694bd252d48cce5794499736f", features = ["system-registry"] } # tonks = { path = "../../../dev/tonks", features = ["system-registry"] } # Concurrency/threading diff --git a/server/src/broadcasters/entity_deletion.rs b/server/src/broadcasters/entity_deletion.rs new file mode 100644 index 000000000..e5848c921 --- /dev/null +++ b/server/src/broadcasters/entity_deletion.rs @@ -0,0 +1,46 @@ +use crate::chunk_logic::ChunkHolders; +use crate::entity::EntityDeleteEvent; +use crate::network::Network; +use crate::player::Player; +use crate::state::State; +use feather_core::network::packet::implementation::{ + DestroyEntities, PlayerInfo, PlayerInfoAction, +}; +use legion::query::Read; +use rayon::prelude::*; +use tonks::{PreparedWorld, Query}; + +/// Broadcasts when an entity is deleted. +#[event_handler] +fn broadcast_entity_deletion( + events: &[EntityDeleteEvent], + holders: &ChunkHolders, + _query: &mut Query<(Read, Read)>, + world: &mut PreparedWorld, + state: &State, +) { + events.par_iter().for_each(|event: &EntityDeleteEvent| { + if let Some(pos) = event.position { + let chunk = pos.chunk_pos(); + + for entity in holders.holders_for(chunk).unwrap_or(&[]) { + if let Some(network) = world.get_component::(*entity) { + network.send(DestroyEntities { + entity_ids: vec![event.id.0], + }); + state.register_entity_unload(event.entity, *entity); + } + } + } + + // If entity was a player, broadcast PlayerInfo with delete status + if world.get_component::(event.entity).is_some() { + let packet = PlayerInfo { + action: PlayerInfoAction::RemovePlayer, + uuid: event.uuid, + }; + + state.broadcast_global(packet, None); + } + }); +} diff --git a/server/src/broadcasters/mod.rs b/server/src/broadcasters/mod.rs index a5238912f..a1d8be0e4 100644 --- a/server/src/broadcasters/mod.rs +++ b/server/src/broadcasters/mod.rs @@ -5,5 +5,6 @@ //! * Those which broadcast packets to all clients who can see a given entity through `State::broadcast_entity_update()`. pub mod entity_creation; +pub mod entity_deletion; pub mod keepalive; pub mod movement; diff --git a/server/src/broadcasters/movement.rs b/server/src/broadcasters/movement.rs index d43870d8e..ce057595e 100644 --- a/server/src/broadcasters/movement.rs +++ b/server/src/broadcasters/movement.rs @@ -56,6 +56,10 @@ fn broadcast_move( return; } + if !world.is_alive(client) { + return; + } + let last_known_positions = world.get_component::(client).unwrap(); if let Some(last_position) = last_known_positions.0.get(&event.entity) { let old_pos = last_position.load(); diff --git a/server/src/entity/mod.rs b/server/src/entity/mod.rs index d83c9d4a2..727852dce 100644 --- a/server/src/entity/mod.rs +++ b/server/src/entity/mod.rs @@ -8,6 +8,7 @@ use legion::query::{Read, Write}; use std::ops::{Deref, DerefMut}; use std::sync::atomic::{AtomicI32, Ordering}; use tonks::{EntityAccessor, PreparedWorld, Query}; +use uuid::Uuid; /// ID of an entity. This value is generally unique. #[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] @@ -27,6 +28,8 @@ pub struct EntityCreateEvent { pub struct EntityDeleteEvent { pub entity: Entity, pub position: Option, + pub id: EntityId, + pub uuid: Uuid, } /// Event triggered when an entity moves. diff --git a/server/src/network.rs b/server/src/network.rs index f45c4faaa..2921b7192 100644 --- a/server/src/network.rs +++ b/server/src/network.rs @@ -6,7 +6,7 @@ //! from players and allows systems to poll for packets //! received of a given type. -use crate::entity::EntityDeleteEvent; +use crate::entity::{EntityDeleteEvent, EntityId}; use crate::io::{ListenerToServerMessage, NetworkIoManager, ServerToWorkerMessage}; use crate::player; use crate::state::State; @@ -21,6 +21,7 @@ use parking_lot::{Mutex, MutexGuard}; use std::iter; use strum::EnumCount; use tonks::{PreparedWorld, Query}; +use uuid::Uuid; type QueuedPackets = Vec<(Entity, Box)>; @@ -181,12 +182,16 @@ pub fn network_( match msg { ServerToWorkerMessage::NotifyDisconnect(_) => { state.exec_with_scheduler(move |world, scheduler| { - assert!(world.delete(entity), "player already deleted"); let position = *world.get_component::(entity).unwrap(); + let id = *world.get_component::(entity).unwrap(); + let uuid = *world.get_component::(entity).unwrap(); scheduler.trigger(EntityDeleteEvent { entity, position: Some(position), + id, + uuid, }); + assert!(world.delete(entity), "player already deleted"); }); } ServerToWorkerMessage::NotifyPacketReceived(packet) => { diff --git a/server/src/state.rs b/server/src/state.rs index 3b81d4c57..1d0a8d676 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -203,16 +203,18 @@ impl State { pub fn register_entity_send(&self, entity: Entity, to: Entity) { self.exec(move |world| { let pos = *world.get_component(entity).unwrap(); - let mut positions = world.get_component_mut::(to).unwrap(); - positions.0.insert(entity, AtomicCell::new(pos)); + if let Some(mut positions) = world.get_component_mut::(to) { + positions.0.insert(entity, AtomicCell::new(pos)); + } }); } /// The opposite of `register_entity_send`. pub fn register_entity_unload(&self, entity: Entity, on: Entity) { self.exec(move |world| { - let mut positions = world.get_component_mut::(on).unwrap(); - positions.0.remove(&entity); + if let Some(mut positions) = world.get_component_mut::(on) { + positions.0.remove(&entity); + } }) } } From ae94e6e0c0f46dd85d967fe4586b67b55a247847 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sat, 4 Jan 2020 20:45:26 -0700 Subject: [PATCH 35/37] Implement player hand animations --- server/src/broadcasters/animation.rs | 21 +++++++++++++++++++++ server/src/broadcasters/mod.rs | 1 + server/src/packet_handlers/animation.rs | 20 ++++++++++++++++++++ server/src/packet_handlers/mod.rs | 1 + server/src/packet_handlers/movement.rs | 2 +- server/src/player/mod.rs | 9 ++++++++- 6 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 server/src/broadcasters/animation.rs create mode 100644 server/src/packet_handlers/animation.rs diff --git a/server/src/broadcasters/animation.rs b/server/src/broadcasters/animation.rs new file mode 100644 index 000000000..a239e3806 --- /dev/null +++ b/server/src/broadcasters/animation.rs @@ -0,0 +1,21 @@ +use crate::entity::EntityId; +use crate::player::PlayerAnimationEvent; +use crate::state::State; +use feather_core::network::packet::implementation::AnimationClientbound; +use legion::query::Read; +use tonks::{PreparedWorld, Query}; + +/// Broadcasts animations. +#[event_handler] +fn broadcast_animation( + event: &PlayerAnimationEvent, + state: &State, + _query: &mut Query>, + world: &mut PreparedWorld, +) { + let packet = AnimationClientbound { + entity_id: world.get_component::(event.player).unwrap().0, + animation: event.animation, + }; + state.broadcast_entity_update(event.player, packet, Some(event.player)); +} diff --git a/server/src/broadcasters/mod.rs b/server/src/broadcasters/mod.rs index a1d8be0e4..7da636247 100644 --- a/server/src/broadcasters/mod.rs +++ b/server/src/broadcasters/mod.rs @@ -4,6 +4,7 @@ //! * Those which broadcast packets to all online clients through `State::broadcast_global()`. //! * Those which broadcast packets to all clients who can see a given entity through `State::broadcast_entity_update()`. +mod animation; pub mod entity_creation; pub mod entity_deletion; pub mod keepalive; diff --git a/server/src/packet_handlers/animation.rs b/server/src/packet_handlers/animation.rs new file mode 100644 index 000000000..e42a10d3f --- /dev/null +++ b/server/src/packet_handlers/animation.rs @@ -0,0 +1,20 @@ +use crate::network::PacketQueue; +use crate::player::PlayerAnimationEvent; +use feather_core::network::packet::implementation::AnimationServerbound; +use feather_core::{ClientboundAnimation, Hand}; +use tonks::Trigger; + +/// Handles animation packets. +#[system] +fn handle_animation(queue: &PacketQueue, trigger: &mut Trigger) { + queue + .received::() + .for_each(|(player, packet)| { + let animation = match packet.hand { + Hand::Main => ClientboundAnimation::SwingMainArm, + Hand::Off => ClientboundAnimation::SwingOffhand, + }; + + trigger.trigger(PlayerAnimationEvent { player, animation }); + }); +} diff --git a/server/src/packet_handlers/mod.rs b/server/src/packet_handlers/mod.rs index f25137873..3af94b557 100644 --- a/server/src/packet_handlers/mod.rs +++ b/server/src/packet_handlers/mod.rs @@ -1,3 +1,4 @@ //! Systems which handle packets through `crate::network::PacketQueue`. +mod animation; mod movement; diff --git a/server/src/packet_handlers/movement.rs b/server/src/packet_handlers/movement.rs index ecd230a32..41240d4a1 100644 --- a/server/src/packet_handlers/movement.rs +++ b/server/src/packet_handlers/movement.rs @@ -13,7 +13,7 @@ struct Buf(Vec<(Entity, Position)>); /// Handles player movement packets. #[system] -fn movement( +fn handle_movement( queue: &PacketQueue, _query: &mut Query>, world: &mut PreparedWorld, diff --git a/server/src/player/mod.rs b/server/src/player/mod.rs index 4e6f1a41f..67bdab92a 100644 --- a/server/src/player/mod.rs +++ b/server/src/player/mod.rs @@ -10,7 +10,7 @@ use crate::network::Network; use crate::state::State; use crate::util::degrees_to_stops; use feather_core::network::packet::implementation::{PlayerInfo, PlayerInfoAction, SpawnPlayer}; -use feather_core::{Gamemode, Packet, Position}; +use feather_core::{ClientboundAnimation, Gamemode, Packet, Position}; use legion::entity::Entity; use mojang_api::ProfileProperty; use tonks::{EntityAccessor, PreparedWorld}; @@ -26,6 +26,13 @@ pub struct PlayerJoinEvent { pub player: Entity, } +/// Event triggered when a player causes an animation. +#[derive(Debug, Clone)] +pub struct PlayerAnimationEvent { + pub player: Entity, + pub animation: ClientboundAnimation, +} + /// Tag used to mark a player. /// /// Note that this is a _tag_, not a component. From f14870ee39069d62f51b753438dbdca98f617205 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sun, 5 Jan 2020 11:48:21 -0700 Subject: [PATCH 36/37] Fix weird movement behavior by not using parallel iterators in movement broadcaster --- server/src/broadcasters/keepalive.rs | 2 +- server/src/broadcasters/movement.rs | 22 +++++++++++----------- server/src/state.rs | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/server/src/broadcasters/keepalive.rs b/server/src/broadcasters/keepalive.rs index 8a10a8a7d..ddf748ab3 100644 --- a/server/src/broadcasters/keepalive.rs +++ b/server/src/broadcasters/keepalive.rs @@ -2,7 +2,7 @@ use crate::state::State; use crate::{TickCount, TPS}; use feather_core::network::packet::implementation::KeepAliveClientbound; -/// Broadcasts keep alives every second. +/// Broadcasts keepalives every second. #[system] fn broadcast_keepalive(state: &State, tick: &TickCount) { if tick.0 % TPS == 0 { diff --git a/server/src/broadcasters/movement.rs b/server/src/broadcasters/movement.rs index ce057595e..c0d3be05b 100644 --- a/server/src/broadcasters/movement.rs +++ b/server/src/broadcasters/movement.rs @@ -4,7 +4,6 @@ use crate::chunk_logic::ChunkHolders; use crate::entity::{EntityId, EntityMoveEvent}; use crate::network::Network; use crate::util::{calculate_relative_move, degrees_to_stops}; -use crossbeam::atomic::AtomicCell; use feather_core::network::packet::implementation::{ EntityHeadLook, EntityLook, EntityLookAndRelativeMove, EntityRelativeMove, }; @@ -20,7 +19,7 @@ use tonks::{PreparedWorld, Query}; /// This component is used to determine /// the relative movement for an entity. #[derive(Default)] -pub struct LastKnownPositions(pub HashMap>); +pub struct LastKnownPositions(pub HashMap); /// System to broadcast when an entity moves. #[event_handler] @@ -35,7 +34,7 @@ fn broadcast_move( world: &mut PreparedWorld, chunk_holders: &ChunkHolders, ) { - events.par_iter().for_each(|event: &EntityMoveEvent| { + events.iter().for_each(|event: &EntityMoveEvent| { // Find position of entity. let pos = *world.get_component::(event.entity).unwrap(); @@ -50,7 +49,7 @@ fn broadcast_move( // then the entity has not yet been sent to the client, so we do not send a position // update. (When an entity is spawned on a client, the `LastKnownPositions` entry // is inserted with the starting position.) - clients.par_iter().copied().for_each(|client: Entity| { + clients.iter().copied().for_each(|client: Entity| { // Don't sent player's position to themself if client == event.entity { return; @@ -60,11 +59,10 @@ fn broadcast_move( return; } - let last_known_positions = world.get_component::(client).unwrap(); - if let Some(last_position) = last_known_positions.0.get(&event.entity) { - let old_pos = last_position.load(); - - let packets = packets_for_movement_update(entity_id, old_pos, pos); + let mut last_known_positions = + unsafe { world.get_component_mut_unchecked::(client) }.unwrap(); + if let Some(old_pos) = last_known_positions.0.get_mut(&event.entity) { + let packets = packets_for_movement_update(entity_id, *old_pos, pos); let network = world.get_component::(client).unwrap(); @@ -73,7 +71,7 @@ fn broadcast_move( }); // Update last known position. - last_position.store(pos); + *old_pos = pos; } }); }); @@ -94,7 +92,9 @@ fn packets_for_movement_update( let mut packets = smallvec![]; let has_moved = old_pos.x != new_pos.x || old_pos.y != new_pos.y || old_pos.z != new_pos.z; - let has_looked = old_pos.pitch != new_pos.pitch || old_pos.yaw != new_pos.yaw; + let has_looked = old_pos.pitch != new_pos.pitch + || old_pos.yaw != new_pos.yaw + || old_pos.on_ground != new_pos.on_ground; if has_moved { let (rx, ry, rz) = calculate_relative_move(old_pos, new_pos); diff --git a/server/src/state.rs b/server/src/state.rs index 1d0a8d676..c13605acd 100644 --- a/server/src/state.rs +++ b/server/src/state.rs @@ -204,7 +204,7 @@ impl State { self.exec(move |world| { let pos = *world.get_component(entity).unwrap(); if let Some(mut positions) = world.get_component_mut::(to) { - positions.0.insert(entity, AtomicCell::new(pos)); + positions.0.insert(entity, pos); } }); } From 4d199a4cc448747787db1b2d998f54326323cc88 Mon Sep 17 00:00:00 2001 From: caelunshun Date: Sun, 5 Jan 2020 12:06:18 -0700 Subject: [PATCH 37/37] Add opt-level=1 to profile.dev --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 5fa8c4a44..d9d83091f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,6 @@ members = [ "generator", "util/rand-legacy", ] + +[profile.dev] +opt-level = 1