diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ae20e10bd..7c54295dc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,6 +71,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Animals are more effective in combat - Pathfinding is much smoother and pets are cleverer - Animals run/turn at different speeds +- Updated windowing library (winit 0.19 -> 0.22) ### Removed diff --git a/Cargo.lock b/Cargo.lock index a847fa1d48..8b3401d012 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000444226fcff248f2bc4c7625be32c63caccfecc2723a2b9f78a7487a49c407" +[[package]] +name = "android_log-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8052e2d8aabbb8d556d6abbcce2a22b9590996c5f849b9c7ce4544a2e3b984e" + [[package]] name = "ansi_term" version = "0.11.0" @@ -428,6 +434,17 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "calloop" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aa2097be53a00de9e8fc349fea6d76221f398f5c4fa550d420669906962d160" +dependencies = [ + "mio", + "mio-extras", + "nix 0.14.1", +] + [[package]] name = "cast" version = "0.2.3" @@ -463,11 +480,10 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cgl" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e7ec0b74fe5897894cbc207092c577e87c52f8a59e8ca8d97ef37551f60a49" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" dependencies = [ - "gleam", "libc", ] @@ -530,14 +546,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.18.5" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1706996401131526e36b3b49f0c4d912639ce110996f3ca144d78946727bce54" +checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" dependencies = [ "bitflags", "block", - "core-foundation", - "core-graphics", + "core-foundation 0.6.4", + "core-graphics 0.17.3", "foreign-types", "libc", "objc", @@ -545,14 +561,14 @@ dependencies = [ [[package]] name = "cocoa" -version = "0.19.1" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29f7768b2d1be17b96158e3285951d366b40211320fb30826a76cb7a0da6400" +checksum = "0c49e86fc36d5704151f5996b7b3795385f50ce09e3be0f47a0cfde869681cf8" dependencies = [ "bitflags", "block", - "core-foundation", - "core-graphics", + "core-foundation 0.7.0", + "core-graphics 0.19.2", "foreign-types", "libc", "objc", @@ -561,7 +577,7 @@ dependencies = [ [[package]] name = "conrod_core" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" dependencies = [ "conrod_derive", "copypasta", @@ -576,7 +592,7 @@ dependencies = [ [[package]] name = "conrod_derive" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", @@ -586,7 +602,7 @@ dependencies = [ [[package]] name = "conrod_winit" version = "0.63.0" -source = "git+https://gitlab.com/veloren/conrod.git?branch=pre-winit-20#46b374edc9537300e5278905ebd14dff45cfd927" +source = "git+https://gitlab.com/veloren/conrod.git#1ab6eccf94b16a8977a3274b31d4dbfef9cf9a30" [[package]] name = "const-random" @@ -663,7 +679,7 @@ dependencies = [ "objc-foundation", "objc_id", "smithay-clipboard", - "wayland-client 0.23.6", + "wayland-client", "x11-clipboard", ] @@ -673,7 +689,17 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.6.2", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171" +dependencies = [ + "core-foundation-sys 0.7.0", "libc", ] @@ -683,6 +709,12 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +[[package]] +name = "core-foundation-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" + [[package]] name = "core-graphics" version = "0.17.3" @@ -690,11 +722,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56790968ab1c8a1202a102e6de05fc6e1ec87da99e4e93e9a7d13efbfc1e95a9" dependencies = [ "bitflags", - "core-foundation", + "core-foundation 0.6.4", "foreign-types", "libc", ] +[[package]] +name = "core-graphics" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3889374e6ea6ab25dba90bb5d96202f61108058361f6dc72e8b03e6f8bbe923" +dependencies = [ + "bitflags", + "core-foundation 0.7.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-video-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ecad23610ad9757664d644e369246edde1803fcb43ed72876565098a5d3828" +dependencies = [ + "cfg-if", + "core-foundation-sys 0.7.0", + "core-graphics 0.19.2", + "libc", + "objc", +] + [[package]] name = "coreaudio-rs" version = "0.9.1" @@ -721,7 +778,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b55d55d69f403f62a95bd3c04b431e0aedf5120c70f15d07a8edd234443dd59" dependencies = [ "alsa-sys", - "core-foundation-sys", + "core-foundation-sys 0.6.2", "coreaudio-rs", "lazy_static", "libc", @@ -992,6 +1049,17 @@ dependencies = [ "byteorder 1.3.4", ] +[[package]] +name = "derivative" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb582b60359da160a9477ee80f15c8d784c477e69c217ef2cdd4169c24ea380f" +dependencies = [ + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "deunicode" version = "1.1.1" @@ -1063,6 +1131,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04e93ca78226c51902d7aa8c12c988338aadd9e85ed9c6be8aaac39192ff3605" +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + [[package]] name = "dlib" version = "0.4.2" @@ -1505,17 +1579,6 @@ dependencies = [ "gl_generator 0.14.0", ] -[[package]] -name = "gfx_window_glutin" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "310ff66f08b5a55854b18fea2f48bdbb75c94458207ba574c9723be78e97a646" -dependencies = [ - "gfx_core", - "gfx_device_gl", - "glutin", -] - [[package]] name = "gilrs" version = "0.7.4" @@ -1537,7 +1600,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43c758daf46af26d6872fe55507e3b2339779a160a06ad7a9b2a082f221209cd" dependencies = [ - "core-foundation", + "core-foundation 0.6.4", "io-kit-sys", "libc", "libudev-sys", @@ -1621,15 +1684,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "gleam" -version = "0.6.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cae10d7c99d0e77b4766e850a60898a17c1abaf01075531f1066f03dc7dc5fc5" -dependencies = [ - "gl_generator 0.13.1", -] - [[package]] name = "glib" version = "0.5.0" @@ -1672,15 +1726,15 @@ dependencies = [ [[package]] name = "glutin" -version = "0.21.2" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5371b35b309dace06be1b81b5f6adb1c9de578b7dbe1e74bf7e4ef762cf6febd" +checksum = "9a9666c8fd9afd008f6559e2468c35e11aad1d110d525bb3b354e4138ec0e20f" dependencies = [ "android_glue", "cgl", - "cocoa 0.18.5", - "core-foundation", - "core-graphics", + "cocoa 0.20.2", + "core-foundation 0.7.0", + "core-graphics 0.19.2", "glutin_egl_sys", "glutin_emscripten_sys", "glutin_gles2_sys", @@ -1688,10 +1742,11 @@ dependencies = [ "glutin_wgl_sys", "lazy_static", "libloading 0.5.2", + "log", "objc", "osmesa-sys", - "parking_lot 0.9.0", - "wayland-client 0.21.13", + "parking_lot 0.10.2", + "wayland-client", "winapi 0.3.8", "winit", ] @@ -2048,7 +2103,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f21dcc74995dd4cd090b147e79789f8d65959cbfb5f0b118002db869ea3bd0a0" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.6.2", "mach", ] @@ -2076,6 +2131,12 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + [[package]] name = "jobserver" version = "0.1.21" @@ -2448,6 +2509,37 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "ndk" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a356cafe20aee088789830bfea3a61336e84ded9e545e00d3869ce95dcb80c" +dependencies = [ + "jni-sys", + "ndk-sys", + "num_enum", +] + +[[package]] +name = "ndk-glue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1730ee2e3de41c3321160a6da815f008c4006d71b095880ea50e17cf52332b8" +dependencies = [ + "android_log-sys", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-sys", +] + +[[package]] +name = "ndk-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2820aca934aba5ed91c79acc72b6a44048ceacc5d36c035ed4e051f12d887d" + [[package]] name = "net2" version = "0.2.34" @@ -2638,6 +2730,28 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca565a7df06f3d4b485494f25ba05da1435950f4dc263440eda7a6fa9b8e36e4" +dependencies = [ + "derivative", + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5a33ddddfee04c0283a7653987d634e880347e96b5b2ed64de07efb59db9d" +dependencies = [ + "proc-macro-crate", + "proc-macro2 1.0.18", + "quote 1.0.7", + "syn 1.0.33", +] + [[package]] name = "objc" version = "0.2.7" @@ -2682,6 +2796,17 @@ dependencies = [ "byteorder 1.3.4", ] +[[package]] +name = "old_school_gfx_glutin_ext" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0557cea37cc48d238c938ded2873a6cc772704ee1eb01e832b43c2dd99624bc" +dependencies = [ + "gfx_core", + "gfx_device_gl", + "glutin", +] + [[package]] name = "once_cell" version = "1.4.0" @@ -2957,6 +3082,15 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-error" version = "0.4.12" @@ -3671,23 +3805,6 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4" -[[package]] -name = "smithay-client-toolkit" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ccb8c57049b2a34d2cc2b203fa785020ba0129d31920ef0d317430adaf748fa" -dependencies = [ - "andrew", - "bitflags", - "dlib", - "lazy_static", - "memmap", - "nix 0.14.1", - "wayland-client 0.21.13", - "wayland-commons 0.21.13", - "wayland-protocols 0.21.13", -] - [[package]] name = "smithay-client-toolkit" version = "0.6.6" @@ -3700,8 +3817,8 @@ dependencies = [ "lazy_static", "memmap", "nix 0.14.1", - "wayland-client 0.23.6", - "wayland-protocols 0.23.6", + "wayland-client", + "wayland-protocols", ] [[package]] @@ -3711,7 +3828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917e8ec7f535cd1a6cbf749c8866c24d67c548a80ac48c8e88a182eab5c07bd1" dependencies = [ "nix 0.14.1", - "smithay-client-toolkit 0.6.6", + "smithay-client-toolkit", ] [[package]] @@ -4506,13 +4623,12 @@ dependencies = [ "crossbeam", "deunicode", "directories-next", - "dispatch", + "dispatch 0.1.4", "dot_vox", "euc", "failure", "gfx", "gfx_device_gl", - "gfx_window_glutin", "gilrs", "git2", "glsl-include", @@ -4522,6 +4638,7 @@ dependencies = [ "image", "msgbox", "num 0.2.1", + "old_school_gfx_glutin_ext", "rand 0.7.3", "rodio", "ron", @@ -4706,21 +4823,6 @@ version = "0.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd" -[[package]] -name = "wayland-client" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49963e5f9eeaf637bfcd1b9f0701c99fd5cd05225eb51035550d4272806f2713" -dependencies = [ - "bitflags", - "downcast-rs", - "libc", - "nix 0.14.1", - "wayland-commons 0.21.13", - "wayland-scanner 0.21.13", - "wayland-sys 0.21.13", -] - [[package]] name = "wayland-client" version = "0.23.6" @@ -4728,22 +4830,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1080ebe0efabcf12aef2132152f616038f2d7dcbbccf7b2d8c5270fe14bcda" dependencies = [ "bitflags", + "calloop", "downcast-rs", "libc", + "mio", "nix 0.14.1", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-commons" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c08896768b667e1df195d88a62a53a2d1351a1ed96188be79c196b35bb32ec" -dependencies = [ - "nix 0.14.1", - "wayland-sys 0.21.13", + "wayland-commons", + "wayland-scanner", + "wayland-sys", ] [[package]] @@ -4753,20 +4847,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb66b0d1a27c39bbce712b6372131c6e25149f03ffb0cd017cf8f7de8d66dbdb" dependencies = [ "nix 0.14.1", - "wayland-sys 0.23.6", -] - -[[package]] -name = "wayland-protocols" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afde2ea2a428eee6d7d2c8584fdbe8b82eee8b6c353e129a434cd6e07f42145" -dependencies = [ - "bitflags", - "wayland-client 0.21.13", - "wayland-commons 0.21.13", - "wayland-scanner 0.21.13", - "wayland-sys 0.21.13", + "wayland-sys", ] [[package]] @@ -4776,20 +4857,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6cc286643656742777d55dc8e70d144fa4699e426ca8e9d4ef454f4bf15ffcf9" dependencies = [ "bitflags", - "wayland-client 0.23.6", - "wayland-commons 0.23.6", - "wayland-scanner 0.23.6", -] - -[[package]] -name = "wayland-scanner" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf3828c568714507315ee425a9529edc4a4aa9901409e373e9e0027e7622b79e" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "xml-rs", + "wayland-client", + "wayland-commons", + "wayland-scanner", ] [[package]] @@ -4803,16 +4873,6 @@ dependencies = [ "xml-rs", ] -[[package]] -name = "wayland-sys" -version = "0.21.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520ab0fd578017a0ee2206623ba9ef4afe5e8f23ca7b42f6acfba2f4e66b1628" -dependencies = [ - "dlib", - "lazy_static", -] - [[package]] name = "wayland-sys" version = "0.23.6" @@ -4897,26 +4957,31 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winit" -version = "0.19.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e96eb4bb472fa43e718e8fa4aef82f86cd9deac9483a1e1529230babdb394a8" +version = "0.22.2" +source = "git+https://github.com/Imberflur/winit.git?branch=macos-test#e98133adf2abbfc4368f6c069d0beb2b8b688b42" dependencies = [ - "android_glue", - "backtrace", "bitflags", - "cocoa 0.18.5", - "core-foundation", - "core-graphics", + "cocoa 0.20.2", + "core-foundation 0.7.0", + "core-graphics 0.19.2", + "core-video-sys", + "dispatch 0.2.0", + "instant", "lazy_static", "libc", "log", + "mio", + "mio-extras", + "ndk", + "ndk-glue", + "ndk-sys", "objc", - "parking_lot 0.9.0", + "parking_lot 0.10.2", "percent-encoding 2.1.0", "raw-window-handle", "serde", - "smithay-client-toolkit 0.4.6", - "wayland-client 0.21.13", + "smithay-client-toolkit", + "wayland-client", "winapi 0.3.8", "x11-dl", ] diff --git a/Cargo.toml b/Cargo.toml index 277aa6d984..b87d9807fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,7 @@ debug = false [profile.releasedebuginfo] inherits = 'release' debug = 1 + +# cpal conflict fix isn't released yet +[patch.crates-io] +winit = { git = "https://github.com/Imberflur/winit.git", branch = "macos-test" } diff --git a/common/src/state.rs b/common/src/state.rs index edae2d01b0..f232d07163 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -249,15 +249,11 @@ impl State { /// Removes every chunk of the terrain. pub fn clear_terrain(&mut self) { - let keys = self - .terrain_mut() - .drain() - .map(|(key, _)| key) - .collect::>(); + let removed_chunks = &mut self.ecs.write_resource::().removed_chunks; - for key in keys { - self.remove_chunk(key); - } + self.terrain_mut().drain().for_each(|(key, _)| { + removed_chunks.insert(key); + }); } /// Insert the provided chunk into this state's terrain. diff --git a/voxygen/Cargo.toml b/voxygen/Cargo.toml index 9fcc062af1..7576819aed 100644 --- a/voxygen/Cargo.toml +++ b/voxygen/Cargo.toml @@ -25,11 +25,11 @@ anim = { package = "veloren-voxygen-anim", path = "src/anim", default-features = # Graphics gfx = "0.18.2" gfx_device_gl = { version = "0.16.2", optional = true } -gfx_window_glutin = "0.31.0" -glutin = "0.21.1" -winit = { version = "0.19.4", features = ["serde"] } -conrod_core = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-winit-20" } -conrod_winit = { git = "https://gitlab.com/veloren/conrod.git", branch = "pre-winit-20" } +old_school_gfx_glutin_ext = "0.24" +glutin = "0.24.1" +winit = { version = "0.22.2", features = ["serde"] } +conrod_core = { git = "https://gitlab.com/veloren/conrod.git" } +conrod_winit = { git = "https://gitlab.com/veloren/conrod.git" } euc = { git = "https://github.com/zesterer/euc.git" } # ECS @@ -87,7 +87,6 @@ winres = "0.1" criterion = "0.3" git2 = "0.13" world = { package = "veloren-world", path = "../world" } -gfx_window_glutin = { version = "0.31.0", features = ["headless"] } [[bench]] name = "meshing_benchmark" diff --git a/voxygen/examples/character_renderer.rs b/voxygen/examples/character_renderer.rs index cf0aee4f53..70cfe2a725 100644 --- a/voxygen/examples/character_renderer.rs +++ b/voxygen/examples/character_renderer.rs @@ -1,9 +1,16 @@ -use common::comp; +// TODO: Fix this example when we switch to actively maintained rendering +// backend. Right now we would have to update `gfx_window_glutin` to work with +// the latest version of glutin or we would need to add headless support to +// `old_school_gfx_glutin_ext`. + +fn main() { + println!("Example temporarily disabled, see the TODO comment for details"); +} +/*use common::comp; use gfx_window_glutin::init_headless; use vek::*; use veloren_voxygen::{render, scene::simple as scene}; -#[allow(clippy::clone_on_copy)] // TODO: Pending review in #587 fn main() { // Setup renderer let dim = (200u16, 300u16, 1, gfx::texture::AaMode::Single); @@ -72,4 +79,4 @@ fn main() { // Get image let img = renderer.create_screenshot().unwrap(); img.save("character.png").unwrap(); -} +}*/ diff --git a/voxygen/examples/voxygen - Shortcut.lnk b/voxygen/examples/voxygen - Shortcut.lnk deleted file mode 100644 index 722dd67f2a..0000000000 Binary files a/voxygen/examples/voxygen - Shortcut.lnk and /dev/null differ diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 7a4c2a75f6..a025369b75 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -465,17 +465,15 @@ impl Show { self.want_grab = true; // Unpause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); } else { self.esc_menu = true; self.want_grab = false; // Pause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(true); - }; + #[cfg(feature = "singleplayer")] + global_state.pause(); } } @@ -1721,9 +1719,8 @@ impl Hud { settings_window::Event::ChangeTab(tab) => self.show.open_setting_tab(tab), settings_window::Event::Close => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); self.show.settings(false) }, @@ -1908,24 +1905,21 @@ impl Hud { self.force_ungrab = false; // Unpause the game if we are on singleplayer - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); }, Some(esc_menu::Event::Logout) => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); events.push(Event::Logout); }, Some(esc_menu::Event::Quit) => events.push(Event::Quit), Some(esc_menu::Event::CharacterSelection) => { // Unpause the game if we are on singleplayer so that we can logout - if let Some(singleplayer) = global_state.singleplayer.as_ref() { - singleplayer.pause(false); - }; + #[cfg(feature = "singleplayer")] + global_state.unpause(); events.push(Event::CharacterSelection) }, diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 02bc36d053..ccec393b41 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -17,6 +17,7 @@ pub mod menu; pub mod mesh; pub mod profile; pub mod render; +pub mod run; pub mod scene; pub mod session; pub mod settings; @@ -27,10 +28,16 @@ pub mod window; // Reexports pub use crate::error::Error; +#[cfg(feature = "singleplayer")] +use crate::singleplayer::Singleplayer; use crate::{ - audio::AudioFrontend, profile::Profile, settings::Settings, singleplayer::Singleplayer, - window::Window, + audio::AudioFrontend, + profile::Profile, + render::Renderer, + settings::Settings, + window::{Event, Window}, }; +use common::{assets::watch, clock::Clock}; /// A type used to store state that is shared between all play states. pub struct GlobalState { @@ -39,7 +46,11 @@ pub struct GlobalState { pub window: Window, pub audio: AudioFrontend, pub info_message: Option, + pub clock: Clock, + #[cfg(feature = "singleplayer")] pub singleplayer: Option, + // TODO: redo this so that the watcher doesn't have to exist for reloading to occur + pub localization_watcher: watch::ReloadIndicator, } impl GlobalState { @@ -51,8 +62,25 @@ impl GlobalState { } pub fn maintain(&mut self, dt: f32) { self.audio.maintain(dt); } + + #[cfg(feature = "singleplayer")] + pub fn paused(&self) -> bool { + self.singleplayer + .as_ref() + .map_or(false, Singleplayer::is_paused) + } + + #[cfg(not(feature = "singleplayer"))] + pub fn paused(&self) -> bool { false } + + #[cfg(feature = "singleplayer")] + pub fn unpause(&self) { self.singleplayer.as_ref().map(|s| s.pause(false)); } + + #[cfg(feature = "singleplayer")] + pub fn pause(&self) { self.singleplayer.as_ref().map(|s| s.pause(true)); } } +// TODO: appears to be currently unused by playstates pub enum Direction { Forwards, Backwards, @@ -61,6 +89,8 @@ pub enum Direction { /// States can either close (and revert to a previous state), push a new state /// on top of themselves, or switch to a totally different state. pub enum PlayStateResult { + /// Keep running this play state. + Continue, /// Pop all play states in reverse order and shut down the program. Shutdown, /// Close the current play state and pop it from the play state stack. @@ -74,10 +104,15 @@ pub enum PlayStateResult { /// A trait representing a playable game state. This may be a menu, a game /// session, the title screen, etc. pub trait PlayState { - /// Play the state until some change of state is required (i.e: a menu is - /// opened or the game is closed). - fn play(&mut self, direction: Direction, global_state: &mut GlobalState) -> PlayStateResult; + /// Called when entering this play state from another + fn enter(&mut self, global_state: &mut GlobalState, direction: Direction); + + /// Tick the play state + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult; /// Get a descriptive name for this state type. fn name(&self) -> &'static str; + + /// Draw the play state. + fn render(&mut self, renderer: &mut Renderer, settings: &Settings); } diff --git a/voxygen/src/main.rs b/voxygen/src/main.rs index b4f64dc017..1820be77fd 100644 --- a/voxygen/src/main.rs +++ b/voxygen/src/main.rs @@ -7,16 +7,19 @@ use veloren_voxygen::{ audio::{self, AudioFrontend}, i18n::{self, i18n_asset_key, VoxygenLocalization}, logging, - menu::main::MainMenuState, profile::Profile, + run, settings::{AudioOutput, Settings}, window::Window, - Direction, GlobalState, PlayState, PlayStateResult, + GlobalState, }; -use common::assets::{load, load_expect}; -use std::{mem, panic}; -use tracing::{debug, error, warn}; +use common::{ + assets::{load_watched, watch}, + clock::Clock, +}; +use std::panic; +use tracing::{error, warn}; fn main() { #[cfg(feature = "tweak")] @@ -26,57 +29,14 @@ fn main() { // Note: This won't log anything due to it being called before // `logging::init`. The issue is we need to read a setting to decide // whether we create a log file or not. - let settings = Settings::load(); - - // Init logging and hold the guards. - let _guards = logging::init(&settings); - - // Save settings to add new fields or create the file if it is not already - // there. + let mut settings = Settings::load(); + // Save settings to add new fields or create the file if it is not already there if let Err(err) = settings.save_to_file() { panic!("Failed to save settings: {:?}", err); } - let mut audio = match settings.audio.output { - AudioOutput::Off => None, - AudioOutput::Automatic => audio::get_default_device(), - AudioOutput::Device(ref dev) => Some(dev.clone()), - } - .map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels)) - .unwrap_or_else(AudioFrontend::no_audio); - - audio.set_music_volume(settings.audio.music_volume); - audio.set_sfx_volume(settings.audio.sfx_volume); - - // Load the profile. - let profile = Profile::load(); - - let mut global_state = GlobalState { - audio, - profile, - window: Window::new(&settings).expect("Failed to create window!"), - settings, - info_message: None, - singleplayer: None, - }; - - // Try to load the localization and log missing entries - let localized_strings = load::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )) - .unwrap_or_else(|e| { - let preferred_language = &global_state.settings.language.selected_language; - warn!( - ?e, - ?preferred_language, - "Impossible to load language: change to the default language (English) instead.", - ); - global_state.settings.language.selected_language = i18n::REFERENCE_LANG.to_owned(); - load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )) - }); - localized_strings.log_missing_entries(); + // Init logging and hold the guards. + let _guards = logging::init(&settings); // Set up panic handler to relay swish panic messages to the user let default_hook = panic::take_hook(); @@ -159,68 +119,56 @@ fn main() { #[cfg(feature = "hot-anim")] anim::init(); - // Set up the initial play state. - let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; - states.last().map(|current_state| { - let current_state = current_state.name(); - debug!(?current_state, "Started game with state") - }); - - // What's going on here? - // --------------------- - // The state system used by Voxygen allows for the easy development of - // stack-based menus. For example, you may want a "title" state that can - // push a "main menu" state on top of it, which can in turn push a - // "settings" state or a "game session" state on top of it. The code below - // manages the state transfer logic automatically so that we don't have to - // re-engineer it for each menu we decide to add to the game. - let mut direction = Direction::Forwards; - while let Some(state_result) = states - .last_mut() - .map(|last| last.play(direction, &mut global_state)) - { - // Implement state transfer logic. - match state_result { - PlayStateResult::Shutdown => { - direction = Direction::Backwards; - debug!("Shutting down all states..."); - while states.last().is_some() { - states.pop().map(|old_state| { - let old_state = old_state.name(); - debug!(?old_state, "Popped state"); - global_state.on_play_state_changed(); - }); - } - }, - PlayStateResult::Pop => { - direction = Direction::Backwards; - states.pop().map(|old_state| { - let old_state = old_state.name(); - debug!(?old_state, "Popped state"); - global_state.on_play_state_changed(); - }); - }, - PlayStateResult::Push(new_state) => { - direction = Direction::Forwards; - debug!("Pushed state '{}'.", new_state.name()); - states.push(new_state); - global_state.on_play_state_changed(); - }, - PlayStateResult::Switch(mut new_state_box) => { - direction = Direction::Forwards; - states.last_mut().map(|old_state_box| { - let old_state = old_state_box.name(); - let new_state = new_state_box.name(); - debug!(?old_state, ?new_state, "Switching to states",); - mem::swap(old_state_box, &mut new_state_box); - global_state.on_play_state_changed(); - }); - }, - } + // Setup audio + let mut audio = match settings.audio.output { + AudioOutput::Off => None, + AudioOutput::Automatic => audio::get_default_device(), + AudioOutput::Device(ref dev) => Some(dev.clone()), } + .map(|dev| AudioFrontend::new(dev, settings.audio.max_sfx_channels)) + .unwrap_or_else(AudioFrontend::no_audio); - // Save any unsaved changes to profile. - global_state.profile.save_to_file_warn(); - // Save any unsaved changes to settings. - global_state.settings.save_to_file_warn(); + audio.set_music_volume(settings.audio.music_volume); + audio.set_sfx_volume(settings.audio.sfx_volume); + + // Load the profile. + let profile = Profile::load(); + + let mut localization_watcher = watch::ReloadIndicator::new(); + let localized_strings = load_watched::( + &i18n_asset_key(&settings.language.selected_language), + &mut localization_watcher, + ) + .unwrap_or_else(|error| { + let selected_language = &settings.language.selected_language; + warn!( + ?error, + ?selected_language, + "Impossible to load language: change to the default language (English) instead.", + ); + settings.language.selected_language = i18n::REFERENCE_LANG.to_owned(); + load_watched::( + &i18n_asset_key(&settings.language.selected_language), + &mut localization_watcher, + ) + .unwrap() + }); + localized_strings.log_missing_entries(); + + // Create window + let (window, event_loop) = Window::new(&settings).expect("Failed to create window!"); + + let global_state = GlobalState { + audio, + profile, + window, + settings, + clock: Clock::start(), + info_message: None, + #[cfg(feature = "singleplayer")] + singleplayer: None, + localization_watcher, + }; + + run::run(global_state, event_loop); } diff --git a/voxygen/src/menu/char_selection/mod.rs b/voxygen/src/menu/char_selection/mod.rs index bb8962d6f8..ece5cb3ea4 100644 --- a/voxygen/src/menu/char_selection/mod.rs +++ b/voxygen/src/menu/char_selection/mod.rs @@ -2,15 +2,17 @@ mod ui; use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, + render::Renderer, scene::simple::{self as scene, Scene}, session::SessionState, + settings::Settings, window::Event as WinEvent, Direction, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; -use common::{assets, clock::Clock, comp, msg::ClientState, state::DeltaTime}; +use common::{assets, comp, msg::ClientState, state::DeltaTime}; use specs::WorldExt; -use std::{cell::RefCell, rc::Rc, time::Duration}; +use std::{cell::RefCell, rc::Rc}; use tracing::error; use ui::CharSelectionUi; @@ -32,20 +34,34 @@ impl CharSelectionState { ), } } + + fn get_humanoid_body(&self) -> Option { + self.char_selection_ui + .get_character_list() + .and_then(|data| { + if let Some(character) = data.get(self.char_selection_ui.selected_character) { + match character.body { + comp::Body::Humanoid(body) => Some(body), + _ => None, + } + } else { + None + } + }) + } } impl PlayState for CharSelectionState { - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { - // Set up an fps clock. - let mut clock = Clock::start(); - + fn enter(&mut self, _: &mut GlobalState, _: Direction) { // Load the player's character list self.client.borrow_mut().load_character_list(); + } - let mut current_client_state = self.client.borrow().get_client_state(); - while let ClientState::Pending | ClientState::Registered = current_client_state { + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { + let client_state = self.client.borrow().get_client_state(); + if let ClientState::Pending | ClientState::Registered = client_state { // Handle window events - for event in global_state.window.fetch_events(&mut global_state.settings) { + for event in events { if self.char_selection_ui.handle_event(event.clone()) { continue; } @@ -60,8 +76,6 @@ impl PlayState for CharSelectionState { } } - global_state.window.renderer_mut().clear(); - // Maintain the UI. let events = self .char_selection_ui @@ -100,23 +114,7 @@ impl PlayState for CharSelectionState { } } - // Maintain global state. - global_state.maintain(clock.get_last_delta().as_secs_f32()); - - let humanoid_body = self - .char_selection_ui - .get_character_list() - .and_then(|data| { - if let Some(character) = data.get(self.char_selection_ui.selected_character) { - match character.body { - comp::Body::Humanoid(body) => Some(body), - _ => None, - } - } else { - None - } - }); - + let humanoid_body = self.get_humanoid_body(); let loadout = self.char_selection_ui.get_loadout(); // Maintain the scene. @@ -141,17 +139,6 @@ impl PlayState for CharSelectionState { loadout.as_ref(), ); } - // Render the scene. - self.scene.render( - global_state.window.renderer_mut(), - self.client.borrow().get_tick(), - humanoid_body, - loadout.as_ref(), - ); - - // Draw the UI to the screen. - self.char_selection_ui - .render(global_state.window.renderer_mut(), self.scene.globals()); // Tick the client (currently only to keep the connection alive). let localized_strings = assets::load_expect::(&i18n_asset_key( @@ -160,7 +147,7 @@ impl PlayState for CharSelectionState { match self.client.borrow_mut().tick( comp::ControllerInputs::default(), - clock.get_last_delta(), + global_state.clock.get_last_delta(), |_| {}, ) { Ok(events) => { @@ -190,25 +177,33 @@ impl PlayState for CharSelectionState { }, } + // TODO: make sure rendering is not relying on cleaned up stuff self.client.borrow_mut().cleanup(); - // Finish the frame. - global_state.window.renderer_mut().flush(); - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers"); - - // Wait for the next tick. - clock.tick(Duration::from_millis( - 1000 / (global_state.settings.graphics.max_fps as u64), - )); - - current_client_state = self.client.borrow().get_client_state(); + PlayStateResult::Continue + } else { + error!("Client not in pending or registered state. Popping char selection play state"); + // TODO set global_state.info_message + PlayStateResult::Pop } - - PlayStateResult::Pop } fn name(&self) -> &'static str { "Title" } + + fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + let humanoid_body = self.get_humanoid_body(); + let loadout = self.char_selection_ui.get_loadout(); + + // Render the scene. + self.scene.render( + renderer, + self.client.borrow().get_tick(), + humanoid_body, + loadout.as_ref(), + ); + + // Draw the UI to the screen. + self.char_selection_ui + .render(renderer, self.scene.globals()); + } } diff --git a/voxygen/src/menu/main/mod.rs b/voxygen/src/menu/main/mod.rs index cdd9410545..e520df7ecf 100644 --- a/voxygen/src/menu/main/mod.rs +++ b/voxygen/src/menu/main/mod.rs @@ -1,19 +1,22 @@ mod client_init; -#[cfg(feature = "singleplayer")] mod ui; +mod ui; use super::char_selection::CharSelectionState; +#[cfg(feature = "singleplayer")] +use crate::singleplayer::Singleplayer; use crate::{ - singleplayer::Singleplayer, window::Event, Direction, GlobalState, PlayState, PlayStateResult, + render::Renderer, settings::Settings, window::Event, Direction, GlobalState, PlayState, + PlayStateResult, }; use client_init::{ClientInit, Error as InitError, Msg as InitMsg}; -use common::{assets::load_expect, clock::Clock, comp}; -#[cfg(feature = "singleplayer")] -use std::time::Duration; +use common::{assets::load_expect, comp}; use tracing::{error, warn}; use ui::{Event as MainMenuEvent, MainMenuUi}; pub struct MainMenuState { main_menu_ui: MainMenuUi, + // Used for client creation. + client_init: Option, } impl MainMenuState { @@ -21,6 +24,7 @@ impl MainMenuState { pub fn new(global_state: &mut GlobalState) -> Self { Self { main_menu_ui: MainMenuUi::new(global_state), + client_init: None, } } } @@ -28,237 +32,226 @@ impl MainMenuState { const DEFAULT_PORT: u16 = 14004; impl PlayState for MainMenuState { - #[allow(clippy::useless_format)] // TODO: Pending review in #587 - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { - // Set up an fps clock. - let mut clock = Clock::start(); - - // Used for client creation. - let mut client_init: Option = None; - + fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Kick off title music if global_state.settings.audio.output.is_enabled() && global_state.audio.music_enabled() { global_state.audio.play_title_music(); } // Reset singleplayer server if it was running already - global_state.singleplayer = None; + #[cfg(feature = "singleplayer")] + { + global_state.singleplayer = None; + } + } + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { let localized_strings = load_expect::( &crate::i18n::i18n_asset_key(&global_state.settings.language.selected_language), ); - loop { - // Handle window events. - for event in global_state.window.fetch_events(&mut global_state.settings) { - match event { - Event::Close => return PlayStateResult::Shutdown, - // Pass events to ui. - Event::Ui(event) => { - self.main_menu_ui.handle_event(event); - }, - // Ignore all other events. - _ => {}, - } - } - - global_state.window.renderer_mut().clear(); - - // Poll client creation. - match client_init.as_ref().and_then(|init| init.poll()) { - Some(InitMsg::Done(Ok(mut client))) => { - self.main_menu_ui.connected(); - // Register voxygen components / resources - crate::ecs::init(client.state_mut().ecs_mut()); - return PlayStateResult::Push(Box::new(CharSelectionState::new( - global_state, - std::rc::Rc::new(std::cell::RefCell::new(client)), - ))); + // Handle window events. + for event in events { + match event { + Event::Close => return PlayStateResult::Shutdown, + // Pass events to ui. + Event::Ui(event) => { + self.main_menu_ui.handle_event(event); }, - Some(InitMsg::Done(Err(e))) => { - client_init = None; - global_state.info_message = Some({ - let err = match e { - InitError::BadAddress(_) | InitError::NoAddress => { - localized_strings.get("main.login.server_not_found").into() + // Ignore all other events. + _ => {}, + } + } + // Poll client creation. + match self.client_init.as_ref().and_then(|init| init.poll()) { + Some(InitMsg::Done(Ok(mut client))) => { + self.client_init = None; + self.main_menu_ui.connected(); + // Register voxygen components / resources + crate::ecs::init(client.state_mut().ecs_mut()); + return PlayStateResult::Push(Box::new(CharSelectionState::new( + global_state, + std::rc::Rc::new(std::cell::RefCell::new(client)), + ))); + }, + Some(InitMsg::Done(Err(err))) => { + self.client_init = None; + global_state.info_message = Some({ + let err = match err { + InitError::BadAddress(_) | InitError::NoAddress => { + localized_strings.get("main.login.server_not_found").into() + }, + InitError::ClientError(err) => match err { + client::Error::AuthErr(e) => format!( + "{}: {}", + localized_strings.get("main.login.authentication_error"), + e + ), + client::Error::TooManyPlayers => { + localized_strings.get("main.login.server_full").into() }, - InitError::ClientError(err) => match err { - client::Error::AuthErr(e) => format!( + client::Error::AuthServerNotTrusted => localized_strings + .get("main.login.untrusted_auth_server") + .into(), + client::Error::ServerWentMad => localized_strings + .get("main.login.outdated_client_or_server") + .into(), + client::Error::ServerTimeout => { + localized_strings.get("main.login.timeout").into() + }, + client::Error::ServerShutdown => { + localized_strings.get("main.login.server_shut_down").into() + }, + client::Error::AlreadyLoggedIn => { + localized_strings.get("main.login.already_logged_in").into() + }, + client::Error::NotOnWhitelist => { + localized_strings.get("main.login.not_on_whitelist").into() + }, + client::Error::InvalidCharacter => { + localized_strings.get("main.login.invalid_character").into() + }, + client::Error::NetworkErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::ParticipantErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::StreamErr(e) => format!( + "{}: {:?}", + localized_strings.get("main.login.network_error"), + e + ), + client::Error::Other(e) => { + format!("{}: {}", localized_strings.get("common.error"), e) + }, + client::Error::AuthClientError(e) => match e { + client::AuthClientError::JsonError(e) => format!( "{}: {}", - localized_strings.get("main.login.authentication_error"), + localized_strings.get("common.fatal_error"), e ), - client::Error::TooManyPlayers => { - localized_strings.get("main.login.server_full").into() - }, - client::Error::AuthServerNotTrusted => localized_strings - .get("main.login.untrusted_auth_server") - .into(), - client::Error::ServerWentMad => localized_strings - .get("main.login.outdated_client_or_server") - .into(), - client::Error::ServerTimeout => { - localized_strings.get("main.login.timeout").into() - }, - client::Error::ServerShutdown => { - localized_strings.get("main.login.server_shut_down").into() - }, - client::Error::AlreadyLoggedIn => { - localized_strings.get("main.login.already_logged_in").into() - }, - client::Error::NotOnWhitelist => { - localized_strings.get("main.login.not_on_whitelist").into() - }, - client::Error::NetworkErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::ParticipantErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::StreamErr(e) => format!( - "{}: {:?}", - localized_strings.get("main.login.network_error"), - e - ), - client::Error::Other(e) => { - format!("{}: {}", localized_strings.get("common.error"), e) - }, - client::Error::AuthClientError(e) => match e { - client::AuthClientError::JsonError(e) => format!( - "{}: {}", - localized_strings.get("common.fatal_error"), - e - ), - client::AuthClientError::RequestError() => format!( - "{}", - localized_strings.get("main.login.failed_sending_request") - ), - client::AuthClientError::ServerError(_, e) => format!("{}", e), - }, - client::Error::InvalidCharacter => { - localized_strings.get("main.login.invalid_character").into() - }, + // TODO: remove parentheses + client::AuthClientError::RequestError() => localized_strings + .get("main.login.failed_sending_request") + .to_owned(), + client::AuthClientError::ServerError(_, e) => e, }, - InitError::ClientCrashed => { - localized_strings.get("main.login.client_crashed").into() - }, - }; - // Log error for possible additional use later or incase that the error - // displayed is cut of. - error!("{}", err); - err - }); - }, - Some(InitMsg::IsAuthTrusted(auth_server)) => { - if global_state - .settings - .networking - .trusted_auth_servers - .contains(&auth_server) - { - // Can't fail since we just polled it, it must be Some - client_init.as_ref().unwrap().auth_trust(auth_server, true); - } else { - // Show warning that auth server is not trusted and prompt for approval - self.main_menu_ui.auth_trust_prompt(auth_server); - } - }, - None => {}, - } + }, + InitError::ClientCrashed => { + localized_strings.get("main.login.client_crashed").into() + }, + }; + // Log error for possible additional use later or incase that the error + // displayed is cut of. + error!("{}", err); + err + }); + }, + Some(InitMsg::IsAuthTrusted(auth_server)) => { + if global_state + .settings + .networking + .trusted_auth_servers + .contains(&auth_server) + { + // Can't fail since we just polled it, it must be Some + self.client_init + .as_ref() + .unwrap() + .auth_trust(auth_server, true); + } else { + // Show warning that auth server is not trusted and prompt for approval + self.main_menu_ui.auth_trust_prompt(auth_server); + } + }, + None => {}, + } - // Maintain global_state - global_state.maintain(clock.get_last_delta().as_secs_f32()); - - // Maintain the UI. - for event in self - .main_menu_ui - .maintain(global_state, clock.get_last_delta()) - { - match event { - MainMenuEvent::LoginAttempt { + // Maintain the UI. + for event in self + .main_menu_ui + .maintain(global_state, global_state.clock.get_last_delta()) + { + match event { + MainMenuEvent::LoginAttempt { + username, + password, + server_address, + } => { + attempt_login( + global_state, username, password, server_address, - } => { - attempt_login( - global_state, - username, - password, - server_address, - DEFAULT_PORT, - &mut client_init, - ); - }, - MainMenuEvent::CancelLoginAttempt => { - // client_init contains Some(ClientInit), which spawns a thread which - // contains a TcpStream::connect() call This call is - // blocking TODO fix when the network rework happens - global_state.singleplayer = None; - client_init = None; - self.main_menu_ui.cancel_connection(); - }, + DEFAULT_PORT, + &mut self.client_init, + ); + }, + MainMenuEvent::CancelLoginAttempt => { + // client_init contains Some(ClientInit), which spawns a thread which contains a + // TcpStream::connect() call This call is blocking + // TODO fix when the network rework happens #[cfg(feature = "singleplayer")] - MainMenuEvent::StartSingleplayer => { - let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool + { + global_state.singleplayer = None; + } + self.client_init = None; + self.main_menu_ui.cancel_connection(); + }, + #[cfg(feature = "singleplayer")] + MainMenuEvent::StartSingleplayer => { + let (singleplayer, server_settings) = Singleplayer::new(None); // TODO: Make client and server use the same thread pool - global_state.singleplayer = Some(singleplayer); + global_state.singleplayer = Some(singleplayer); - attempt_login( - global_state, - "singleplayer".to_owned(), - "".to_owned(), - server_settings.gameserver_address.ip().to_string(), - server_settings.gameserver_address.port(), - &mut client_init, - ); - }, - MainMenuEvent::Settings => {}, // TODO - MainMenuEvent::Quit => return PlayStateResult::Shutdown, - /*MainMenuEvent::DisclaimerClosed => { - global_state.settings.show_disclaimer = false - },*/ - MainMenuEvent::AuthServerTrust(auth_server, trust) => { - if trust { - global_state - .settings - .networking - .trusted_auth_servers - .insert(auth_server.clone()); - global_state.settings.save_to_file_warn(); - } - client_init - .as_ref() - .map(|init| init.auth_trust(auth_server, trust)); - }, - } + attempt_login( + global_state, + "singleplayer".to_owned(), + "".to_owned(), + server_settings.gameserver_address.ip().to_string(), + server_settings.gameserver_address.port(), + &mut self.client_init, + ); + }, + MainMenuEvent::Settings => {}, // TODO + MainMenuEvent::Quit => return PlayStateResult::Shutdown, + /*MainMenuEvent::DisclaimerClosed => { + global_state.settings.show_disclaimer = false + },*/ + MainMenuEvent::AuthServerTrust(auth_server, trust) => { + if trust { + global_state + .settings + .networking + .trusted_auth_servers + .insert(auth_server.clone()); + global_state.settings.save_to_file_warn(); + } + self.client_init + .as_ref() + .map(|init| init.auth_trust(auth_server, trust)); + }, } - - if let Some(info) = global_state.info_message.take() { - self.main_menu_ui.show_info(info); - } - - // Draw the UI to the screen. - self.main_menu_ui.render(global_state.window.renderer_mut()); - - // Finish the frame. - global_state.window.renderer_mut().flush(); - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers!"); - - // Wait for the next tick - clock.tick(Duration::from_millis( - 1000 / (global_state.settings.graphics.max_fps as u64), - )); } + + if let Some(info) = global_state.info_message.take() { + self.main_menu_ui.show_info(info); + } + + PlayStateResult::Continue } fn name(&self) -> &'static str { "Title" } + + fn render(&mut self, renderer: &mut Renderer, _: &Settings) { + // Draw the UI to the screen. + self.main_menu_ui.render(renderer); + } } fn attempt_login( diff --git a/voxygen/src/run.rs b/voxygen/src/run.rs new file mode 100644 index 0000000000..b4f8856e17 --- /dev/null +++ b/voxygen/src/run.rs @@ -0,0 +1,145 @@ +use crate::{ + menu::main::MainMenuState, + ui, + window::{Event, EventLoop}, + Direction, GlobalState, PlayState, PlayStateResult, +}; +use std::{mem, time::Duration}; +use tracing::debug; + +pub fn run(mut global_state: GlobalState, event_loop: EventLoop) { + // Set up the initial play state. + let mut states: Vec> = vec![Box::new(MainMenuState::new(&mut global_state))]; + states.last_mut().map(|current_state| { + current_state.enter(&mut global_state, Direction::Forwards); + let current_state = current_state.name(); + debug!(?current_state, "Started game with state"); + }); + + // Used to ignore every other `MainEventsCleared` + // This is a workaround for a bug on macos in which mouse motion events are only + // reported every other cycle of the event loop + // See: https://github.com/rust-windowing/winit/issues/1418 + let mut polled_twice = false; + + event_loop.run(move |event, _, control_flow| { + // Continously run loop since we handle sleeping + *control_flow = winit::event_loop::ControlFlow::Poll; + + // Get events for the ui. + if let Some(event) = ui::Event::try_from(&event, global_state.window.window()) { + global_state.window.send_event(Event::Ui(event)); + } + + match event { + winit::event::Event::MainEventsCleared => { + if polled_twice { + handle_main_events_cleared(&mut states, control_flow, &mut global_state); + } + polled_twice = !polled_twice; + }, + winit::event::Event::WindowEvent { event, .. } => global_state + .window + .handle_window_event(event, &mut global_state.settings), + winit::event::Event::DeviceEvent { event, .. } => { + global_state.window.handle_device_event(event) + }, + winit::event::Event::LoopDestroyed => { + // Save any unsaved changes to settings and profile + global_state.settings.save_to_file_warn(); + global_state.profile.save_to_file_warn(); + }, + _ => {}, + } + }); +} + +fn handle_main_events_cleared( + states: &mut Vec>, + control_flow: &mut winit::event_loop::ControlFlow, + global_state: &mut GlobalState, +) { + // Run tick here + + // What's going on here? + // --------------------- + // The state system used by Voxygen allows for the easy development of + // stack-based menus. For example, you may want a "title" state + // that can push a "main menu" state on top of it, which can in + // turn push a "settings" state or a "game session" state on top of it. + // The code below manages the state transfer logic automatically so that we + // don't have to re-engineer it for each menu we decide to add + // to the game. + let mut exit = true; + while let Some(state_result) = states.last_mut().map(|last| { + let events = global_state.window.fetch_events(); + last.tick(global_state, events) + }) { + // Implement state transfer logic. + match state_result { + PlayStateResult::Continue => { + // Wait for the next tick. + global_state.clock.tick(Duration::from_millis( + 1000 / global_state.settings.graphics.max_fps as u64, + )); + + // Maintain global state. + global_state.maintain(global_state.clock.get_last_delta().as_secs_f32()); + + exit = false; + break; + }, + PlayStateResult::Shutdown => { + debug!("Shutting down all states..."); + while states.last().is_some() { + states.pop().map(|old_state| { + debug!("Popped state '{}'.", old_state.name()); + global_state.on_play_state_changed(); + }); + } + }, + PlayStateResult::Pop => { + states.pop().map(|old_state| { + debug!("Popped state '{}'.", old_state.name()); + global_state.on_play_state_changed(); + }); + states.last_mut().map(|new_state| { + new_state.enter(global_state, Direction::Backwards); + }); + }, + PlayStateResult::Push(mut new_state) => { + new_state.enter(global_state, Direction::Forwards); + debug!("Pushed state '{}'.", new_state.name()); + states.push(new_state); + global_state.on_play_state_changed(); + }, + PlayStateResult::Switch(mut new_state) => { + new_state.enter(global_state, Direction::Forwards); + states.last_mut().map(|old_state| { + debug!( + "Switching to state '{}' from state '{}'.", + new_state.name(), + old_state.name() + ); + mem::swap(old_state, &mut new_state); + global_state.on_play_state_changed(); + }); + }, + } + } + + if exit { + *control_flow = winit::event_loop::ControlFlow::Exit; + } + + if let Some(last) = states.last_mut() { + global_state.window.renderer_mut().clear(); + last.render(global_state.window.renderer_mut(), &global_state.settings); + // Finish the frame. + global_state.window.renderer_mut().flush(); + global_state + .window + .swap_buffers() + .expect("Failed to swap window buffers!"); + } +} diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index d6d1b48148..44602088fc 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -5,15 +5,15 @@ use crate::{ i18n::{i18n_asset_key, VoxygenLocalization}, key_state::KeyState, menu::char_selection::CharSelectionState, + render::Renderer, scene::{camera, Scene, SceneData}, - settings::{AudioOutput, ControlSettings}, + settings::{AudioOutput, ControlSettings, Settings}, window::{AnalogGameInput, Event, GameInput}, Direction, Error, GlobalState, PlayState, PlayStateResult, }; use client::{self, Client}; use common::{ - assets::{load_expect, load_watched, watch}, - clock::Clock, + assets::{load_expect, load_watched}, comp, comp::{ ChatMsg, ChatType, InventoryUpdateEvent, Pos, Vel, MAX_MOUNT_RANGE_SQR, @@ -46,6 +46,12 @@ pub struct SessionState { inputs: comp::ControllerInputs, selected_block: Block, voxygen_i18n: std::sync::Arc, + walk_forward_dir: Vec2, + walk_right_dir: Vec2, + freefly_vel: Vec3, + free_look: bool, + auto_walk: bool, + is_aiming: bool, } /// Represents an active game session (i.e., the one being played). @@ -59,14 +65,12 @@ impl SessionState { .camera_mut() .set_fov_deg(global_state.settings.graphics.fov); let hud = Hud::new(global_state, &client.borrow()); - { - let mut client = client.borrow_mut(); - let my_entity = client.entity(); - client.state_mut().ecs_mut().insert(MyEntity(my_entity)); - } let voxygen_i18n = load_expect::(&i18n_asset_key( &global_state.settings.language.selected_language, )); + + let walk_forward_dir = scene.camera().forward_xy(); + let walk_right_dir = scene.camera().right_xy(); Self { scene, client, @@ -75,8 +79,20 @@ impl SessionState { hud, selected_block: Block::new(BlockKind::Normal, Rgb::broadcast(255)), voxygen_i18n, + walk_forward_dir, + walk_right_dir, + freefly_vel: Vec3::zero(), + free_look: false, + auto_walk: false, + is_aiming: false, } } + + fn stop_auto_walk(&mut self) { + self.auto_walk = false; + self.hud.auto_walk(false); + self.key_state.auto_walk = false; + } } impl SessionState { @@ -86,9 +102,6 @@ impl SessionState { let mut client = self.client.borrow_mut(); for event in client.tick(self.inputs.clone(), dt, crate::ecs::sys::add_local_systems)? { - self.voxygen_i18n = load_expect::(&i18n_asset_key( - &global_state.settings.language.selected_language, - )); match event { client::Event::Chat(m) => { self.hud.new_message(m); @@ -153,12 +166,10 @@ impl SessionState { } impl PlayState for SessionState { - fn play(&mut self, _: Direction, global_state: &mut GlobalState) -> PlayStateResult { + fn enter(&mut self, global_state: &mut GlobalState, _: Direction) { // Trap the cursor. global_state.window.grab_cursor(true); - // Set up an fps clock. - let mut clock = Clock::start(); self.client.borrow_mut().clear_terrain(); // Send startup commands to the server @@ -167,30 +178,28 @@ impl PlayState for SessionState { self.client.borrow_mut().send_chat(cmd.to_string()); } } + } - // Keep a watcher on the language - let mut localization_watcher = watch::ReloadIndicator::new(); - let mut localized_strings = load_watched::( - &i18n_asset_key(&global_state.settings.language.selected_language), - &mut localization_watcher, - ) - .unwrap(); + fn tick(&mut self, global_state: &mut GlobalState, events: Vec) -> PlayStateResult { + self.voxygen_i18n = load_expect::(&i18n_asset_key( + &global_state.settings.language.selected_language, + )); - let mut walk_forward_dir = self.scene.camera().forward_xy(); - let mut walk_right_dir = self.scene.camera().right_xy(); - let mut freefly_vel = Vec3::zero(); - let mut free_look = false; - let mut auto_walk = false; + // TODO: can this be a method on the session or are there borrowcheck issues? - fn stop_auto_walk(auto_walk: &mut bool, key_state: &mut KeyState, hud: &mut Hud) { - *auto_walk = false; - hud.auto_walk(false); - key_state.auto_walk = false; - } - - // Game loop - let mut current_client_state = self.client.borrow().get_client_state(); - while let ClientState::Pending | ClientState::Character = current_client_state { + let client_state = self.client.borrow().get_client_state(); + if let ClientState::Pending | ClientState::Character = client_state { + // Update MyEntity + // Note: Alternatively, the client could emit an event when the entity changes + // which may or may not be more elegant + { + let my_entity = self.client.borrow().entity(); + self.client + .borrow_mut() + .state_mut() + .ecs_mut() + .insert(MyEntity(my_entity)); + } // Compute camera data self.scene .camera_mut() @@ -229,6 +238,7 @@ impl PlayState for SessionState { }, ) }; + self.is_aiming = is_aiming; let cam_dir: Vec3 = Vec3::from(view_mat.inverted() * -Vec4::unit_z()); @@ -278,7 +288,7 @@ impl PlayState for SessionState { })); // Handle window events. - for event in global_state.window.fetch_events(&mut global_state.settings) { + for event in events { // Pass all events to the ui first. if self.hud.handle_event(event.clone(), global_state) { continue; @@ -331,7 +341,7 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::Respawn, state) if state != self.key_state.respawn => { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); self.key_state.respawn = state; if state { self.client.borrow_mut().respawn(); @@ -348,7 +358,7 @@ impl PlayState for SessionState { { self.key_state.toggle_sit = state; if state { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); self.client.borrow_mut().toggle_sit(); } } @@ -357,31 +367,31 @@ impl PlayState for SessionState { { self.key_state.toggle_dance = state; if state { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); self.client.borrow_mut().toggle_dance(); } } Event::InputUpdate(GameInput::MoveForward, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.up = state }, Event::InputUpdate(GameInput::MoveBack, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.down = state }, Event::InputUpdate(GameInput::MoveLeft, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.left = state }, Event::InputUpdate(GameInput::MoveRight, state) => { if state && global_state.settings.gameplay.stop_auto_walk_on_input { - stop_auto_walk(&mut auto_walk, &mut self.key_state, &mut self.hud); + self.stop_auto_walk(); } self.key_state.right = state }, @@ -509,12 +519,12 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::FreeLook, state) => { match (global_state.settings.gameplay.free_look_behavior, state) { (PressBehavior::Toggle, true) => { - free_look = !free_look; - self.hud.free_look(free_look); + self.free_look = !self.free_look; + self.hud.free_look(self.free_look); }, (PressBehavior::Hold, state) => { - free_look = state; - self.hud.free_look(free_look); + self.free_look = state; + self.hud.free_look(self.free_look); }, _ => {}, }; @@ -522,14 +532,14 @@ impl PlayState for SessionState { Event::InputUpdate(GameInput::AutoWalk, state) => { match (global_state.settings.gameplay.auto_walk_behavior, state) { (PressBehavior::Toggle, true) => { - auto_walk = !auto_walk; - self.key_state.auto_walk = auto_walk; - self.hud.auto_walk(auto_walk); + self.auto_walk = !self.auto_walk; + self.key_state.auto_walk = self.auto_walk; + self.hud.auto_walk(self.auto_walk); }, (PressBehavior::Hold, state) => { - auto_walk = state; - self.key_state.auto_walk = auto_walk; - self.hud.auto_walk(auto_walk); + self.auto_walk = state; + self.key_state.auto_walk = self.auto_walk; + self.hud.auto_walk(self.auto_walk); }, _ => {}, } @@ -567,9 +577,9 @@ impl PlayState for SessionState { } } - if !free_look { - walk_forward_dir = self.scene.camera().forward_xy(); - walk_right_dir = self.scene.camera().right_xy(); + if !self.free_look { + self.walk_forward_dir = self.scene.camera().forward_xy(); + self.walk_right_dir = self.scene.camera().right_xy(); self.inputs.look_dir = Dir::from_unnormalized(cam_dir + aim_dir_offset).unwrap(); } @@ -581,8 +591,9 @@ impl PlayState for SessionState { camera::CameraMode::FirstPerson | camera::CameraMode::ThirdPerson => { // Move the player character based on their walking direction. // This could be different from the camera direction if free look is enabled. - self.inputs.move_dir = walk_right_dir * axis_right + walk_forward_dir * axis_up; - freefly_vel = Vec3::zero(); + self.inputs.move_dir = + self.walk_right_dir * axis_right + self.walk_forward_dir * axis_up; + self.freefly_vel = Vec3::zero(); }, camera::CameraMode::Freefly => { @@ -596,27 +607,27 @@ impl PlayState for SessionState { let right = self.scene.camera().right(); let dir = right * axis_right + forward * axis_up; - let dt = clock.get_last_delta().as_secs_f32(); - if freefly_vel.magnitude_squared() > 0.01 { - let new_vel = - freefly_vel - freefly_vel.normalized() * (FREEFLY_DAMPING * dt); - if freefly_vel.dot(new_vel) > 0.0 { - freefly_vel = new_vel; + let dt = global_state.clock.get_last_delta().as_secs_f32(); + if self.freefly_vel.magnitude_squared() > 0.01 { + let new_vel = self.freefly_vel + - self.freefly_vel.normalized() * (FREEFLY_DAMPING * dt); + if self.freefly_vel.dot(new_vel) > 0.0 { + self.freefly_vel = new_vel; } else { - freefly_vel = Vec3::zero(); + self.freefly_vel = Vec3::zero(); } } if dir.magnitude_squared() > 0.01 { - freefly_vel += dir * (FREEFLY_ACCEL * dt); - if freefly_vel.magnitude() > FREEFLY_MAX_SPEED { - freefly_vel = freefly_vel.normalized() * FREEFLY_MAX_SPEED; + self.freefly_vel += dir * (FREEFLY_ACCEL * dt); + if self.freefly_vel.magnitude() > FREEFLY_MAX_SPEED { + self.freefly_vel = self.freefly_vel.normalized() * FREEFLY_MAX_SPEED; } } let pos = self.scene.camera().get_focus_pos(); self.scene .camera_mut() - .set_focus_pos(pos + freefly_vel * dt); + .set_focus_pos(pos + self.freefly_vel * dt); // Do not apply any movement to the player character self.inputs.move_dir = Vec2::zero(); @@ -626,16 +637,14 @@ impl PlayState for SessionState { self.inputs.climb = self.key_state.climb(); // Runs if either in a multiplayer server or the singleplayer server is unpaused - if global_state.singleplayer.is_none() - || !global_state.singleplayer.as_ref().unwrap().is_paused() - { + if !global_state.paused() { // Perform an in-game tick. - match self.tick(clock.get_avg_delta(), global_state) { + match self.tick(global_state.clock.get_avg_delta(), global_state) { Ok(TickAction::Continue) => {}, // Do nothing Ok(TickAction::Disconnect) => return PlayStateResult::Pop, // Go to main menu Err(err) => { global_state.info_message = - Some(localized_strings.get("common.connection_lost").to_owned()); + Some(self.voxygen_i18n.get("common.connection_lost").to_owned()); error!("[session] Failed to tick the scene: {:?}", err); return PlayStateResult::Pop; @@ -643,19 +652,17 @@ impl PlayState for SessionState { } } - // Maintain global state. - global_state.maintain(clock.get_last_delta().as_secs_f32()); - // Recompute dependents just in case some input modified the camera self.scene .camera_mut() .compute_dependents(&*self.client.borrow().state().terrain()); + // Extract HUD events ensuring the client borrow gets dropped. let mut hud_events = self.hud.maintain( &self.client.borrow(), global_state, DebugInfo { - tps: clock.get_tps(), + tps: global_state.clock.get_tps(), ping_ms: self.client.borrow().get_ping_ms_rolling_avg(), coordinates: self .client @@ -687,7 +694,7 @@ impl PlayState for SessionState { num_figures_visible: self.scene.figure_mgr().figure_count_visible() as u32, }, &self.scene.camera(), - clock.get_last_delta(), + global_state.clock.get_last_delta(), HudInfo { is_aiming, is_first_person: matches!( @@ -698,8 +705,8 @@ impl PlayState for SessionState { ); // Look for changes in the localization files - if localization_watcher.reloaded() { - hud_events.push(HudEvent::ChangeLanguage(localized_strings.metadata.clone())); + if global_state.localization_watcher.reloaded() { + hud_events.push(HudEvent::ChangeLanguage(self.voxygen_i18n.metadata.clone())); } // Maintain the UI. @@ -916,13 +923,13 @@ impl PlayState for SessionState { HudEvent::ChangeLanguage(new_language) => { global_state.settings.language.selected_language = new_language.language_identifier; - localized_strings = load_watched::( + self.voxygen_i18n = load_watched::( &i18n_asset_key(&global_state.settings.language.selected_language), - &mut localization_watcher, + &mut global_state.localization_watcher, ) .unwrap(); - localized_strings.log_missing_entries(); - self.hud.update_language(localized_strings.clone()); + self.voxygen_i18n.log_missing_entries(); + self.hud.update_language(self.voxygen_i18n.clone()); }, HudEvent::ToggleFullscreen => { global_state @@ -978,59 +985,62 @@ impl PlayState for SessionState { }; // Runs if either in a multiplayer server or the singleplayer server is unpaused - if global_state.singleplayer.is_none() - || !global_state.singleplayer.as_ref().unwrap().is_paused() - { + if !global_state.paused() { self.scene.maintain( global_state.window.renderer_mut(), &mut global_state.audio, &scene_data, ); } - - let renderer = global_state.window.renderer_mut(); - // Clear the screen - renderer.clear(); - // Render the screen using the global renderer - self.scene.render( - renderer, - client.state(), - client.entity(), - client.get_tick(), - &scene_data, - ); - // Draw the UI to the screen - self.hud.render(renderer, self.scene.globals()); - // Finish the frame - renderer.flush(); } - // Display the frame on the window. - global_state - .window - .swap_buffers() - .expect("Failed to swap window buffers!"); - - // Wait for the next tick. - clock.tick(Duration::from_millis( - 1000 / global_state.settings.graphics.max_fps as u64, - )); - // Clean things up after the tick. self.cleanup(); - current_client_state = self.client.borrow().get_client_state(); - } - - if let ClientState::Registered = current_client_state { - return PlayStateResult::Switch(Box::new(CharSelectionState::new( + PlayStateResult::Continue + } else if let ClientState::Registered = client_state { + PlayStateResult::Switch(Box::new(CharSelectionState::new( global_state, self.client.clone(), - ))); + ))) + } else { + error!("Client not in the expected state, exiting session play state"); + PlayStateResult::Pop } - - PlayStateResult::Pop } fn name(&self) -> &'static str { "Session" } + + /// Render the session to the screen. + /// + /// This method should be called once per frame. + fn render(&mut self, renderer: &mut Renderer, settings: &Settings) { + // Render the screen using the global renderer + { + let client = self.client.borrow(); + + let scene_data = SceneData { + state: client.state(), + player_entity: client.entity(), + loaded_distance: client.loaded_distance(), + view_distance: client.view_distance().unwrap_or(1), + tick: client.get_tick(), + thread_pool: client.thread_pool(), + gamma: settings.graphics.gamma, + mouse_smoothing: settings.gameplay.smooth_pan_enable, + sprite_render_distance: settings.graphics.sprite_render_distance as f32, + figure_lod_render_distance: settings.graphics.figure_lod_render_distance as f32, + is_aiming: self.is_aiming, + }; + self.scene.render( + renderer, + client.state(), + client.entity(), + client.get_tick(), + &scene_data, + ); + } + // Draw the UI to the screen + self.hud.render(renderer, self.scene.globals()); + } } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index fb6f787e96..3aec021d4c 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -6,11 +6,11 @@ use crate::{ window::{GameInput, KeyMouse}, }; use directories_next::{ProjectDirs, UserDirs}; -use glutin::{MouseButton, VirtualKeyCode}; use hashbrown::{HashMap, HashSet}; use serde_derive::{Deserialize, Serialize}; use std::{fs, io::prelude::*, path::PathBuf}; use tracing::warn; +use winit::event::{MouseButton, VirtualKeyCode}; // ControlSetting-like struct used by Serde, to handle not serializing/building // post-deserializing the inverse_keybindings hashmap diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs index b6a6dc20d7..e5a30b62cf 100644 --- a/voxygen/src/ui/event.rs +++ b/voxygen/src/ui/event.rs @@ -1,26 +1,30 @@ use conrod_core::{event::Input, input::Button}; use vek::*; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Event(pub Input); impl Event { pub fn try_from( - event: glutin::Event, - window: &glutin::ContextWrapper, + event: &winit::event::Event<()>, + window: &glutin::ContextWrapper, ) -> Option { use conrod_winit::*; // A wrapper around the winit window that allows us to implement the trait // necessary for enabling the winit <-> conrod conversion functions. - struct WindowRef<'a>(&'a winit::Window); + struct WindowRef<'a>(&'a winit::window::Window); // Implement the `WinitWindow` trait for `WindowRef` to allow for generating // compatible conversion functions. impl<'a> conrod_winit::WinitWindow for WindowRef<'a> { fn get_inner_size(&self) -> Option<(u32, u32)> { - winit::Window::get_inner_size(&self.0).map(Into::into) + Some( + winit::window::Window::inner_size(&self.0) + .to_logical::(self.hidpi_factor()) + .into(), + ) } - fn hidpi_factor(&self) -> f32 { winit::Window::get_hidpi_factor(&self.0) as _ } + fn hidpi_factor(&self) -> f64 { winit::window::Window::scale_factor(&self.0) } } convert_event!(event, &WindowRef(window.window())).map(Self) } diff --git a/voxygen/src/ui/scale.rs b/voxygen/src/ui/scale.rs index e946d2c570..01eb21afba 100644 --- a/voxygen/src/ui/scale.rs +++ b/voxygen/src/ui/scale.rs @@ -19,7 +19,7 @@ pub enum ScaleMode { pub struct Scale { mode: ScaleMode, // Current dpi factor - dpi_factor: f64, + scale_factor: f64, // Current logical window size window_dims: Vec2, } @@ -27,10 +27,10 @@ pub struct Scale { impl Scale { pub fn new(window: &Window, mode: ScaleMode) -> Self { let window_dims = window.logical_size(); - let dpi_factor = window.renderer().get_resolution().x as f64 / window_dims.x; + let scale_factor = window.renderer().get_resolution().x as f64 / window_dims.x; Scale { mode, - dpi_factor, + scale_factor, window_dims, } } @@ -54,7 +54,7 @@ impl Scale { // coordinates. pub fn scale_factor_logical(&self) -> f64 { match self.mode { - ScaleMode::Absolute(scale) => scale / self.dpi_factor, + ScaleMode::Absolute(scale) => scale / self.scale_factor, ScaleMode::DpiFactor => 1.0, ScaleMode::RelativeToWindow(dims) => { (self.window_dims.x / dims.x).min(self.window_dims.y / dims.y) @@ -64,11 +64,11 @@ impl Scale { // Calculate factor to transform between physical coordinates and our scaled // coordinates. - pub fn scale_factor_physical(&self) -> f64 { self.scale_factor_logical() * self.dpi_factor } + pub fn scale_factor_physical(&self) -> f64 { self.scale_factor_logical() * self.scale_factor } - // Updates internal window size (and/or dpi_factor). + // Updates internal window size (and/or scale_factor). pub fn window_resized(&mut self, new_dims: Vec2, renderer: &Renderer) { - self.dpi_factor = renderer.get_resolution().x as f64 / new_dims.x; + self.scale_factor = renderer.get_resolution().x as f64 / new_dims.x; self.window_dims = new_dims; } diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index 73b75de715..c50dad3496 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -4,10 +4,10 @@ use crate::{ settings::{ControlSettings, Settings}, ui, Error, }; +use crossbeam::channel; use gilrs::{EventType, Gilrs}; use hashbrown::HashMap; - -use crossbeam::channel; +use old_school_gfx_glutin_ext::{ContextBuilderExt, WindowInitExt, WindowUpdateExt}; use serde_derive::{Deserialize, Serialize}; use std::fmt; use tracing::{error, info, warn}; @@ -241,7 +241,7 @@ pub enum AnalogGameInput { } /// Represents an incoming event from the window. -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum Event { /// The window has been requested to close. Close, @@ -280,19 +280,20 @@ pub enum Event { ScreenshotMessage(String), } -pub type MouseButton = winit::MouseButton; -pub type PressState = winit::ElementState; +pub type MouseButton = winit::event::MouseButton; +pub type PressState = winit::event::ElementState; +pub type EventLoop = winit::event_loop::EventLoop<()>; #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub enum KeyMouse { - Key(glutin::VirtualKeyCode), - Mouse(glutin::MouseButton), + Key(winit::event::VirtualKeyCode), + Mouse(winit::event::MouseButton), } impl fmt::Display for KeyMouse { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::KeyMouse::*; - use glutin::{MouseButton, VirtualKeyCode::*}; + use winit::event::{MouseButton, VirtualKeyCode::*}; write!(f, "{}", match self { Key(Key1) => "1", Key(Key2) => "2", @@ -466,23 +467,23 @@ impl fmt::Display for KeyMouse { } pub struct Window { - events_loop: glutin::EventsLoop, renderer: Renderer, - window: glutin::ContextWrapper, + window: glutin::ContextWrapper, cursor_grabbed: bool, pub pan_sensitivity: u32, pub zoom_sensitivity: u32, pub zoom_inversion: bool, pub mouse_y_inversion: bool, fullscreen: bool, + modifiers: winit::event::ModifiersState, needs_refresh_resize: bool, - keypress_map: HashMap, + keypress_map: HashMap, pub remapping_keybindings: Option, - supplement_events: Vec, + events: Vec, focused: bool, gilrs: Option, controller_settings: ControllerSettings, - cursor_position: winit::dpi::LogicalPosition, + cursor_position: winit::dpi::PhysicalPosition, mouse_emulation_vec: Vec2, // Currently used to send and receive screenshot result messages message_sender: channel::Sender, @@ -490,30 +491,32 @@ pub struct Window { } impl Window { - pub fn new(settings: &Settings) -> Result { - let events_loop = glutin::EventsLoop::new(); + pub fn new(settings: &Settings) -> Result<(Window, EventLoop), Error> { + let event_loop = EventLoop::new(); let size = settings.graphics.window_size; - let win_builder = glutin::WindowBuilder::new() + let win_builder = winit::window::WindowBuilder::new() .with_title("Veloren") - .with_dimensions(glutin::dpi::LogicalSize::new( - size[0] as f64, - size[1] as f64, - )) + .with_inner_size(winit::dpi::LogicalSize::new(size[0] as f64, size[1] as f64)) .with_maximized(true); - let ctx_builder = glutin::ContextBuilder::new() - .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) - .with_vsync(false); + // Avoid cpal / winit OleInitialize conflict + // See: https://github.com/rust-windowing/winit/pull/1524 + #[cfg(target_os = "windows")] + let win_builder = winit::platform::windows::WindowBuilderExtWindows::with_drag_and_drop( + win_builder, + false, + ); let (window, device, factory, win_color_view, win_depth_view) = - gfx_window_glutin::init::( - win_builder, - ctx_builder, - &events_loop, - ) - .map_err(|err| Error::BackendError(Box::new(err)))?; + glutin::ContextBuilder::new() + .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 2))) + .with_vsync(false) + .with_gfx_color_depth::() + .build_windowed(win_builder, &event_loop) + .map_err(|err| Error::BackendError(Box::new(err)))? + .init_gfx::(); let vendor = device.get_info().platform_name.vendor; let renderer = device.get_info().platform_name.renderer; @@ -559,7 +562,6 @@ impl Window { ) = channel::unbounded::(); let mut this = Self { - events_loop, renderer: Renderer::new( device, factory, @@ -576,14 +578,15 @@ impl Window { zoom_inversion: settings.gameplay.zoom_inversion, mouse_y_inversion: settings.gameplay.mouse_y_inversion, fullscreen: false, + modifiers: Default::default(), needs_refresh_resize: false, keypress_map, remapping_keybindings: None, - supplement_events: vec![], + events: Vec::new(), focused: true, gilrs, controller_settings, - cursor_position: winit::dpi::LogicalPosition::new(0.0, 0.0), + cursor_position: winit::dpi::PhysicalPosition::new(0.0, 0.0), mouse_emulation_vec: Vec2::zero(), // Currently used to send and receive screenshot result messages message_sender, @@ -592,195 +595,30 @@ impl Window { this.fullscreen(settings.graphics.fullscreen); - Ok(this) + Ok((this, event_loop)) + } + + pub fn window( + &self, + ) -> &glutin::ContextWrapper { + &self.window } pub fn renderer(&self) -> &Renderer { &self.renderer } pub fn renderer_mut(&mut self) -> &mut Renderer { &mut self.renderer } - #[allow(clippy::match_bool)] // TODO: Pending review in #587 - pub fn fetch_events(&mut self, settings: &mut Settings) -> Vec { - let mut events = vec![]; - events.append(&mut self.supplement_events); + pub fn fetch_events(&mut self) -> Vec { // Refresh ui size (used when changing playstates) if self.needs_refresh_resize { - events.push(Event::Ui(ui::Event::new_resize(self.logical_size()))); + self.events + .push(Event::Ui(ui::Event::new_resize(self.logical_size()))); self.needs_refresh_resize = false; } // Receive any messages sent through the message channel - self.message_receiver - .try_iter() - .for_each(|message| events.push(Event::ScreenshotMessage(message))); - - // Copy data that is needed by the events closure to avoid lifetime errors. - // TODO: Remove this if/when the compiler permits it. - let cursor_grabbed = self.cursor_grabbed; - let renderer = &mut self.renderer; - let window = &mut self.window; - let remapping_keybindings = &mut self.remapping_keybindings; - let focused = &mut self.focused; - let controls = &mut settings.controls; - let keypress_map = &mut self.keypress_map; - let pan_sensitivity = self.pan_sensitivity; - let zoom_sensitivity = self.zoom_sensitivity; - let zoom_inversion = match self.zoom_inversion { - true => -1.0, - false => 1.0, - }; - let mouse_y_inversion = match self.mouse_y_inversion { - true => -1.0, - false => 1.0, - }; - let mut toggle_fullscreen = false; - let mut take_screenshot = false; - let mut cursor_position = None; - - self.events_loop.poll_events(|event| { - // Get events for ui. - if let Some(event) = ui::Event::try_from(event.clone(), window) { - events.push(Event::Ui(event)); - } - - match event { - glutin::Event::WindowEvent { event, .. } => match event { - glutin::WindowEvent::CloseRequested => events.push(Event::Close), - glutin::WindowEvent::Resized(glutin::dpi::LogicalSize { width, height }) => { - let (mut color_view, mut depth_view) = renderer.win_views_mut(); - gfx_window_glutin::update_views(window, &mut color_view, &mut depth_view); - renderer.on_resize().unwrap(); - events.push(Event::Resize(Vec2::new(width as u32, height as u32))); - }, - glutin::WindowEvent::Moved(glutin::dpi::LogicalPosition { x, y }) => { - events.push(Event::Moved(Vec2::new(x as u32, y as u32))) - }, - glutin::WindowEvent::ReceivedCharacter(c) => events.push(Event::Char(c)), - glutin::WindowEvent::MouseInput { button, state, .. } => { - if let (true, Some(game_inputs)) = ( - cursor_grabbed, - Window::map_input( - KeyMouse::Mouse(button), - controls, - remapping_keybindings, - ), - ) { - for game_input in game_inputs { - events.push(Event::InputUpdate( - *game_input, - state == glutin::ElementState::Pressed, - )); - } - } - events.push(Event::MouseButton(button, state)); - }, - glutin::WindowEvent::KeyboardInput { input, .. } => { - if let Some(key) = input.virtual_keycode { - if let Some(game_inputs) = Window::map_input( - KeyMouse::Key(key), - controls, - remapping_keybindings, - ) { - for game_input in game_inputs { - match game_input { - GameInput::Fullscreen => { - if input.state == glutin::ElementState::Pressed - && !Self::is_pressed( - keypress_map, - GameInput::Fullscreen, - ) - { - toggle_fullscreen = !toggle_fullscreen; - } - Self::set_pressed( - keypress_map, - GameInput::Fullscreen, - input.state, - ); - }, - GameInput::Screenshot => { - take_screenshot = input.state - == glutin::ElementState::Pressed - && !Self::is_pressed( - keypress_map, - GameInput::Screenshot, - ); - Self::set_pressed( - keypress_map, - GameInput::Screenshot, - input.state, - ); - }, - _ => events.push(Event::InputUpdate( - *game_input, - input.state == glutin::ElementState::Pressed, - )), - } - } - } - } - }, - - glutin::WindowEvent::Focused(state) => { - *focused = state; - events.push(Event::Focused(state)); - }, - glutin::WindowEvent::CursorMoved { position, .. } => { - cursor_position = Some(position); - }, - _ => {}, - }, - glutin::Event::DeviceEvent { event, .. } => match event { - glutin::DeviceEvent::MouseMotion { - delta: (dx, dy), .. - } if *focused => { - let delta = Vec2::new( - dx as f32 * (pan_sensitivity as f32 / 100.0), - dy as f32 * (pan_sensitivity as f32 * mouse_y_inversion / 100.0), - ); - - if cursor_grabbed { - events.push(Event::CursorPan(delta)); - } else { - events.push(Event::CursorMove(delta)); - } - }, - glutin::DeviceEvent::MouseWheel { delta, .. } if cursor_grabbed && *focused => { - events.push(Event::Zoom({ - // Since scrolling apparently acts different depending on platform - #[cfg(target_os = "windows")] - const PLATFORM_FACTOR: f32 = -4.0; - #[cfg(not(target_os = "windows"))] - const PLATFORM_FACTOR: f32 = 1.0; - - let y = match delta { - glutin::MouseScrollDelta::LineDelta(_x, y) => y, - // TODO: Check to see if there is a better way to find the "line - // height" than just hardcoding 16.0 pixels. Alternately we could - // get rid of this and have the user set zoom sensitivity, since - // it's unlikely people would expect a configuration file to work - // across operating systems. - glutin::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32, - }; - y * (zoom_sensitivity as f32 / 100.0) * zoom_inversion * PLATFORM_FACTOR - })) - }, - _ => {}, - }, - _ => {}, - } - }); - - if let Some(pos) = cursor_position { - self.cursor_position = pos; - } - - if take_screenshot { - self.take_screenshot(&settings); - } - - if toggle_fullscreen { - self.toggle_fullscreen(settings); + for message in self.message_receiver.try_iter() { + self.events.push(Event::ScreenshotMessage(message)) } if let Some(gilrs) = &mut self.gilrs { @@ -808,7 +646,7 @@ impl Window { | EventType::ButtonRepeated(button, code) => { handle_buttons( &self.controller_settings, - &mut events, + &mut self.events, &Button::from((button, code)), true, ); @@ -816,7 +654,7 @@ impl Window { EventType::ButtonReleased(button, code) => { handle_buttons( &self.controller_settings, - &mut events, + &mut self.events, &Button::from((button, code)), false, ); @@ -843,13 +681,14 @@ impl Window { }, EventType::AxisChanged(axis, value, code) => { - let value = match self + let value = if self .controller_settings .inverted_axes .contains(&Axis::from((axis, code))) { - true => value * -1.0, - false => value, + -value + } else { + value }; let value = self @@ -865,17 +704,17 @@ impl Window { for action in actions { match *action { AxisGameAction::MovementX => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::MovementX(value), )); }, AxisGameAction::MovementY => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::MovementY(value), )); }, AxisGameAction::CameraX => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::CameraX( value * self.controller_settings.pan_sensitivity @@ -885,7 +724,7 @@ impl Window { )); }, AxisGameAction::CameraY => { - events.push(Event::AnalogGameInput( + self.events.push(Event::AnalogGameInput( AnalogGameInput::CameraY( value * self.controller_settings.pan_sensitivity @@ -906,22 +745,22 @@ impl Window { for action in actions { match *action { AxisMenuAction::MoveX => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::MoveX(value), )); }, AxisMenuAction::MoveY => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::MoveY(value), )); }, AxisMenuAction::ScrollX => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::ScrollX(value), )); }, AxisMenuAction::ScrollY => { - events.push(Event::AnalogMenuInput( + self.events.push(Event::AnalogMenuInput( AnalogMenuInput::ScrollY(value), )); }, @@ -936,6 +775,7 @@ impl Window { } } + let mut events = std::mem::take(&mut self.events); // Mouse emulation for the menus, to be removed when a proper menu navigation // system is available if !self.cursor_grabbed { @@ -952,46 +792,231 @@ impl Window { self.mouse_emulation_vec.y = d * -1.0; None }, - _ => { - let event = Event::AnalogMenuInput(input); - Some(event) - }, - }, - Event::MenuInput(input, state) => match input { - MenuInput::Apply => Some(match state { - true => Event::Ui(ui::Event(conrod_core::event::Input::Press( - conrod_core::input::Button::Mouse( - conrod_core::input::state::mouse::Button::Left, - ), - ))), - false => Event::Ui(ui::Event(conrod_core::event::Input::Release( - conrod_core::input::Button::Mouse( - conrod_core::input::state::mouse::Button::Left, - ), - ))), - }), - _ => Some(event), + input => Some(Event::AnalogMenuInput(input)), }, + Event::MenuInput(MenuInput::Apply, state) => Some(match state { + true => Event::Ui(ui::Event(conrod_core::event::Input::Press( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + false => Event::Ui(ui::Event(conrod_core::event::Input::Release( + conrod_core::input::Button::Mouse( + conrod_core::input::state::mouse::Button::Left, + ), + ))), + }), _ => Some(event), }) .collect(); + let sensitivity = self.controller_settings.mouse_emulation_sensitivity; - if self.mouse_emulation_vec != Vec2::zero() { - self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32) - .unwrap_or(()); - } + // TODO: make this independent of framerate + // TODO: consider multiplying by scale factor + self.offset_cursor(self.mouse_emulation_vec * sensitivity as f32); } + events } + pub fn handle_device_event(&mut self, event: winit::event::DeviceEvent) { + use winit::event::DeviceEvent; + + let mouse_y_inversion = match self.mouse_y_inversion { + true => -1.0, + false => 1.0, + }; + + match event { + DeviceEvent::MouseMotion { + delta: (dx, dy), .. + } if self.focused => { + let delta = Vec2::new( + dx as f32 * (self.pan_sensitivity as f32 / 100.0), + dy as f32 * (self.pan_sensitivity as f32 * mouse_y_inversion / 100.0), + ); + + if self.cursor_grabbed { + self.events.push(Event::CursorPan(delta)); + } else { + self.events.push(Event::CursorMove(delta)); + } + }, + DeviceEvent::MouseWheel { delta, .. } if self.cursor_grabbed && self.focused => { + self.events.push(Event::Zoom({ + // Since scrolling apparently acts different depending on platform + #[cfg(target_os = "windows")] + const PLATFORM_FACTOR: f32 = -4.0; + #[cfg(not(target_os = "windows"))] + const PLATFORM_FACTOR: f32 = 1.0; + + let y = match delta { + winit::event::MouseScrollDelta::LineDelta(_x, y) => y, + // TODO: Check to see if there is a better way to find the "line + // height" than just hardcoding 16.0 pixels. Alternately we could + // get rid of this and have the user set zoom sensitivity, since + // it's unlikely people would expect a configuration file to work + // across operating systems. + winit::event::MouseScrollDelta::PixelDelta(pos) => (pos.y / 16.0) as f32, + }; + y * (self.zoom_sensitivity as f32 / 100.0) + * if self.zoom_inversion { -1.0 } else { 1.0 } + * PLATFORM_FACTOR + })) + }, + _ => {}, + } + } + + pub fn handle_window_event( + &mut self, + event: winit::event::WindowEvent, + settings: &mut Settings, + ) { + use winit::event::WindowEvent; + + let controls = &mut settings.controls; + // TODO: these used to be used to deduplicate events which they no longer do + // this needs to be handled elsewhere + let mut toggle_fullscreen = false; + let mut take_screenshot = false; + + match event { + WindowEvent::CloseRequested => self.events.push(Event::Close), + WindowEvent::Resized(winit::dpi::PhysicalSize { width, height }) => { + let (mut color_view, mut depth_view) = self.renderer.win_views_mut(); + self.window.update_gfx(&mut color_view, &mut depth_view); + self.renderer.on_resize().unwrap(); + // TODO: update users of this event with the fact that it is now the physical + // size + self.events + .push(Event::Resize(Vec2::new(width as u32, height as u32))); + }, + WindowEvent::ReceivedCharacter(c) => self.events.push(Event::Char(c)), + WindowEvent::MouseInput { button, state, .. } => { + if let (true, Some(game_inputs)) = + // Mouse input not mapped to input if it is not grabbed + ( + self.cursor_grabbed, + Window::map_input( + KeyMouse::Mouse(button), + controls, + &mut self.remapping_keybindings, + ), + ) { + for game_input in game_inputs { + self.events.push(Event::InputUpdate( + *game_input, + state == winit::event::ElementState::Pressed, + )); + } + } + self.events.push(Event::MouseButton(button, state)); + }, + WindowEvent::ModifiersChanged(modifiers) => self.modifiers = modifiers, + WindowEvent::KeyboardInput { + input, + is_synthetic, + .. + } => { + // Ignore synthetic tab presses so that we don't get tabs when alt-tabbing back + // into the window + if matches!( + input.virtual_keycode, + Some(winit::event::VirtualKeyCode::Tab) + ) && is_synthetic + { + return; + } + // Ignore Alt-F4 so we don't try to do anything heavy like take a screenshot + // when the window is about to close + if matches!(input, winit::event::KeyboardInput { + state: winit::event::ElementState::Pressed, + virtual_keycode: Some(winit::event::VirtualKeyCode::F4), + .. + }) && self.modifiers.alt() + { + return; + } + + if let Some(key) = input.virtual_keycode { + if let Some(game_inputs) = Window::map_input( + KeyMouse::Key(key), + controls, + &mut self.remapping_keybindings, + ) { + for game_input in game_inputs { + match game_input { + GameInput::Fullscreen => { + if input.state == winit::event::ElementState::Pressed + && !Self::is_pressed( + &mut self.keypress_map, + GameInput::Fullscreen, + ) + { + toggle_fullscreen = !toggle_fullscreen; + } + Self::set_pressed( + &mut self.keypress_map, + GameInput::Fullscreen, + input.state, + ); + }, + GameInput::Screenshot => { + take_screenshot = input.state + == winit::event::ElementState::Pressed + && !Self::is_pressed( + &mut self.keypress_map, + GameInput::Screenshot, + ); + Self::set_pressed( + &mut self.keypress_map, + GameInput::Screenshot, + input.state, + ); + }, + _ => self.events.push(Event::InputUpdate( + *game_input, + input.state == winit::event::ElementState::Pressed, + )), + } + } + } + } + }, + WindowEvent::Focused(state) => { + self.focused = state; + self.events.push(Event::Focused(state)); + }, + WindowEvent::CursorMoved { position, .. } => { + self.cursor_position = position; + }, + _ => {}, + } + + if take_screenshot { + self.take_screenshot(&settings); + } + + if toggle_fullscreen { + self.toggle_fullscreen(settings); + } + } + /// Moves cursor by an offset - pub fn offset_cursor(&self, d: Vec2) -> Result<(), String> { - self.window - .window() - .set_cursor_position(winit::dpi::LogicalPosition::new( - d.x as f64 + self.cursor_position.x, - d.y as f64 + self.cursor_position.y, - )) + pub fn offset_cursor(&self, d: Vec2) { + if d != Vec2::zero() { + if let Err(err) = + self.window + .window() + .set_cursor_position(winit::dpi::LogicalPosition::new( + d.x as f64 + self.cursor_position.x, + d.y as f64 + self.cursor_position.y, + )) + { + error!("Error setting cursor position: {:?}", err); + } + } } pub fn swap_buffers(&self) -> Result<(), Error> { @@ -1004,8 +1029,8 @@ impl Window { pub fn grab_cursor(&mut self, grab: bool) { self.cursor_grabbed = grab; - self.window.window().hide_cursor(grab); - let _ = self.window.window().grab_cursor(grab); + self.window.window().set_cursor_visible(!grab); + let _ = self.window.window().set_cursor_grab(grab); } pub fn toggle_fullscreen(&mut self, settings: &mut Settings) { @@ -1020,7 +1045,13 @@ impl Window { let window = self.window.window(); self.fullscreen = fullscreen; if fullscreen { - window.set_fullscreen(Some(window.get_current_monitor())); + window.set_fullscreen(Some(winit::window::Fullscreen::Exclusive( + window + .current_monitor() + .video_modes() + .max_by_key(|mode| mode.size().width) + .expect("No video modes available!!"), + ))); } else { window.set_fullscreen(None); } @@ -1033,8 +1064,8 @@ impl Window { let (w, h) = self .window .window() - .get_inner_size() - .unwrap_or(glutin::dpi::LogicalSize::new(0.0, 0.0)) + .inner_size() + .to_logical::(self.window.window().scale_factor()) .into(); Vec2::new(w, h) } @@ -1048,7 +1079,7 @@ impl Window { )); } - pub fn send_supplement_event(&mut self, event: Event) { self.supplement_events.push(event) } + pub fn send_event(&mut self, event: Event) { self.events.push(event) } pub fn take_screenshot(&mut self, settings: &Settings) { match self.renderer.create_screenshot() { @@ -1086,15 +1117,20 @@ impl Window { } } - fn is_pressed(map: &mut HashMap, input: GameInput) -> bool { - *(map.entry(input).or_insert(glutin::ElementState::Released)) - == glutin::ElementState::Pressed + fn is_pressed( + map: &mut HashMap, + input: GameInput, + ) -> bool { + *(map + .entry(input) + .or_insert(winit::event::ElementState::Released)) + == winit::event::ElementState::Pressed } fn set_pressed( - map: &mut HashMap, + map: &mut HashMap, input: GameInput, - state: glutin::ElementState, + state: winit::event::ElementState, ) { map.insert(input, state); } @@ -1109,6 +1145,7 @@ impl Window { remapping: &mut Option, ) -> Option> { match *remapping { + // TODO: save settings Some(game_input) => { controls.modify_binding(game_input, key_mouse); *remapping = None;