diff --git a/Cargo.lock b/Cargo.lock index 77eada9a..cfb43eac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,6 +608,13 @@ dependencies = [ "urlencoding", ] +[[package]] +name = "assets-splitting" +version = "0.1.0" +dependencies = [ + "anyhow", +] + [[package]] name = "async-executor" version = "1.13.2" @@ -1335,6 +1342,17 @@ dependencies = [ "wayland-client", ] +[[package]] +name = "camera" +version = "0.1.0" +dependencies = [ + "graphics", + "graphics-types", + "hiarc", + "map", + "math", +] + [[package]] name = "cc" version = "1.2.20" @@ -1557,10 +1575,10 @@ dependencies = [ "anyhow", "arrayvec 0.7.6", "assets-base", + "assets-splitting", "base", "base-io", "base-io-traits", - "client-extra", "either", "fixed", "game-interface", @@ -1602,7 +1620,6 @@ dependencies = [ "graphics", "graphics-backend", "graphics-types", - "hiarc", "log 0.4.27", "math", "pool", @@ -1614,13 +1631,6 @@ dependencies = [ "ui-generic", ] -[[package]] -name = "client-extra" -version = "0.1.0" -dependencies = [ - "anyhow", -] - [[package]] name = "client-ghost" version = "0.1.0" @@ -1709,6 +1719,7 @@ dependencies = [ "base", "base-io", "bincode", + "camera", "client-containers", "config", "fixed", @@ -1722,6 +1733,7 @@ dependencies = [ "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", "image-utils", + "legacy-map", "log 0.4.27", "map", "math", @@ -1729,6 +1741,7 @@ dependencies = [ "num-traits", "pool", "rayon", + "rustc-hash 2.1.1", "serde", "sound", "strum 0.27.1", @@ -1743,6 +1756,7 @@ version = "0.1.0" dependencies = [ "base", "base-io", + "camera", "client-containers", "client-render", "client-render-base", @@ -1755,7 +1769,6 @@ dependencies = [ "game-interface", "graphics", "graphics-types", - "hiarc", "map", "math", "num-traits", @@ -2721,6 +2734,7 @@ dependencies = [ "bincode", "binds", "bytes", + "camera", "chrono", "client-accounts", "client-console", @@ -2765,7 +2779,7 @@ dependencies = [ "hiarc", "image-utils", "input-binds", - "legacy_proxy", + "legacy-proxy", "log 0.4.27", "map", "math", @@ -3237,6 +3251,7 @@ dependencies = [ "base-io", "base-io-traits", "bincode", + "camera", "client-containers", "client-notifications", "client-render-base", @@ -3260,6 +3275,7 @@ dependencies = [ "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", "image-utils", + "legacy-map", "log 0.4.27", "map", "map-convert-lib", @@ -3576,10 +3592,10 @@ dependencies = [ name = "emoticon-convert" version = "0.1.0" dependencies = [ + "assets-base", + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", - "tar", ] [[package]] @@ -3796,10 +3812,10 @@ checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" name = "extra-convert" version = "0.1.0" dependencies = [ + "assets-base", + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", - "tar", ] [[package]] @@ -4138,21 +4154,13 @@ dependencies = [ "base", "command-parser", "config", - "flate2", "game-config", "game-interface", - "graphics-types", "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", - "image-utils", "indexmap 2.9.0", - "itertools 0.14.0", - "map", "math", - "num-derive", - "num-traits", "pool", - "rayon", "rustc-hash 2.1.1", "serde", "serde_with", @@ -4184,10 +4192,10 @@ dependencies = [ name = "game-convert" version = "0.1.0" dependencies = [ + "assets-base", + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", - "tar", ] [[package]] @@ -4553,6 +4561,7 @@ dependencies = [ "replace_with", "serde", "serde_json", + "strum 0.27.1", "thiserror 2.0.12", "thread-priority", ] @@ -4616,6 +4625,7 @@ dependencies = [ "num-traits", "pool", "serde", + "strum 0.27.1", ] [[package]] @@ -4999,10 +5009,10 @@ dependencies = [ name = "hud-convert" version = "0.1.0" dependencies = [ + "assets-base", + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", - "tar", ] [[package]] @@ -5696,7 +5706,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] -name = "legacy_proxy" +name = "legacy-map" +version = "0.1.0" +dependencies = [ + "anyhow", + "base", + "config", + "flate2", + "graphics-types", + "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", + "hiarc", + "image-utils", + "itertools 0.14.0", + "map", + "math", + "num-derive", + "num-traits", + "rayon", + "serde", + "time", +] + +[[package]] +name = "legacy-proxy" version = "0.1.0" dependencies = [ "anyhow", @@ -5711,7 +5743,9 @@ dependencies = [ "game-interface", "game-network", "game-server", + "hex", "hexdump", + "legacy-map", "libtw2-gamenet-ddnet", "libtw2-net", "libtw2-packer", @@ -5803,7 +5837,7 @@ dependencies = [ [[package]] name = "libtw2-common" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "zerocopy 0.7.35", @@ -5812,7 +5846,7 @@ dependencies = [ [[package]] name = "libtw2-gamenet-common" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "buffer", @@ -5828,7 +5862,7 @@ dependencies = [ [[package]] name = "libtw2-gamenet-ddnet" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "buffer", @@ -5843,7 +5877,7 @@ dependencies = [ [[package]] name = "libtw2-gamenet-snap" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "buffer", "libtw2-common", @@ -5855,7 +5889,7 @@ dependencies = [ [[package]] name = "libtw2-huffman" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "buffer", @@ -5866,7 +5900,7 @@ dependencies = [ [[package]] name = "libtw2-net" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "buffer", @@ -5883,7 +5917,7 @@ dependencies = [ [[package]] name = "libtw2-packer" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "arrayvec 0.5.2", "buffer", @@ -5895,7 +5929,7 @@ dependencies = [ [[package]] name = "libtw2-snapshot" version = "0.0.1" -source = "git+https://github.com/Jupeyy/libtw2.git?rev=d81eff37fcefdffbd309015e445b9c7cfd167b95#d81eff37fcefdffbd309015e445b9c7cfd167b95" +source = "git+https://github.com/Jupeyy/libtw2.git?rev=0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57#0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" dependencies = [ "buffer", "libtw2-common", @@ -6154,6 +6188,7 @@ name = "map" version = "0.1.0" dependencies = [ "anyhow", + "assets-base", "base", "base-fs", "base-io", @@ -6165,6 +6200,7 @@ dependencies = [ "flate2", "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", + "image-utils", "is_sorted", "lz4_flex", "math", @@ -6202,7 +6238,7 @@ dependencies = [ "base-fs", "base-io", "difference", - "game-base", + "legacy-map", "map", "ogg-opus", "oxipng", @@ -7486,10 +7522,10 @@ dependencies = [ name = "part-convert" version = "0.1.0" dependencies = [ + "assets-base", + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", - "tar", ] [[package]] @@ -9223,8 +9259,8 @@ checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" name = "skin-convert" version = "0.1.0" dependencies = [ + "assets-splitting", "clap 4.5.37", - "client-extra", "image-utils", ] @@ -9607,6 +9643,7 @@ dependencies = [ "graphics-types", "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", + "legacy-map", "log 0.4.27", "map", "math", @@ -10919,6 +10956,7 @@ dependencies = [ "game-interface", "hashlink 0.10.0 (git+https://github.com/Jupeyy/hashlink/?branch=ddnet)", "hiarc", + "legacy-map", "log 0.4.27", "map", "math", diff --git a/Cargo.toml b/Cargo.toml index b1a5a7f1..d386ca09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,7 +60,7 @@ members = [ "src/skin-convert", "src/part-convert", "src/master-server", - "game/client-extra", + "game/assets-splitting", "game/client-map", "game/client-render-base", "game/client-render", @@ -109,7 +109,7 @@ members = [ "game/community", "lib/input-binds", "game/ghost", "game/client-ghost", "game/client-replay", "game/editor-wasm", "game/client-notifications", "src/editor-server", "src/extra-convert", "game/editor-interface", "game/editor-auto-mapper-wasm", "game/api-auto-mapper", - "examples/wasm-modules/auto-mapper", "game/legacy_proxy", + "examples/wasm-modules/auto-mapper", "game/legacy-proxy", "game/camera", "game/legacy-map", ] [package] @@ -179,7 +179,8 @@ prediction-timer = { path = "game/prediction-timer" } editor-wasm = { path = "game/editor-wasm", default-features = false } game-state-wasm = { path = "game/game-state-wasm" } editor = { path = "game/editor", default-features = false } -legacy_proxy = { path = "game/legacy_proxy" } +legacy-proxy = { path = "game/legacy-proxy" } +camera = { path = "game/camera" } egui-winit = { version = "0.31.1", default-features = false, features = ["x11", "arboard", "links"] } tokio = { version = "1.45.0", features = ["rt-multi-thread", "sync", "fs", "time", "macros"] } diff --git a/data b/data index f84b8e34..58a84fc6 160000 --- a/data +++ b/data @@ -1 +1 @@ -Subproject commit f84b8e34b74ad4052ad2f49b1a9e9ff00144a9d7 +Subproject commit 58a84fc69d110f4e88f2d681c8e781b709248b52 diff --git a/examples/wasm-modules/prediction_timer_ui/src/pred_timer_ui/ui/plot.rs b/examples/wasm-modules/prediction_timer_ui/src/pred_timer_ui/ui/plot.rs index c59fccbd..4ae6fc0d 100644 --- a/examples/wasm-modules/prediction_timer_ui/src/pred_timer_ui/ui/plot.rs +++ b/examples/wasm-modules/prediction_timer_ui/src/pred_timer_ui/ui/plot.rs @@ -21,7 +21,7 @@ pub fn render(ui: &mut egui::Ui, history: &mut VecDeque) { let line = Line::new("", sin); Plot::new(name) - .height(150.0) + .height(75.0) .show(ui, |plot_ui| plot_ui.line(line)); }; diff --git a/examples/wasm-modules/state/Cargo.toml b/examples/wasm-modules/state/Cargo.toml index e827482f..46457156 100644 --- a/examples/wasm-modules/state/Cargo.toml +++ b/examples/wasm-modules/state/Cargo.toml @@ -19,6 +19,7 @@ command-parser = { path = "../../../lib/command-parser" } api-state = { path = "../../../game/api-state" } game-base = { path = "../../../game/game-base" } +legacy-map = { path = "../../../game/legacy-map" } vanilla = { path = "../../../game/vanilla" } game-interface = { path = "../../../game/game-interface" } map = { path = "../../../game/map" } diff --git a/game/assets-base/src/lib.rs b/game/assets-base/src/lib.rs index 36ca8cf2..7296e05b 100644 --- a/game/assets-base/src/lib.rs +++ b/game/assets-base/src/lib.rs @@ -1,4 +1,4 @@ -pub mod loader; +pub mod tar; pub mod verify; use std::collections::HashMap; diff --git a/game/assets-base/src/loader.rs b/game/assets-base/src/loader.rs deleted file mode 100644 index fb647286..00000000 --- a/game/assets-base/src/loader.rs +++ /dev/null @@ -1,45 +0,0 @@ -use std::{io::Read, path::PathBuf}; - -use anyhow::anyhow; -use rustc_hash::FxHashMap; -use tar::EntryType; - -pub fn read_tar(file: &[u8]) -> anyhow::Result>> { - let mut file = tar::Archive::new(std::io::Cursor::new(file)); - match file.entries() { - Ok(entries) => entries - .filter_map(|entry| { - entry - .map_err(|err| anyhow::anyhow!(err)) - .map(|mut entry| { - let ty = entry.header().entry_type(); - if let EntryType::Regular = ty { - Some( - entry - .path() - .map(|path| path.to_path_buf()) - .map_err(|err| anyhow::anyhow!(err)) - .and_then(|path| { - let mut file: Vec<_> = Default::default(); - - entry - .read_to_end(&mut file) - .map(|_| (path, file)) - .map_err(|err| anyhow::anyhow!(err)) - }), - ) - } else if matches!(ty, EntryType::Directory) { - None - } else { - Some(Err(anyhow!( - "The tar reader expects files & dictionaries only" - ))) - } - }) - .transpose() - .map(|r| r.and_then(|r| r)) - }) - .collect::>>(), - Err(err) => Err(anyhow::anyhow!(err)), - } -} diff --git a/game/assets-base/src/tar.rs b/game/assets-base/src/tar.rs new file mode 100644 index 00000000..b1c549d3 --- /dev/null +++ b/game/assets-base/src/tar.rs @@ -0,0 +1,212 @@ +use std::borrow::Cow; +use std::ops::Range; +use std::path::Path; + +use std::sync::atomic::AtomicUsize; +use std::sync::Arc; +use std::{io::Read, path::PathBuf}; +use tar::Header; + +use anyhow::anyhow; +use rustc_hash::FxHashMap; +use tar::EntryType; + +pub struct TarReaderWrapper { + cur_pos: Arc, + file: Arc>, +} + +impl std::io::Read for TarReaderWrapper { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let pos = self.cur_pos.load(std::sync::atomic::Ordering::Relaxed); + if pos >= self.file.len() { + return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "")); + } + let file = &self.file[pos..]; + let read_len = file.len().min(buf.len()); + if read_len == 0 { + return Err(std::io::Error::new(std::io::ErrorKind::UnexpectedEof, "")); + } + buf.copy_from_slice(&file[0..read_len]); + self.cur_pos.store( + self.cur_pos.load(std::sync::atomic::Ordering::Relaxed) + read_len, + std::sync::atomic::Ordering::Relaxed, + ); + Ok(read_len) + } +} + +impl std::io::Seek for TarReaderWrapper { + fn seek(&mut self, pos: std::io::SeekFrom) -> std::io::Result { + match pos { + std::io::SeekFrom::Start(off) => { + self.cur_pos + .store(off as usize, std::sync::atomic::Ordering::Relaxed); + Ok(off) + } + std::io::SeekFrom::End(off) => { + let pos = self.file.len() as i64 + off; + if pos < 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "seek pos below 0", + )); + } + let pos = pos as usize; + self.cur_pos + .store(pos, std::sync::atomic::Ordering::Relaxed); + Ok(pos as u64) + } + std::io::SeekFrom::Current(off) => { + let pos = self.cur_pos.load(std::sync::atomic::Ordering::Relaxed) as i64 + off; + if pos < 0 { + return Err(std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "seek pos below 0", + )); + } + let pos = pos as usize; + self.cur_pos + .store(pos, std::sync::atomic::Ordering::Relaxed); + Ok(pos as u64) + } + } + } +} + +pub type TarBuilder = tar::Builder>; +pub struct TarReader { + tar: tar::Archive, + + cur_pos: Arc, + file: Arc>, +} +pub struct TarEntry { + file: Arc>, + range: Range, +} +pub type TarEntries = FxHashMap; + +pub fn new_tar() -> TarBuilder { + let mut builder = tar::Builder::new(Vec::new()); + builder.mode(tar::HeaderMode::Deterministic); + builder +} + +pub fn tar_add_file(builder: &mut TarBuilder, path: impl AsRef, file: &[u8]) { + let mut header = Header::new_gnu(); + header.set_cksum(); + header.set_size(file.len() as u64); + header.set_mode(0o644); + header.set_uid(1000); + header.set_gid(1000); + builder + .append_data(&mut header, path, std::io::Cursor::new(file)) + .unwrap(); +} + +/// Constructs a reader for tar files. +/// +/// ### Performance +/// +/// Causes smaller heap allocations. +pub fn tar_reader(file: Vec) -> TarReader { + let file = Arc::new(file); + let cur_pos: Arc = Default::default(); + TarReader { + tar: tar::Archive::new(TarReaderWrapper { + cur_pos: cur_pos.clone(), + file: file.clone(), + }), + cur_pos, + file, + } +} + +fn entry_to_path( + entry: &mut tar::Entry<'_, T>, +) -> Option> { + let ty = entry.header().entry_type(); + if let EntryType::Regular = ty { + Some( + entry + .path() + .map(|path| path.to_path_buf()) + .map_err(|err| anyhow::anyhow!(err)), + ) + } else if matches!(ty, EntryType::Directory) { + None + } else { + Some(Err(anyhow!( + "The tar reader expects files & dictionaries only" + ))) + } +} + +fn tar_entry_to_vec( + entry: &mut tar::Entry<'_, std::io::Cursor>>, +) -> anyhow::Result> { + let mut file: Vec<_> = Default::default(); + entry.read_to_end(&mut file)?; + Ok(file) +} + +pub fn read_tar_files(file: Cow<[u8]>) -> anyhow::Result>> { + let mut file = tar::Archive::new(std::io::Cursor::new(file)); + match file.entries_with_seek() { + Ok(entries) => entries + .filter_map(|entry| { + entry + .map_err(|err| anyhow::anyhow!(err)) + .map(|mut entry| { + entry_to_path(&mut entry).map(|p| { + p.and_then(|path| { + let file = tar_entry_to_vec(&mut entry)?; + Ok((path, file)) + }) + }) + }) + .transpose() + .map(|r| r.and_then(|r| r)) + }) + .collect::>>(), + Err(err) => Err(anyhow::anyhow!(err)), + } +} + +pub fn tar_file_entries(reader: &mut TarReader) -> anyhow::Result> { + reader + .tar + .entries_with_seek()? + .filter_map(|entry| { + entry + .map_err(|err| anyhow::anyhow!(err)) + .map(|mut entry| { + entry_to_path(&mut entry).map(|p| { + p.map(|p| { + let pos = reader.cur_pos.load(std::sync::atomic::Ordering::Relaxed); + ( + p, + TarEntry { + file: reader.file.clone(), + range: pos..pos + entry.size() as usize, + }, + ) + }) + }) + }) + .transpose() + .map(|r| r.and_then(|r| r)) + }) + .collect::>>() +} + +pub fn tar_entry_to_file(entry: &TarEntry) -> anyhow::Result<&[u8]> { + Ok(entry.file.get(entry.range.clone()).ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::BrokenPipe, + "tar file's size in header was \ + bigger than the actual file", + ) + })?) +} diff --git a/game/assets-base/src/verify.rs b/game/assets-base/src/verify.rs index 3f32f87c..5f123f57 100644 --- a/game/assets-base/src/verify.rs +++ b/game/assets-base/src/verify.rs @@ -1,2 +1,3 @@ +pub mod json; pub mod ogg_vorbis; pub mod txt; diff --git a/game/assets-base/src/verify/json.rs b/game/assets-base/src/verify/json.rs new file mode 100644 index 00000000..feca11b6 --- /dev/null +++ b/game/assets-base/src/verify/json.rs @@ -0,0 +1,4 @@ +pub fn verify_json(file: &[u8]) -> anyhow::Result<()> { + serde_json::from_slice::(file)?; + Ok(()) +} diff --git a/game/client-extra/Cargo.toml b/game/assets-splitting/Cargo.toml similarity index 89% rename from game/client-extra/Cargo.toml rename to game/assets-splitting/Cargo.toml index 5db40ad4..1463afba 100644 --- a/game/client-extra/Cargo.toml +++ b/game/assets-splitting/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "client-extra" +name = "assets-splitting" version = "0.1.0" edition = "2021" diff --git a/game/client-extra/src/ddrace_hud_split.rs b/game/assets-splitting/src/ddrace_hud_split.rs similarity index 100% rename from game/client-extra/src/ddrace_hud_split.rs rename to game/assets-splitting/src/ddrace_hud_split.rs diff --git a/game/client-extra/src/emoticon_split.rs b/game/assets-splitting/src/emoticon_split.rs similarity index 100% rename from game/client-extra/src/emoticon_split.rs rename to game/assets-splitting/src/emoticon_split.rs diff --git a/game/client-extra/src/extra_split.rs b/game/assets-splitting/src/extra_split.rs similarity index 100% rename from game/client-extra/src/extra_split.rs rename to game/assets-splitting/src/extra_split.rs diff --git a/game/client-extra/src/game_split.rs b/game/assets-splitting/src/game_split.rs similarity index 100% rename from game/client-extra/src/game_split.rs rename to game/assets-splitting/src/game_split.rs diff --git a/game/client-extra/src/lib.rs b/game/assets-splitting/src/lib.rs similarity index 100% rename from game/client-extra/src/lib.rs rename to game/assets-splitting/src/lib.rs diff --git a/game/client-extra/src/particles_split.rs b/game/assets-splitting/src/particles_split.rs similarity index 100% rename from game/client-extra/src/particles_split.rs rename to game/assets-splitting/src/particles_split.rs diff --git a/game/client-extra/src/skin_split.rs b/game/assets-splitting/src/skin_split.rs similarity index 100% rename from game/client-extra/src/skin_split.rs rename to game/assets-splitting/src/skin_split.rs diff --git a/game/camera/Cargo.toml b/game/camera/Cargo.toml new file mode 100644 index 00000000..8a70b515 --- /dev/null +++ b/game/camera/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "camera" +version = "0.1.0" +edition = "2021" + +[dependencies] +math = { path = "../../lib/math" } +hiarc = { path = "../../lib/hiarc", features = ["derive"] } +graphics = { path = "../../lib/graphics" } +graphics-types = { path = "../../lib/graphics-types" } + +map = { path = "../map" } + diff --git a/game/camera/src/lib.rs b/game/camera/src/lib.rs new file mode 100644 index 00000000..d40c25d3 --- /dev/null +++ b/game/camera/src/lib.rs @@ -0,0 +1,225 @@ +use std::fmt::Debug; + +use graphics::handles::canvas::canvas::GraphicsCanvasHandle; +use graphics_types::rendering::State; +use hiarc::Hiarc; +use map::map::groups::MapGroupAttr; +use math::math::vector::vec2; + +pub enum CanvasType<'a> { + Handle(&'a GraphicsCanvasHandle), + Custom { aspect_ratio: f32 }, +} + +pub trait CameraInterface: Debug { + fn pos(&self) -> vec2; + fn zoom(&self) -> f32; + fn parallax_aware_zoom(&self) -> bool; + + fn project( + &self, + canvas_handle: &GraphicsCanvasHandle, + state: &mut State, + design_group: Option<&MapGroupAttr>, + ); +} + +#[derive(Hiarc)] +pub struct Camera { + pub pos: vec2, + pub zoom: f32, + pub forced_aspect_ratio: Option, + pub parallax_aware_zoom: bool, +} + +impl Debug for Camera { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Camera") + .field("pos", &self.pos) + .field("zoom", &self.zoom) + .field("forced_aspect_ratio", &self.forced_aspect_ratio) + .field("parallax_aware_zoom", &self.parallax_aware_zoom) + .finish() + } +} + +impl Camera { + pub fn new( + pos: vec2, + zoom: f32, + forced_aspect_ratio: Option, + parallax_aware_zoom: bool, + ) -> Self { + Self { + pos, + zoom, + forced_aspect_ratio, + parallax_aware_zoom, + } + } + pub fn calc_canvas_params(aspect: f32, zoom: f32, width: &mut f32, height: &mut f32) { + const AMOUNT: f32 = 1150.0 / 32.0 * 1000.0 / 32.0; + const WIDTH_MAX: f32 = 1500.0 / 32.0; + const HEIGHT_MAX: f32 = 1050.0 / 32.0; + + let f = AMOUNT.sqrt() / aspect.sqrt(); + *width = f * aspect; + *height = f; + + // limit the view + if *width > WIDTH_MAX { + *width = WIDTH_MAX; + *height = *width / aspect; + } + + if *height > HEIGHT_MAX { + *height = HEIGHT_MAX; + *width = *height * aspect; + } + + *width *= zoom; + *height *= zoom; + } + + pub fn map_pos_to_group_attr(center: vec2, parallax: vec2, offset: vec2) -> vec2 { + let center = vec2::new(center.x * parallax.x / 100.0, center.y * parallax.y / 100.0); + vec2::new(offset.x + center.x, offset.y + center.y) + } + + pub fn map_canvas_to_world( + center: vec2, + parallax: vec2, + offset: vec2, + aspect: f32, + zoom: f32, + parallax_aware_zoom: bool, + ) -> [f32; 4] { + let mut width = 0.0; + let mut height = 0.0; + Self::calc_canvas_params(aspect, zoom, &mut width, &mut height); + + let parallax_zoom = if parallax_aware_zoom { + parallax.x.max(parallax.y).clamp(0.0, 100.0) + } else { + 100.0 + }; + let scale = (parallax_zoom * (zoom - 1.0) + 100.0) / 100.0 / zoom; + width *= scale; + height *= scale; + + let center = Self::map_pos_to_group_attr(center, parallax, offset); + let mut points: [f32; 4] = [0.0; 4]; + points[0] = center.x - width / 2.0; + points[1] = center.y - height / 2.0; + points[2] = points[0] + width; + points[3] = points[1] + height; + points + } + + pub fn canvas_points_of_group_attr( + canvas: CanvasType<'_>, + center: vec2, + parallax: vec2, + offset: vec2, + zoom: f32, + parallax_aware_zoom: bool, + ) -> [f32; 4] { + Self::map_canvas_to_world( + center, + parallax, + offset, + match canvas { + CanvasType::Handle(canvas_handle) => canvas_handle.canvas_aspect(), + CanvasType::Custom { aspect_ratio } => aspect_ratio, + }, + zoom, + parallax_aware_zoom, + ) + } + + pub fn para_and_offset_of_group(design_group: Option<&MapGroupAttr>) -> (vec2, vec2) { + if let Some(design_group) = design_group { + ( + vec2::new( + design_group.parallax.x.to_num::(), + design_group.parallax.y.to_num::(), + ), + vec2::new( + design_group.offset.x.to_num::(), + design_group.offset.y.to_num::(), + ), + ) + } else { + (vec2::new(100.0, 100.0), vec2::default()) + } + } + + pub fn canvas_points_of_group( + canvas: CanvasType<'_>, + center: vec2, + design_group: Option<&MapGroupAttr>, + zoom: f32, + parallax_aware_zoom: bool, + ) -> [f32; 4] { + let (parallax, offset) = Self::para_and_offset_of_group(design_group); + Self::canvas_points_of_group_attr( + canvas, + center, + parallax, + offset, + zoom, + parallax_aware_zoom, + ) + } + + pub fn pos_to_group(inp: vec2, design_group: Option<&MapGroupAttr>) -> vec2 { + let (parallax, offset) = Self::para_and_offset_of_group(design_group); + + Self::map_pos_to_group_attr(inp, parallax, offset) + } + + pub fn map_canvas_of_group( + canvas: CanvasType<'_>, + state: &mut State, + center: vec2, + design_group: Option<&MapGroupAttr>, + zoom: f32, + parallax_aware_zoom: bool, + ) { + let points = + Self::canvas_points_of_group(canvas, center, design_group, zoom, parallax_aware_zoom); + state.map_canvas(points[0], points[1], points[2], points[3]); + } +} + +impl CameraInterface for Camera { + fn pos(&self) -> vec2 { + self.pos + } + + fn zoom(&self) -> f32 { + self.zoom + } + + fn parallax_aware_zoom(&self) -> bool { + self.parallax_aware_zoom + } + + fn project( + &self, + canvas_handle: &GraphicsCanvasHandle, + state: &mut State, + design_group: Option<&MapGroupAttr>, + ) { + Self::map_canvas_of_group( + self.forced_aspect_ratio + .map(|aspect_ratio| CanvasType::Custom { aspect_ratio }) + .unwrap_or(CanvasType::Handle(canvas_handle)), + state, + self.pos, + design_group, + self.zoom, + self.parallax_aware_zoom, + ) + } +} diff --git a/game/client-console/src/console/local_console.rs b/game/client-console/src/console/local_console.rs index 73ad82f2..48aa5b7e 100644 --- a/game/client-console/src/console/local_console.rs +++ b/game/client-console/src/console/local_console.rs @@ -392,8 +392,8 @@ impl LocalConsoleBuilder { let events = console_events.clone(); list.push(ConsoleEntry::Cmd(ConsoleEntryCmd { name: name.to_string(), - usage: format!("triggers a player action: {}", name), - description: format!("Triggers the player action: {}", name), + usage: format!("triggers a player action: {name}"), + description: format!("Triggers the player action: {name}"), cmd: Rc::new(move |_config_engine, _config_game, _, _path| { events.push(LocalConsoleEvent::LocalPlayerAction(action)); Ok(String::default()) @@ -420,7 +420,7 @@ impl LocalConsoleBuilder { } for i in 0..=9 { - res.push(format!("numpad{}", i)); + res.push(format!("numpad{i}")); } res.push("numpad_subtract".to_string()); res.push("numpad_add".to_string()); @@ -430,7 +430,7 @@ impl LocalConsoleBuilder { res.push("numpad_enter".to_string()); for i in 0..=9 { - res.push(format!("digit{}", i)); + res.push(format!("digit{i}")); } res.push("page_up".to_string()); diff --git a/game/client-console/src/console/remote_console.rs b/game/client-console/src/console/remote_console.rs index 94be4f9c..b6b23b34 100644 --- a/game/client-console/src/console/remote_console.rs +++ b/game/client-console/src/console/remote_console.rs @@ -53,7 +53,7 @@ impl RemoteConsole { for arg in args { if let Some(user_ty) = &arg.user_ty { - usage += &format!("<{}>", user_ty); + usage += &format!("<{user_ty}>"); } else { match &arg.ty { CommandArgType::Command => usage += " ", diff --git a/game/client-containers/Cargo.toml b/game/client-containers/Cargo.toml index 88eaa9d4..1f23c641 100644 --- a/game/client-containers/Cargo.toml +++ b/game/client-containers/Cargo.toml @@ -14,7 +14,7 @@ image-utils = { path = "../../lib/image-utils" } hiarc = { path = "../../lib/hiarc", features = ["derive", "enable_rayon", "enable_rustc_hash", "enable_url", "enable_fixed", "enable_either"] } math = { path = "../../lib/math" } -client-extra = { path = "../client-extra" } +assets-splitting = { path = "../assets-splitting" } game-interface = { path = "../game-interface" } assets-base = { path = "../assets-base" } diff --git a/game/client-containers/src/container.rs b/game/client-containers/src/container.rs index 1b89e8ea..20eddd12 100644 --- a/game/client-containers/src/container.rs +++ b/game/client-containers/src/container.rs @@ -1,5 +1,5 @@ use assets_base::{ - loader::read_tar, + tar::read_tar_files, verify::{ogg_vorbis::verify_ogg_vorbis, txt::verify_txt}, AssetsIndex, AssetsMeta, }; @@ -461,10 +461,8 @@ where }, ) { log::warn!( - "downloaded image resource (png) {}\ - is not a valid png file: {}", - file_name, - err + "downloaded image resource (png) {file_name}\ + is not a valid png file: {err}" ); return false; } @@ -473,9 +471,7 @@ where if let Err(err) = verify_ogg_vorbis(file) { log::warn!( "downloaded sound resource (ogg vorbis) \ - ({}) is not a valid ogg vorbis file: {}", - file_name, - err + ({file_name}) is not a valid ogg vorbis file: {err}" ); return false; } @@ -489,10 +485,8 @@ where }, _ => { log::warn!( - "Unsupported resource type {} for {} \ - could not be validated", - file_ty, - file_name, + "Unsupported resource type {file_ty} \ + for {file_name} could not be validated" ); return false; } @@ -586,15 +580,12 @@ where { if let Err(err) = fs.create_dir(&dir_path).await { log::warn!( - "Failed to create directory for downloaded file {} to disk: {err}", - key_name + "Failed to create directory for downloaded file {key_name} \ + to disk: {err}" ); } else if let Err(err) = fs.write_file(dir_path.join(name).as_ref(), file).await { - log::warn!( - "Failed to write downloaded file {} to disk: {err}", - key_name - ); + log::warn!("Failed to write downloaded file {key_name} to disk: {err}"); } } }) @@ -632,7 +623,7 @@ where ))) .await { - if let Ok(tar_files) = read_tar(&file) { + if let Ok(tar_files) = read_tar_files(file.into()) { files = Some(ContainerLoadedItem::Directory(ContainerLoadedItemDir::new( tar_files, ))); @@ -664,7 +655,7 @@ where }) { let _g = http_download_tasks.acquire().await?; if let Ok(file) = http.download_binary(game_server_http, &hash).await { - if let Ok(tar_files) = read_tar(&file) { + if let Ok(tar_files) = read_tar_files(file.as_ref().into()) { let mut verified = true; for (name, file) in &tar_files { if !Self::verify_resource( @@ -760,7 +751,7 @@ where .read_file(&base_path.join(format!("{}.tar", key.name.as_str()))) .await { - if let Ok(tar_files) = read_tar(&file) { + if let Ok(tar_files) = read_tar_files(file.into()) { files = Some(ContainerLoadedItem::Directory(ContainerLoadedItemDir::new( tar_files, ))); @@ -795,7 +786,7 @@ where .await { if entry.ty == "tar" { - if let Ok(tar_files) = read_tar(&file) { + if let Ok(tar_files) = read_tar_files(file.into()) { files = Some(ContainerLoadedItem::Directory( ContainerLoadedItemDir::new(tar_files), )); @@ -830,7 +821,7 @@ where match res { Ok(file) => { let write_to_disk = if ty == "tar" { - if let Ok(tar_files) = read_tar(&file) { + if let Ok(tar_files) = read_tar_files(file.as_ref().into()) { let mut verified = true; for (name, file) in &tar_files { if !Self::verify_resource( @@ -1182,8 +1173,8 @@ where if let Ok(dir_index) = dir_index.as_ref() { entries.extend( dir_index - .iter() - .map(|(name, _)| (name.clone(), ContainerItemIndexType::Http)), + .keys() + .map(|name| (name.clone(), ContainerItemIndexType::Http)), ); } } else { @@ -1572,7 +1563,7 @@ pub fn load_file_part_and_upload_ex( img.as_mut_slice().copy_from_slice(&part_img.png.data); if let Err(err) = graphics_mt.try_flush_mem(&mut img, true) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {} for {part_name}", err); + log::debug!("err while flushing memory: {err} for {part_name}"); } Ok(ImgFilePartResult { img: ContainerItemLoadData { @@ -1665,7 +1656,7 @@ pub fn load_sound_file_part_and_upload_ex( sound_path = sound_path.join(Path::new(extra_path)); } - sound_path = sound_path.join(Path::new(&format!("{}.ogg", part_name))); + sound_path = sound_path.join(Path::new(&format!("{part_name}.ogg"))); let is_default = item_name == "default"; @@ -1684,17 +1675,12 @@ pub fn load_sound_file_part_and_upload_ex( .files .get(&path_def) .ok_or_else(|| { - anyhow!( - "requested sound file {} didn't exist in default items", - item_name - ) + anyhow!("requested sound file {item_name} didn't exist in default items") }) .map(|s| (s, true)) } else { Err(anyhow!( - "requested sound file for {} not found: {}", - item_name, - part_name + "requested sound file for {item_name} not found: {part_name}" )) } } @@ -1704,7 +1690,7 @@ pub fn load_sound_file_part_and_upload_ex( mem.as_mut_slice().copy_from_slice(file); if let Err(err) = sound_mt.try_flush_mem(&mut mem) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {} for {part_name}", err); + log::debug!("err while flushing memory: {err} for {part_name}"); } Ok(SoundFilePartResult { mem, from_default }) @@ -1834,7 +1820,7 @@ pub fn load_file_part_and_convert_3d_and_upload( img.as_mut_slice().copy_from_slice(&part_img.png.data); if let Err(err) = graphics_mt.try_flush_mem(&mut img, true) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {} for {part_name}", err); + log::debug!("err while flushing memory: {err} for {part_name}"); } Ok(ImgFilePartResult { img: ContainerItemLoadData { diff --git a/game/client-containers/src/emoticons.rs b/game/client-containers/src/emoticons.rs index 8feb3e06..30ea529c 100644 --- a/game/client-containers/src/emoticons.rs +++ b/game/client-containers/src/emoticons.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, sync::Arc}; use arrayvec::ArrayVec; -use client_extra::emoticon_split::Emoticon06Part; +use assets_splitting::emoticon_split::Emoticon06Part; use game_interface::types::emoticons::{EmoticonType, EnumCount}; use graphics::{ graphics_mt::GraphicsMultiThreaded, @@ -39,12 +39,12 @@ impl LoadEmoticons { &mut mem })?; let converted = - client_extra::emoticon_split::split_06_emoticon(img.data, img.width, img.height)?; + assets_splitting::emoticon_split::split_06_emoticon(img.data, img.width, img.height)?; let mut insert_part = |name: &str, part: Emoticon06Part| -> anyhow::Result<()> { let file = image_utils::png::save_png_image(&part.data, part.width, part.height)?; - files.insert(format!("{}.png", name).into(), file); + files.insert(format!("{name}.png").into(), file); Ok(()) }; insert_part("oop", converted.oop)?; diff --git a/game/client-containers/src/hud.rs b/game/client-containers/src/hud.rs index f052fbd3..c04f1f0a 100644 --- a/game/client-containers/src/hud.rs +++ b/game/client-containers/src/hud.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use client_extra::ddrace_hud_split::DdraceHudPart; +use assets_splitting::ddrace_hud_split::DdraceHudPart; use game_interface::types::weapons::{EnumCount, WeaponType}; use graphics::{ graphics_mt::GraphicsMultiThreaded, @@ -100,12 +100,12 @@ impl LoadHud { &mut mem })?; let converted = - client_extra::ddrace_hud_split::split_ddrace_hud(img.data, img.width, img.height)?; + assets_splitting::ddrace_hud_split::split_ddrace_hud(img.data, img.width, img.height)?; let mut insert_part = |name: &str, part: DdraceHudPart| -> anyhow::Result<()> { let file = image_utils::png::save_png_image(&part.data, part.width, part.height)?; - files.insert(format!("{}.png", name).into(), file); + files.insert(format!("{name}.png").into(), file); Ok(()) }; insert_part("jump", converted.jump)?; diff --git a/game/client-containers/src/particles.rs b/game/client-containers/src/particles.rs index ddc739c8..c75ce13d 100644 --- a/game/client-containers/src/particles.rs +++ b/game/client-containers/src/particles.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, sync::Arc}; -use client_extra::particles_split::Particles06Part; +use assets_splitting::particles_split::Particles06Part; use graphics::{ graphics_mt::GraphicsMultiThreaded, handles::texture::texture::{GraphicsTextureHandle, TextureContainer}, @@ -109,12 +109,12 @@ impl LoadParticle { &mut mem })?; let converted = - client_extra::particles_split::split_06_particles(img.data, img.width, img.height)?; + assets_splitting::particles_split::split_06_particles(img.data, img.width, img.height)?; let mut insert_part = |name: &str, part: Particles06Part| -> anyhow::Result<()> { let file = image_utils::png::save_png_image(&part.data, part.width, part.height)?; - files.insert(format!("{}.png", name).into(), file); + files.insert(format!("{name}.png").into(), file); Ok(()) }; insert_part("slice_001", converted.slice)?; diff --git a/game/client-containers/src/skins.rs b/game/client-containers/src/skins.rs index 85fb7cd7..113243ad 100644 --- a/game/client-containers/src/skins.rs +++ b/game/client-containers/src/skins.rs @@ -2,7 +2,7 @@ use std::{path::PathBuf, rc::Rc, sync::Arc}; use arrayvec::ArrayVec; -use client_extra::skin_split::Skin06Part; +use assets_splitting::skin_split::Skin06Part; use fixed::{types::extra::U32, FixedI64}; use game_interface::types::{emoticons::EnumCount, render::character::TeeEye}; use graphics::{ @@ -282,7 +282,8 @@ impl LoadSkinTexturesData { mem.resize(width * height * bytes_per_pixel, Default::default()); &mut mem })?; - let converted = client_extra::skin_split::split_06_skin(img.data, img.width, img.height)?; + let converted = + assets_splitting::skin_split::split_06_skin(img.data, img.width, img.height)?; let base: PathBuf = if let Some(skin_extra_path) = skin_extra_path { skin_extra_path.into() } else { @@ -298,7 +299,7 @@ impl LoadSkinTexturesData { file.clone(), ); } - files.insert(base.join(format!("{}.png", name)), file); + files.insert(base.join(format!("{name}.png")), file); Ok(()) }; insert_part("body", converted.body, false)?; @@ -547,7 +548,7 @@ impl LoadSkinTexturesData { img_mem.as_mut_slice().copy_from_slice(&img.data); if let Err(err) = graphics_mt.try_flush_mem(&mut img_mem, true) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {}", err); + log::debug!("err while flushing memory: {err}"); } ContainerItemLoadData { width: img.width, diff --git a/game/client-demo/Cargo.toml b/game/client-demo/Cargo.toml index 78117247..c26923de 100644 --- a/game/client-demo/Cargo.toml +++ b/game/client-demo/Cargo.toml @@ -13,7 +13,6 @@ graphics = { path = "../../lib/graphics" } graphics-types = { path = "../../lib/graphics-types" } graphics-backend = { path = "../../lib/graphics-backend" } pool = { path = "../../lib/pool" } -hiarc = { path = "../../lib/hiarc" } ui-base = { path = "../../lib/ui-base" } ui-generic = { path = "../../lib/ui-generic" } sound = { path = "../../lib/sound" } diff --git a/game/client-demo/src/lib.rs b/game/client-demo/src/lib.rs index 6adeab16..5e626ccb 100644 --- a/game/client-demo/src/lib.rs +++ b/game/client-demo/src/lib.rs @@ -38,13 +38,13 @@ use graphics::{ graphics::graphics::Graphics, handles::{ canvas::canvas::{GraphicsCanvasHandle, GraphicsCanvasMode, OffscreenCanvas}, - stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, + stream::stream::GraphicsStreamHandle, stream_types::StreamedQuad, + texture::texture::TextureType, }, }; use graphics_backend::backend::GraphicsBackend; use graphics_types::rendering::{BlendType, ColorMaskMode, State}; -use hiarc::hi_closure; use math::math::vector::{ffixed, ubvec4, vec2}; use pool::datatypes::{ PoolBTreeMap, PoolFxLinkedHashMap, PoolFxLinkedHashSet, PoolVec, PoolVecDeque, @@ -418,9 +418,10 @@ impl DemoViewerImpl { let map = client_map.try_get_mut().unwrap(); - let ClientMapFile::Game(GameMap { render, .. }) = map else { + let ClientMapFile::Game(map) = map else { panic!("not a game map") }; + let GameMap { render, .. } = map.as_mut(); render.clear_render_state(); } @@ -460,9 +461,10 @@ impl DemoViewerImpl { let map = client_map.try_get_mut().unwrap(); - let ClientMapFile::Game(GameMap { render, game, .. }) = map else { + let ClientMapFile::Game(map) = map else { panic!("not a game map") }; + let GameMap { render, game, .. } = map.as_mut(); let Some((local_players, prev_tick, next_tick)) = (if !viewer.cur_snapshots.is_empty() { let mut it = viewer.cur_snapshots.iter(); @@ -909,32 +911,20 @@ impl DemoViewerImpl { let rect = ▭ let offscreen_canvas = &self.data.offscreen_canvas; self.data.stream_handle.render_quads( - hi_closure!([ - rect: &Rect, - offscreen_canvas: &OffscreenCanvas - ], |mut stream_handle: QuadStreamHandle<'_>| -> () { - stream_handle.set_offscreen_attachment_texture(offscreen_canvas); - stream_handle - .add_vertices( - StreamedQuad::default().from_pos_and_size( - vec2::new( - rect.left_top().x, - rect.left_top().y - ), - vec2::new(rect.width(), rect.height()) - ) - .color( - ubvec4::new(255, 255, 255, 255) - ) - .tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ).into() - ); - }), + &[StreamedQuad::default() + .from_pos_and_size( + vec2::new(rect.left_top().x, rect.left_top().y), + vec2::new(rect.width(), rect.height()), + ) + .color(ubvec4::new(255, 255, 255, 255)) + .tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + )], state, + TextureType::ColorAttachmentOfOffscreen(offscreen_canvas.clone()), ); } diff --git a/game/client-map/src/client_map.rs b/game/client-map/src/client_map.rs index fd4d24f3..4664d533 100644 --- a/game/client-map/src/client_map.rs +++ b/game/client-map/src/client_map.rs @@ -22,7 +22,10 @@ use game_state_wasm::game::state_wasm_manager::{ GameStateMod, GameStateWasmManager, STATE_MODS_PATH, }; use graphics_backend::backend::GraphicsBackend; -use map::map::Map; +use map::{ + file::MapFileReader, + map::{Map, PngValidatorOptions}, +}; use rayon::ThreadPool; pub use render_game_wasm::render::render_wasm_manager::RenderGameWasmManager; use render_game_wasm::render::render_wasm_manager::{RenderGameMod, RENDER_MODS_PATH}; @@ -94,15 +97,16 @@ impl ClientMapLoadingFile { props: RenderGameCreateOptions, log: ConnectingLog, ) -> Self { + let load_hq_assets = false; let downloaded_path: Option<&Path> = (!as_menu_map).then_some("downloaded".as_ref()); let download_map_file_name = if let Some(map_hash) = map_hash { base_path.join(format!( - "{}_{}.twmap", + "{}_{}.twmap.tar", map_name.as_str(), fmt_hash(&map_hash) )) } else { - base_path.join(format!("{}.twmap", map_name.as_str())) + base_path.join(format!("{}.twmap.tar", map_name.as_str())) }; let map_file_name = if let Some(downloaded_path) = downloaded_path { downloaded_path.join(&download_map_file_name) @@ -117,8 +121,7 @@ impl ClientMapLoadingFile { Self { task: io.rt.spawn(async move { log_load.log(format!( - "Ready map file from file system: {:?}", - map_file_name + "Ready map file from file system: {map_file_name:?}" )); let file = file_system.read_file(map_file_name.as_ref()).await; @@ -144,10 +147,18 @@ impl ClientMapLoadingFile { .to_vec(); // maps are allowed to be arbitrary, but all maps should still start // with the twmap header. - anyhow::ensure!( - Map::validate_twmap_header(&file), - "not a twmap file or variant of it." - ); + Map::validate_downloaded_map_file( + &MapFileReader::new(file.clone())?, + if load_hq_assets { + PngValidatorOptions { + max_width: 4096.try_into().unwrap(), + max_height: 4096.try_into().unwrap(), + ..Default::default() + } + } else { + Default::default() + }, + )?; let file_path: &Path = map_file_name.as_ref(); if let Some(dir) = file_path.parent() { file_system.create_dir(dir).await?; @@ -194,8 +205,7 @@ impl ClientMapLoadingFile { io.rt.spawn(async move { log.log(format!( - "Loading physics wasm module: {:?}", - game_mod_file_name + "Loading physics wasm module: {game_mod_file_name:?}" )); let file = fs.read_file(game_mod_file_name.as_ref()).await; @@ -263,6 +273,14 @@ impl ClientMapLoadingFile { } } +pub struct ClientMapPreparing { + render: ClientMapComponentLoading, + map: Vec, + map_name: NetworkReducedAsciiString, + game_mod: GameStateMod, + game_options: GameStateCreateOptions, +} + pub struct GameCreateProps { sound: SoundManager, graphics: Graphics, @@ -351,7 +369,7 @@ impl ClientMapComponentLoading { } else { format!("{}/{}.wasm", RENDER_MODS_PATH, name.as_str()) }; - log.log(format!("Reading rendering module: {}", path_str)); + log.log(format!("Reading rendering module: {path_str}")); let file = fs .read_file(path_str.as_ref()) .await @@ -486,21 +504,15 @@ pub struct GameMap { pub enum ClientMapFile { Menu { render: ClientMapRender }, - Game(GameMap), + Game(Box), } pub enum ClientMapLoading { /// load the "raw" map file - File(ClientMapLoadingFile), + File(Box), /// wait for the individual components to finish parsing the map file /// physics and graphics independently - PrepareComponents { - render: ClientMapComponentLoading, - map: Vec, - map_name: NetworkReducedAsciiString, - game_mod: GameStateMod, - game_options: GameStateCreateOptions, - }, + PrepareComponents(Box), /// finished loading Map(ClientMapFile), /// Map is in an error state @@ -527,7 +539,7 @@ impl ClientMapLoading { props: RenderGameCreateOptions, log: ConnectingLog, ) -> Self { - Self::File(ClientMapLoadingFile::new( + Self::File(Box::new(ClientMapLoadingFile::new( sound, graphics, backend, @@ -543,7 +555,7 @@ impl ClientMapLoading { game_options, props, log, - )) + ))) } pub fn try_get(&self) -> Option<&ClientMapFile> { @@ -601,13 +613,13 @@ impl ClientMapLoading { file.log, ); - *self = Self::PrepareComponents { + *self = Self::PrepareComponents(Box::new(ClientMapPreparing { render: loading, map: map_file, map_name: file.map_name, game_mod, game_options: file.game_options, - } + })) } Err(err) => *self = Self::Err(err), } @@ -615,14 +627,8 @@ impl ClientMapLoading { *self = Self::File(file) } } - Self::PrepareComponents { - render, - map, - map_name, - game_mod, - game_options, - } => { - match render.ty { + Self::PrepareComponents(prepare) => { + match prepare.render.ty { ClientMapComponentLoadingType::Game(mut load_game) => { if let GameLoading::Task { task, props } = load_game { if task.is_finished() { @@ -652,20 +658,20 @@ impl ClientMapLoading { } match load_game { GameLoading::Task { task, props } => { - *self = Self::PrepareComponents { + *self = Self::PrepareComponents(Box::new(ClientMapPreparing { render: ClientMapComponentLoading { ty: ClientMapComponentLoadingType::Game( GameLoading::Task { task, props }, ), - io: render.io, - thread_pool: render.thread_pool, - log: render.log, + io: prepare.render.io, + thread_pool: prepare.render.thread_pool, + log: prepare.render.log, }, - map, - map_name, - game_mod, - game_options, - } + map: prepare.map, + map_name: prepare.map_name, + game_mod: prepare.game_mod, + game_options: prepare.game_options, + })) } GameLoading::Game(mut load_game) => { match load_game.continue_loading() { @@ -673,19 +679,19 @@ impl ClientMapLoading { if loaded { match ( GameStateWasmManager::new( - game_mod.clone(), - map.clone(), - map_name.clone(), - game_options.clone(), - &render.io, + prepare.game_mod.clone(), + prepare.map.clone(), + prepare.map_name.clone(), + prepare.game_options.clone(), + &prepare.render.io, Arc::new(DummyDb), ), GameStateWasmManager::new( - game_mod, - map, - map_name, - game_options, - &render.io, + prepare.game_mod, + prepare.map, + prepare.map_name, + prepare.game_options, + &prepare.render.io, Arc::new(DummyDb), ), ) { @@ -694,10 +700,10 @@ impl ClientMapLoading { game.info.chat_commands.clone(), ); - render.log.log("Loaded map & modules."); + prepare.render.log.log("Loaded map & modules."); // finished loading - *self = - Self::Map(ClientMapFile::Game(GameMap { + *self = Self::Map(ClientMapFile::Game( + Box::new(GameMap { render: load_game, game, unpredicted_game: GameUnpredicted { @@ -705,7 +711,8 @@ impl ClientMapLoading { cur: None, state: unpredicted_game, }, - })); + }), + )); } (Err(err), Ok(_)) | (Ok(_), Err(err)) => { *self = Self::Err(err); @@ -715,20 +722,22 @@ impl ClientMapLoading { } } } else { - *self = Self::PrepareComponents { - render: ClientMapComponentLoading { - ty: ClientMapComponentLoadingType::Game( - GameLoading::Game(load_game), - ), - io: render.io, - thread_pool: render.thread_pool, - log: render.log, + *self = Self::PrepareComponents(Box::new( + ClientMapPreparing { + render: ClientMapComponentLoading { + ty: ClientMapComponentLoadingType::Game( + GameLoading::Game(load_game), + ), + io: prepare.render.io, + thread_pool: prepare.render.thread_pool, + log: prepare.render.log, + }, + map: prepare.map, + map_name: prepare.map_name, + game_mod: prepare.game_mod, + game_options: prepare.game_options, }, - map, - map_name, - game_mod, - game_options, - } + )) } } Err(err) => *self = Self::Err(anyhow!("{}", err)), @@ -745,18 +754,18 @@ impl ClientMapLoading { render: map_prepare, }) } else { - *self = Self::PrepareComponents { + *self = Self::PrepareComponents(Box::new(ClientMapPreparing { render: ClientMapComponentLoading { ty: ClientMapComponentLoadingType::Menu(map_prepare), - io: render.io, - thread_pool: render.thread_pool, - log: render.log, + io: prepare.render.io, + thread_pool: prepare.render.thread_pool, + log: prepare.render.log, }, - map, - map_name, - game_mod, - game_options, - } + map: prepare.map, + map_name: prepare.map_name, + game_mod: prepare.game_mod, + game_options: prepare.game_options, + })) } } Err(err) => { diff --git a/game/client-render-base/Cargo.toml b/game/client-render-base/Cargo.toml index 33dc2a75..53851ea9 100644 --- a/game/client-render-base/Cargo.toml +++ b/game/client-render-base/Cargo.toml @@ -18,11 +18,13 @@ image-utils = { path = "../../lib/image-utils" } client-containers = { path = "../client-containers" } assets-base = { path = "../assets-base" } +legacy-map = { path = "../legacy-map" } game-base = { path = "../game-base" } vanilla = { path = "../vanilla" } map = { path = "../map" } game-config = { path = "../game-config" } game-interface = { path = "../game-interface" } +camera = { path = "../camera" } hashlink = { git = "https://github.com/Jupeyy/hashlink/", branch = "ddnet", features = ["serde", "serde_impl"] } @@ -38,3 +40,4 @@ log = "0.4.27" url = { version = "2.5.4", features = ["serde"] } futures = "0.3.31" strum = { version = "0.27.1", features = ["derive"] } +rustc-hash = "2.1.1" diff --git a/game/client-render-base/src/map/map.rs b/game/client-render-base/src/map/map.rs index 5284f0ea..41defff6 100644 --- a/game/client-render-base/src/map/map.rs +++ b/game/client-render-base/src/map/map.rs @@ -1,6 +1,15 @@ -use std::{borrow::Borrow, cell::Cell, fmt::Debug, ops::IndexMut, time::Duration}; +use std::{ + borrow::Borrow, + cell::Cell, + fmt::Debug, + ops::{IndexMut, Range}, + time::Duration, +}; -use crate::map::map_buffered::{MapRenderLayer, MapRenderTextOverlayType}; +use crate::map::{ + map_buffered::{MapRenderLayer, MapRenderTextOverlayType, QuadVisualRangeAnim}, + render_pipe::RenderPipelinePhysics, +}; use super::{ map_buffered::{ @@ -10,9 +19,10 @@ use super::{ map_pipeline::{EditorTileLayerRenderProps, MapGraphics, QuadRenderInfo, TileLayerDrawInfo}, map_sound::MapSoundProcess, map_with_visual::{MapVisual, MapVisualLayerBase}, - render_pipe::{Camera, RenderPipeline, RenderPipelineBase}, - render_tools::{CanvasType, RenderTools}, + render_pipe::{RenderPipeline, RenderPipelineBase}, + render_tools::RenderTools, }; +use camera::CameraInterface; use client_containers::{ container::ContainerKey, entities::{Entities, EntitiesContainer}, @@ -46,12 +56,13 @@ use map::{ resources::MapResourcesSkeleton, }, }; -use pool::mixed_pool::Pool; +use pool::{datatypes::PoolFxHashMap, mixed_pool::Pool as MixedPool, pool::Pool}; +use rustc_hash::FxHashMap; use serde::de::DeserializeOwned; use math::math::{ mix, - vector::{nffixed, nfvec4, ubvec4, uffixed, vec2}, + vector::{fvec3, nffixed, nfvec4, ubvec4, uffixed, vec2}, PI, }; @@ -71,6 +82,16 @@ pub enum ForcedTexture<'a> { QuadLayer(&'a TextureContainer), } +enum QuadFlushOrAdd { + Flush { fully_transparent_color: bool }, + Add { info: QuadRenderInfo }, +} + +pub struct QuadAnimEvalResult { + pub pos_anims_values: PoolFxHashMap<(usize, time::Duration), fvec3>, + pub color_anims_values: PoolFxHashMap<(usize, time::Duration), nfvec4>, +} + #[derive(Debug, Hiarc)] pub struct RenderMap { map_graphics: MapGraphics, @@ -78,7 +99,9 @@ pub struct RenderMap { canvas_handle: GraphicsCanvasHandle, stream_handle: GraphicsStreamHandle, - tile_layer_render_info_pool: Pool>, + tile_layer_render_info_pool: MixedPool>, + pos_anims: Pool>, + color_anims: Pool>, // sound, handled here because it's such an integral part of the map pub sound: MapSoundProcess, @@ -91,7 +114,7 @@ impl RenderMap { stream_handle: &GraphicsStreamHandle, ) -> RenderMap { let (tile_layer_render_info_pool, tile_layer_render_info_sync_point) = - Pool::with_capacity(64); + MixedPool::with_capacity(64); backend_handle.add_sync_point(tile_layer_render_info_sync_point); RenderMap { map_graphics: MapGraphics::new(backend_handle), @@ -101,6 +124,9 @@ impl RenderMap { tile_layer_render_info_pool, + pos_anims: Pool::with_capacity(8), + color_anims: Pool::with_capacity(8), + sound: MapSoundProcess::new(), } } @@ -680,77 +706,60 @@ impl RenderMap { } } - pub fn prepare_quad_rendering( - mut stream_handle: StreamedUniforms<'_, QuadRenderInfo>, - cur_time: &Duration, - cur_anim_time: &Duration, - include_last_anim_point: bool, - cur_quad_offset: &Cell, - animations: &AnimationsSkeleton, - quads: &[Quad], + fn prepare_quad_rendering_grouped( + color_anims: &FxHashMap<(usize, time::Duration), nfvec4>, + pos_anims: &FxHashMap<(usize, time::Duration), fvec3>, + color_anim: Option, + color_anim_offset: &time::Duration, + pos_anim: Option, + pos_anim_offset: &time::Duration, + mut flush_or_add: impl FnMut(QuadFlushOrAdd), ) { - for (i, quad) in quads.iter().enumerate() { - let color = if let Some(anim) = { - if let Some(color_anim) = quad.color_anim { - animations.color.get(color_anim) - } else { - None - } - } { - RenderMap::animation_eval( - &anim.def, - cur_time, - cur_anim_time, - &quad.color_anim_offset, - include_last_anim_point, - ) + let color = if let Some(anim) = { + if let Some(color_anim) = color_anim { + color_anims.get(&(color_anim, *color_anim_offset)).copied() } else { - nfvec4::new( - nffixed::from_num(1), - nffixed::from_num(1), - nffixed::from_num(1), - nffixed::from_num(1), - ) - }; + None + } + } { + anim + } else { + nfvec4::new( + nffixed::from_num(1), + nffixed::from_num(1), + nffixed::from_num(1), + nffixed::from_num(1), + ) + }; - let mut offset_x = 0.0; - let mut offset_y = 0.0; - let mut rot = 0.0; + let mut offset_x = 0.0; + let mut offset_y = 0.0; + let mut rot = 0.0; - if let Some(anim) = { - if let Some(pos_anim) = quad.pos_anim { - animations.pos.get(pos_anim) - } else { - None - } - } { - let pos_channels = RenderMap::animation_eval( - &anim.def, - cur_time, - cur_anim_time, - &quad.pos_anim_offset, - include_last_anim_point, - ); - offset_x = pos_channels.x.to_num(); - offset_y = pos_channels.y.to_num(); - rot = pos_channels.z.to_num::() / 180.0 * PI; + if let Some(pos_channels) = { + if let Some(pos_anim) = pos_anim { + pos_anims.get(&(pos_anim, *pos_anim_offset)) + } else { + None } + } { + offset_x = pos_channels.x.to_num(); + offset_y = pos_channels.y.to_num(); + rot = pos_channels.z.to_num::() / 180.0 * PI; + } - let is_fully_transparent = color.a() <= 0; - let needs_flush = is_fully_transparent; - - if needs_flush { - stream_handle.flush(); + let is_fully_transparent = color.a() <= 0; + let needs_flush = is_fully_transparent; - cur_quad_offset.set(i); - if is_fully_transparent { - // since this quad is ignored, the offset is the next quad - cur_quad_offset.set(cur_quad_offset.get() + 1); - } - } + if needs_flush { + flush_or_add(QuadFlushOrAdd::Flush { + fully_transparent_color: is_fully_transparent, + }); + } - if !is_fully_transparent { - stream_handle.add(QuadRenderInfo::new( + if !is_fully_transparent { + flush_or_add(QuadFlushOrAdd::Add { + info: QuadRenderInfo::new( ColorRgba { r: color.r().to_num(), g: color.g().to_num(), @@ -759,9 +768,182 @@ impl RenderMap { }, vec2::new(offset_x, offset_y), rot, - )); + ), + }); + } + } + + pub fn prepare_quad_rendering( + mut stream_handle: StreamedUniforms<'_, QuadRenderInfo>, + color_anims: &FxHashMap<(usize, time::Duration), nfvec4>, + pos_anims: &FxHashMap<(usize, time::Duration), fvec3>, + cur_quad_offset: &Cell, + quads: &[Quad], + first_index: usize, + ) { + for (i, quad) in quads.iter().enumerate() { + Self::prepare_quad_rendering_grouped( + color_anims, + pos_anims, + quad.color_anim, + &quad.color_anim_offset, + quad.pos_anim, + &quad.pos_anim_offset, + |reason| { + match reason { + QuadFlushOrAdd::Flush { + fully_transparent_color, + } => { + stream_handle.flush(); + cur_quad_offset.set(i + first_index); + if fully_transparent_color { + // since this quad is ignored, the offset is the next quad + cur_quad_offset.set(cur_quad_offset.get() + 1); + } + } + QuadFlushOrAdd::Add { info } => { + stream_handle.add(info); + } + } + }, + ); + } + } + + fn prepare_group_rendering( + &self, + color_anims: &FxHashMap<(usize, time::Duration), nfvec4>, + pos_anims: &FxHashMap<(usize, time::Duration), fvec3>, + color_anim: Option, + color_anim_offset: &time::Duration, + pos_anim: Option, + pos_anim_offset: &time::Duration, + range: Range, + state: &State, + texture: &TextureType, + buffer_object: &BufferObject, + ) { + Self::prepare_quad_rendering_grouped( + color_anims, + pos_anims, + color_anim, + color_anim_offset, + pos_anim, + pos_anim_offset, + |reason| { + if let QuadFlushOrAdd::Add { info } = reason { + self.map_graphics.render_quad_layer_grouped( + state, + texture.clone(), + buffer_object, + range.end - range.start, + range.start, + info, + ); + } + }, + ); + } + + fn render_quads_with_anim( + &self, + state: &State, + texture: &TextureType, + color_anims: &FxHashMap<(usize, time::Duration), nfvec4>, + pos_anims: &FxHashMap<(usize, time::Duration), fvec3>, + quads: &[Quad], + buffer_container: &BufferObject, + first_index: usize, + ) { + let map_graphics = &self.map_graphics; + let cur_quad_offset_cell = Cell::new(first_index); + let cur_quad_offset = &cur_quad_offset_cell; + self.stream_handle.fill_uniform_instance( + hi_closure!( + [ + color_anims: &FxHashMap<(usize, time::Duration), nfvec4>, + pos_anims: &FxHashMap<(usize, time::Duration), fvec3>, + cur_quad_offset: &Cell, + quads: &[Quad], + first_index: usize, + ], + |stream_handle: StreamedUniforms< + '_, + QuadRenderInfo, + >| + -> () { + RenderMap::prepare_quad_rendering( + stream_handle, + color_anims, + pos_anims, + cur_quad_offset, + quads, + first_index + ); + } + ), + hi_closure!([ + map_graphics: &MapGraphics, + state: &State, + texture: &TextureType, + buffer_container: &BufferObject, + cur_quad_offset: &Cell + ], + |instance: usize, count: usize| -> () { + map_graphics.render_quad_layer( + state, + texture.clone(), + buffer_container, + instance, + count, + cur_quad_offset.get(), + ); + cur_quad_offset.set(cur_quad_offset.get() + count); + }), + ); + } + + pub fn prepare_quad_anims( + pos_anims: &Pool>, + color_anims: &Pool>, + cur_time: &Duration, + cur_anim_time: &Duration, + include_last_anim_point: bool, + visuals: &QuadLayerVisuals, + animations: &AnimationsSkeleton, + ) -> QuadAnimEvalResult { + let mut pos_anims_values = pos_anims.new(); + let mut color_anims_values = color_anims.new(); + + for &(pos_anim, pos_anim_offset) in &visuals.pos_anims { + if let Some(anim) = animations.pos.get(pos_anim) { + let pos_channels = RenderMap::animation_eval( + &anim.def, + cur_time, + cur_anim_time, + &pos_anim_offset, + include_last_anim_point, + ); + pos_anims_values.insert((pos_anim, pos_anim_offset), pos_channels); + } + } + for &(color_anim, color_anim_offset) in &visuals.color_anims { + if let Some(anim) = animations.color.get(color_anim) { + let color_channels = RenderMap::animation_eval( + &anim.def, + cur_time, + cur_anim_time, + &color_anim_offset, + include_last_anim_point, + ); + color_anims_values.insert((color_anim, color_anim_offset), color_channels); } } + + QuadAnimEvalResult { + pos_anims_values, + color_anims_values, + } } fn render_quad_layer( @@ -773,51 +955,100 @@ impl RenderMap { include_last_anim_point: bool, visuals: &QuadLayerVisuals, animations: &AnimationsSkeleton, - quads: &Vec, + quads: &[Quad], ) { - if let Some(buffer_container_index) = &visuals.buffer_object_index { - let map_graphics = &self.map_graphics; + let QuadAnimEvalResult { + pos_anims_values, + color_anims_values, + } = Self::prepare_quad_anims( + &self.pos_anims, + &self.color_anims, + cur_time, + cur_anim_time, + include_last_anim_point, + visuals, + animations, + ); + + if let Some(buffer_container) = &visuals.buffer_object_index { let texture = &texture; - let cur_quad_offset_cell = Cell::new(0); - let cur_quad_offset = &cur_quad_offset_cell; - self.stream_handle.fill_uniform_instance( - hi_closure!( - , - [ - cur_time: &Duration, - cur_anim_time: &Duration, - include_last_anim_point: bool, - cur_quad_offset: &Cell, - animations: &AnimationsSkeleton, - quads: &Vec, - ], - |stream_handle: StreamedUniforms< - '_, - QuadRenderInfo, - >| - -> () { - RenderMap::prepare_quad_rendering( - stream_handle, - cur_time, - cur_anim_time, - include_last_anim_point, - cur_quad_offset, - animations, - quads - ); - }), - hi_closure!([map_graphics: &MapGraphics, state: &State, texture: &TextureType, buffer_container_index: &BufferObject, cur_quad_offset: &Cell], |instance: usize, count: usize| -> () { - map_graphics.render_quad_layer( + for draw_range in &visuals.draw_ranges { + match draw_range.anim { + QuadVisualRangeAnim::NoAnim => { + self.map_graphics.render_quad_layer_grouped( state, texture.clone(), - buffer_container_index, - instance, - count, - cur_quad_offset.get(), + buffer_container, + draw_range.range.end - draw_range.range.start, + draw_range.range.start, + QuadRenderInfo { + color: ColorRgba::new(1.0, 1.0, 1.0, 1.0), + offsets: Default::default(), + rotation: 0.0, + padding: 0.0, + }, ); - cur_quad_offset.set(cur_quad_offset.get() + count); - }), - ); + } + QuadVisualRangeAnim::ColorAnim { anim, anim_offset } => { + self.prepare_group_rendering( + &color_anims_values, + &pos_anims_values, + Some(anim), + &anim_offset, + None, + &Default::default(), + draw_range.range.clone(), + state, + texture, + buffer_container, + ); + } + QuadVisualRangeAnim::PosAnim { anim, anim_offset } => { + self.prepare_group_rendering( + &color_anims_values, + &pos_anims_values, + None, + &Default::default(), + Some(anim), + &anim_offset, + draw_range.range.clone(), + state, + texture, + buffer_container, + ); + } + QuadVisualRangeAnim::FullAnim { + pos, + pos_offset, + color, + color_offset, + } => { + self.prepare_group_rendering( + &color_anims_values, + &pos_anims_values, + Some(color), + &color_offset, + Some(pos), + &pos_offset, + draw_range.range.clone(), + state, + texture, + buffer_container, + ); + } + QuadVisualRangeAnim::Chaos => { + self.render_quads_with_anim( + state, + texture, + &color_anims_values, + &pos_anims_values, + &quads[draw_range.range.clone()], + buffer_container, + draw_range.range.start, + ); + } + } + } } } @@ -837,29 +1068,19 @@ impl RenderMap { pub fn set_group_clipping( &self, state: &mut State, - center: &vec2, - zoom: f32, - parallax_aware_zoom: bool, - forced_aspect_ratio: Option, + camera: &dyn CameraInterface, clipping: &MapGroupAttrClipping, ) -> bool { - let points = RenderTools::canvas_points_of_group( - forced_aspect_ratio - .map(|aspect_ratio| CanvasType::Custom { aspect_ratio }) - .unwrap_or(CanvasType::Handle(&self.canvas_handle)), - center.x, - center.y, - None, - zoom, - parallax_aware_zoom, - ); + let mut fake_state = State::new(); + camera.project(&self.canvas_handle, &mut fake_state, None); + let (tl_x, tl_y, br_x, br_y) = fake_state.get_canvas_mapping(); - let x0 = (clipping.pos.x.to_num::() - points[0]) / (points[2] - points[0]); - let y0 = (clipping.pos.y.to_num::() - points[1]) / (points[3] - points[1]); - let x1 = ((clipping.pos.x.to_num::() + clipping.size.x.to_num::()) - points[0]) - / (points[2] - points[0]); - let y1 = ((clipping.pos.y.to_num::() + clipping.size.y.to_num::()) - points[1]) - / (points[3] - points[1]); + let x0 = (clipping.pos.x.to_num::() - tl_x) / (br_x - tl_x); + let y0 = (clipping.pos.y.to_num::() - tl_y) / (br_y - tl_y); + let x1 = ((clipping.pos.x.to_num::() + clipping.size.x.to_num::()) - tl_x) + / (br_x - tl_x); + let y1 = ((clipping.pos.y.to_num::() + clipping.size.y.to_num::()) - tl_y) + / (br_y - tl_y); if x1 < 0.0 || x0 > 1.0 || y1 < 0.0 || y0 > 1.0 { // group is not visible at all @@ -974,7 +1195,7 @@ impl RenderMap { impl Borrow, >, config: &ConfigMap, - camera: &Camera, + camera: &dyn CameraInterface, cur_time: &Duration, cur_anim_time: &Duration, include_last_anim_point: bool, @@ -986,8 +1207,6 @@ impl RenderMap { T: Borrow, Q: Borrow, { - let center = &camera.pos; - // skip rendering if detail layers if not wanted if layer.high_detail() && !config.high_detail { return; @@ -998,30 +1217,12 @@ impl RenderMap { // clipping if let Some(clipping) = &group_attr.clipping { // set clipping - if !self.set_group_clipping( - &mut state, - center, - camera.zoom, - camera.parallax_aware_zoom, - camera.forced_aspect_ratio, - clipping, - ) { + if !self.set_group_clipping(&mut state, camera, clipping) { return; } } - RenderTools::map_canvas_of_group( - camera - .forced_aspect_ratio - .map(|aspect_ratio| CanvasType::Custom { aspect_ratio }) - .unwrap_or(CanvasType::Handle(&self.canvas_handle)), - &mut state, - center.x, - center.y, - Some(group_attr), - camera.zoom, - camera.parallax_aware_zoom, - ); + camera.project(&self.canvas_handle, &mut state, Some(group_attr)); match layer { MapVisualLayerBase::Tile(layer) => { @@ -1138,7 +1339,7 @@ impl RenderMap { entities_key: Option<&ContainerKey>, physics_group_name: &str, layer: &MapLayerPhysicsSkeleton, - camera: &Camera, + camera: &dyn CameraInterface, cur_time: &Duration, cur_anim_time: &Duration, include_last_anim_point: bool, @@ -1152,18 +1353,7 @@ impl RenderMap { let entities = entities_container.get_or_default_opt(entities_key); let mut state = State::new(); - RenderTools::map_canvas_of_group( - camera - .forced_aspect_ratio - .map(|aspect_ratio| CanvasType::Custom { aspect_ratio }) - .unwrap_or(CanvasType::Handle(&self.canvas_handle)), - &mut state, - camera.pos.x, - camera.pos.y, - None, - camera.zoom, - camera.parallax_aware_zoom, - ); + camera.project(&self.canvas_handle, &mut state, None); let is_main_physics_layer = matches!(layer, MapLayerPhysicsSkeleton::Game(_)); @@ -1267,7 +1457,7 @@ impl RenderMap { fn render_design_impl<'a>( &self, map: &MapVisual, - pipe: &mut RenderPipelineBase, + pipe: &RenderPipelineBase, render_layers: impl Iterator, layer_ty: RenderLayerType, ) { @@ -1310,30 +1500,30 @@ impl RenderMap { pub fn render_physics_layers( &self, - pipe: &mut RenderPipelineBase, + pipe: &mut RenderPipelinePhysics, render_infos: &[MapPhysicsRenderInfo], ) { for render_info in render_infos { self.render_physics_layer( - &pipe.map.animations, + &pipe.base.map.animations, pipe.entities_container, pipe.entities_key, pipe.physics_group_name, - &pipe.map.groups.physics.layers[render_info.layer_index], - pipe.camera, - pipe.cur_time, - pipe.cur_anim_time, - pipe.include_last_anim_point, - pipe.config.physics_layer_opacity, + &pipe.base.map.groups.physics.layers[render_info.layer_index], + pipe.base.camera, + pipe.base.cur_time, + pipe.base.cur_anim_time, + pipe.base.include_last_anim_point, + pipe.base.config.physics_layer_opacity, None, ); } } - pub fn render_background(&self, pipe: &mut RenderPipeline) { + pub fn render_background(&self, pipe: &RenderPipeline) { self.render_design_impl( pipe.base.map, - &mut pipe.base, + &pipe.base, pipe.buffered_map.render.background_render_layers.iter(), RenderLayerType::Background, ); @@ -1348,10 +1538,10 @@ impl RenderMap { ); } - pub fn render_foreground(&self, pipe: &mut RenderPipeline) { + pub fn render_foreground(&self, pipe: &RenderPipeline) { self.render_design_impl( pipe.base.map, - &mut pipe.base, + &pipe.base, pipe.buffered_map.render.foreground_render_layers.iter(), RenderLayerType::Foreground, ); @@ -1367,16 +1557,16 @@ impl RenderMap { } /// render the whole map but only with design layers at full opacity - pub fn render_full_design(&self, map: &MapVisual, pipe: &mut RenderPipeline) { + pub fn render_full_design(&self, map: &MapVisual, pipe: &RenderPipeline) { self.render_design_impl( map, - &mut pipe.base, + &pipe.base, pipe.buffered_map.render.background_render_layers.iter(), RenderLayerType::Background, ); self.render_design_impl( map, - &mut pipe.base, + &pipe.base, pipe.buffered_map.render.foreground_render_layers.iter(), RenderLayerType::Foreground, ); diff --git a/game/client-render-base/src/map/map_buffered.rs b/game/client-render-base/src/map/map_buffered.rs index a2d5293c..8bde6821 100644 --- a/game/client-render-base/src/map/map_buffered.rs +++ b/game/client-render-base/src/map/map_buffered.rs @@ -3,7 +3,6 @@ pub mod graphic_tile; use std::{borrow::BorrowMut, collections::HashMap, ops::Range, sync::Arc}; -use game_base::mapdef_06::{DdraceTileNum, TILE_SWITCHTIMEDOPEN}; use graphics::{ graphics_mt::GraphicsMultiThreaded, handles::{ @@ -14,6 +13,7 @@ use graphics::{ }, }; use hiarc::{hiarc_safer_rc_refcell, Hiarc}; +use legacy_map::mapdef_06::{DdraceTileNum, TILE_SWITCHTIMEDOPEN}; use map::{ map::{ groups::{ @@ -48,15 +48,19 @@ use graphics_types::{ commands::{CommandUpdateBufferObjectRegion, CommandUpdateShaderStorageRegion}, types::{GraphicsBackendMemory, GraphicsMemoryAllocationType}, }; +use rustc_hash::FxHashSet; use sound::{ scene_object::SceneObject, sound_listener::SoundListener, sound_object::SoundObject, sound_play_handle::SoundPlayHandle, types::SoundPlayBaseProps, }; -use crate::map::map_with_visual::{ - MapVisualConfig, MapVisualImage2dArray, MapVisualLayerArbitrary, MapVisualLayerQuad, - MapVisualLayerSound, MapVisualLayerTile, MapVisualMetadata, MapVisualPhysicsLayer, - MapVisualProps, +use crate::map::{ + map_pipeline::GRAPHICS_MAX_QUADS_RENDER_COUNT, + map_with_visual::{ + MapVisualConfig, MapVisualImage2dArray, MapVisualLayerArbitrary, MapVisualLayerQuad, + MapVisualLayerSound, MapVisualLayerTile, MapVisualMetadata, MapVisualPhysicsLayer, + MapVisualProps, + }, }; use self::{ @@ -198,9 +202,41 @@ pub struct QuadVisual { pub index_buffer_byte_offset: usize, } +#[derive(Debug, Hiarc, Clone, Copy, PartialEq, Eq)] +pub enum QuadVisualRangeAnim { + NoAnim, + ColorAnim { + anim: usize, + anim_offset: time::Duration, + }, + PosAnim { + anim: usize, + anim_offset: time::Duration, + }, + FullAnim { + pos: usize, + pos_offset: time::Duration, + color: usize, + color_offset: time::Duration, + }, + /// Too many quads with alternating anims + Chaos, +} + +#[derive(Debug, Hiarc, Clone)] +pub struct QuadVisualRange { + pub anim: QuadVisualRangeAnim, + pub range: Range, +} + #[derive(Debug, Hiarc, Clone)] pub struct QuadLayerVisuals { pub buffer_object_index: Option, + pub draw_ranges: Vec, + /// distinct pos anims in this layer + pub pos_anims: Vec<(usize, time::Duration)>, + /// distinct color anims in this layer + pub color_anims: Vec<(usize, time::Duration)>, } #[hiarc_safer_rc_refcell] @@ -412,11 +448,21 @@ pub struct MapBufferPhysicsTileLayer { overlays: Vec<(MapRenderTextOverlayType, MapBufferTileLayerBase)>, } +#[derive(Debug, Default)] +struct QuadVisualExtra { + draw_ranges: Vec, + // distinct pos & color anims + pos_anims: Vec<(usize, time::Duration)>, + color_anims: Vec<(usize, time::Duration)>, +} + #[derive(Debug, Default)] pub struct ClientMapBufferQuadLayer { mem: Option, quad_count_for_indices: u64, render_info: MapRenderInfo, + + extra: QuadVisualExtra, } pub struct ClientMapBufferUploadData { @@ -862,6 +908,12 @@ impl ClientMapBuffered { let ClientMapBufferQuadLayer { mem: raw_data, quad_count_for_indices, + extra: + QuadVisualExtra { + draw_ranges, + pos_anims, + color_anims, + }, .. } = upload_data; if raw_data @@ -875,10 +927,16 @@ impl ClientMapBuffered { buffer_object_index: Some( buffer_object_handle.create_buffer_object(raw_data.unwrap()), ), + draw_ranges, + pos_anims, + color_anims, } } else { QuadLayerVisuals { buffer_object_index: None, + draw_ranges, + pos_anims, + color_anims, } } } @@ -1236,7 +1294,7 @@ impl ClientMapBuffered { } if let Err(err) = graphics_mt.try_flush_mem(&mut upload_data_buffer, false) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {}", err); + log::debug!("err while flushing memory: {err}"); } Some(upload_data_buffer) } else { @@ -1260,7 +1318,7 @@ impl ClientMapBuffered { } if let Err(err) = graphics_mt.try_flush_mem(&mut upload_data_buffer, false) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {}", err); + log::debug!("err while flushing memory: {err}"); } Some(upload_data_buffer) } else { @@ -1311,6 +1369,94 @@ impl ClientMapBuffered { tmp_quads_textured } + fn quad_visual_ranges(quads: &[Quad]) -> QuadVisualExtra { + if quads.is_empty() { + return Default::default(); + } + + fn quad_to_range_anim(q: &Quad) -> QuadVisualRangeAnim { + let color = q.color_anim; + let pos = q.pos_anim; + if let Some((color, pos)) = color.zip(pos) { + QuadVisualRangeAnim::FullAnim { + pos, + pos_offset: q.pos_anim_offset, + color, + color_offset: q.color_anim_offset, + } + } else if let Some(color) = color { + QuadVisualRangeAnim::ColorAnim { + anim: color, + anim_offset: q.color_anim_offset, + } + } else if let Some(pos) = pos { + QuadVisualRangeAnim::PosAnim { + anim: pos, + anim_offset: q.pos_anim_offset, + } + } else { + QuadVisualRangeAnim::NoAnim + } + } + + let quad = &quads[0]; + let mut ranges = vec![]; + ranges.push(QuadVisualRange { + anim: quad_to_range_anim(quad), + range: 0..0, + }); + + let mut pos_anims: FxHashSet<(usize, time::Duration)> = Default::default(); + let mut color_anims: FxHashSet<(usize, time::Duration)> = Default::default(); + + quads.iter().enumerate().for_each(|(i, quad)| { + let anim = quad_to_range_anim(quad); + + if anim != ranges.last().unwrap().anim { + ranges.push(QuadVisualRange { anim, range: i..i }); + } + ranges.last_mut().unwrap().range.end += 1; + + if let Some(pos_anim) = quad.pos_anim { + pos_anims.insert((pos_anim, quad.pos_anim_offset)); + } + if let Some(color_anim) = quad.color_anim { + color_anims.insert((color_anim, quad.color_anim_offset)); + } + }); + + // convert ranges with quads less than 64 to chaos + let mut res_ranges: Vec = vec![]; + if ranges.len() > 1 { + for range in ranges { + let quad_count = range.range.end - range.range.start; + if quad_count < GRAPHICS_MAX_QUADS_RENDER_COUNT { + if res_ranges + .last_mut() + .is_some_and(|r| matches!(r.anim, QuadVisualRangeAnim::Chaos)) + { + res_ranges.last_mut().unwrap().range.end = range.range.end; + } else { + res_ranges.push(QuadVisualRange { + anim: QuadVisualRangeAnim::Chaos, + range: range.range, + }); + } + } else { + res_ranges.push(range); + } + } + } else { + res_ranges = ranges; + } + + QuadVisualExtra { + draw_ranges: res_ranges, + pos_anims: pos_anims.into_iter().collect(), + color_anims: color_anims.into_iter().collect(), + } + } + fn upload_quad_layer_buffer( attr: &MapLayerQuadsAttrs, quads: &[Quad], @@ -1343,9 +1489,10 @@ impl ClientMapBuffered { if let Err(err) = graphics_mt.try_flush_mem(&mut upload_data_buffer, false) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {}", err); + log::debug!("err while flushing memory: {err}"); } + let extra = Self::quad_visual_ranges(quads); Some(ClientMapBufferQuadLayer { mem: Some(upload_data_buffer), quad_count_for_indices: quads.len() as u64, @@ -1353,6 +1500,7 @@ impl ClientMapBuffered { group_index, layer_index, }, + extra, }) } else { None @@ -2467,9 +2615,12 @@ impl ClientMapBuffered { }); let upload_data_len = upload_data_buffer.len(); - layer - .user - .borrow_mut() + let quad_visuals = layer.user.borrow_mut(); + let extra = Self::quad_visual_ranges(&layer.layer.quads); + quad_visuals.draw_ranges = extra.draw_ranges; + quad_visuals.pos_anims = extra.pos_anims; + quad_visuals.color_anims = extra.color_anims; + quad_visuals .buffer_object_index .as_ref() .unwrap() diff --git a/game/client-render-base/src/map/map_pipeline.rs b/game/client-render-base/src/map/map_pipeline.rs index a7172483..4ef12adb 100644 --- a/game/client-render-base/src/map/map_pipeline.rs +++ b/game/client-render-base/src/map/map_pipeline.rs @@ -44,6 +44,7 @@ pub enum MapPipelineNames { EditorTilePipeline, TileBorderPipeline, QuadPipeline, + QuadGroupedPipeline, } #[derive(Debug, Hiarc, Clone, Copy, Serialize, Deserialize)] @@ -125,22 +126,34 @@ impl QuadRenderInfo { } #[derive(Debug, Serialize, Deserialize)] -pub struct CommandRenderQuadLayer { +pub struct CommandRenderQuadLayerBase { pub state: State, pub texture_index: StateTexture, pub buffer_object_index: u128, - pub quad_info_uniform_instance: usize, pub quad_num: usize, pub quad_offset: usize, } +#[derive(Debug, Serialize, Deserialize)] +pub struct CommandRenderQuadLayer { + pub base: CommandRenderQuadLayerBase, + pub quad_info_uniform_instance: usize, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CommandRenderQuadLayerGrouped { + pub base: CommandRenderQuadLayerBase, + pub quad_info: QuadRenderInfo, +} + #[derive(Debug, Serialize, Deserialize)] pub enum CommandsRenderMap { TileLayer(CommandRenderTileLayer), // render a tilelayer EditorTileLayer(CommandRenderEditorTileLayer), // render a tilelayer for editor BorderTile(CommandRenderBorderTile), // render one tile multiple times QuadLayer(CommandRenderQuadLayer), // render a quad layer + QuadLayerGrouped(CommandRenderQuadLayerGrouped), // render a quad layer without anims } type UniformTilePos = [f32; 4 * 2]; @@ -202,6 +215,16 @@ pub struct UniformQuadGPos { pub quad_offset: i32, } +#[derive(Default)] +#[repr(C)] +pub struct UniformQuadGroupedGPos { + pub pos: [f32; 4 * 2], + + pub color: ColorRgba, + pub offset: vec2, + pub rotation: f32, +} + const TILE_LAYER_VERTEX_SIZE: usize = std::mem::size_of::() + std::mem::size_of::(); // last addition is offset for alignment const TILE_LAYER_TEXTURED_VERTEX_SIZE: usize = TILE_LAYER_VERTEX_SIZE; @@ -356,7 +379,7 @@ impl MapPipeline { } } - fn quad_pipeline_layout(is_textured: bool) -> BackendPipelineLayout { + fn quad_pipeline_vertex_inp(is_textured: bool) -> Vec { let mut attribute_descriptors: Vec = Default::default(); attribute_descriptors.push(BackendVertexInputAttributeDescription { @@ -379,7 +402,18 @@ impl MapPipeline { offset: QUAD_LAYER_VERTEX_SIZE as u32, }); } + attribute_descriptors + } + fn quad_pipeline_stride(is_textured: bool) -> BackendDeviceSize { + (if is_textured { + QUAD_LAYER_TEXTURED_VERTEX_SIZE + } else { + QUAD_LAYER_VERTEX_SIZE + }) as BackendDeviceSize + } + + fn quad_pipeline_layout(is_textured: bool) -> BackendPipelineLayout { let mut set_layouts: Vec = Default::default(); if is_textured { set_layouts.push(BackendResourceDescription::Fragment2DTexture); @@ -397,16 +431,35 @@ impl MapPipeline { }] .to_vec(); - let stride = if is_textured { - QUAD_LAYER_TEXTURED_VERTEX_SIZE - } else { - QUAD_LAYER_VERTEX_SIZE - } as BackendDeviceSize; BackendPipelineLayout { - vertex_attributes: attribute_descriptors, + vertex_attributes: Self::quad_pipeline_vertex_inp(is_textured), descriptor_layouts: set_layouts, push_constants, - stride, + stride: Self::quad_pipeline_stride(is_textured), + geometry_is_line: false, + } + } + + fn quad_grouped_pipeline_layout(is_textured: bool) -> BackendPipelineLayout { + let mut set_layouts: Vec = Default::default(); + if is_textured { + set_layouts.push(BackendResourceDescription::Fragment2DTexture); + } + + let push_constant_size = std::mem::size_of::(); + + let push_constants = [BackendPushConstant { + stage_flags: BackendShaderStage::VERTEX, + offset: 0, + size: push_constant_size as u32, + }] + .to_vec(); + + BackendPipelineLayout { + vertex_attributes: Self::quad_pipeline_vertex_inp(is_textured), + descriptor_layouts: set_layouts, + push_constants, + stride: Self::quad_pipeline_stride(is_textured), geometry_is_line: false, } } @@ -510,9 +563,9 @@ impl MapPipeline { ); } - fn cmd_render_quad_layer_fill_execute_buffer( + fn cmd_render_quad_layer_base_fill_execute_buffer( render_execute_manager: &mut dyn BackendRenderExecuteInterface, - cmd: &CommandRenderQuadLayer, + cmd: &CommandRenderQuadLayerBase, ) { render_execute_manager.set_vertex_buffer(cmd.buffer_object_index); @@ -536,12 +589,6 @@ impl MapPipeline { } } - render_execute_manager.uses_stream_uniform_buffer( - 0, - cmd.quad_info_uniform_instance as u64, - 1, - ); - render_execute_manager.uses_index_buffer(); render_execute_manager.estimated_render_calls( @@ -551,6 +598,25 @@ impl MapPipeline { render_execute_manager.exec_buffer_fill_dynamic_states(&cmd.state); } + fn cmd_render_quad_layer_fill_execute_buffer( + render_execute_manager: &mut dyn BackendRenderExecuteInterface, + cmd: &CommandRenderQuadLayer, + ) { + Self::cmd_render_quad_layer_base_fill_execute_buffer(render_execute_manager, &cmd.base); + render_execute_manager.uses_stream_uniform_buffer( + 0, + cmd.quad_info_uniform_instance as u64, + 1, + ); + } + + fn cmd_render_quad_layer_grouped_fill_execute_buffer( + render_execute_manager: &mut dyn BackendRenderExecuteInterface, + cmd: &CommandRenderQuadLayerGrouped, + ) { + Self::cmd_render_quad_layer_base_fill_execute_buffer(render_execute_manager, &cmd.base); + } + fn render_tile_layer( &self, render_manager: &mut dyn BackendRenderInterface, @@ -747,17 +813,71 @@ impl MapPipeline { ) } + fn cmd_render_quad_layer_grouped( + &self, + render_manager: &mut dyn BackendRenderInterface, + cmd: &CommandRenderQuadLayerGrouped, + ) -> anyhow::Result<()> { + let mut m: [f32; 4 * 2] = Default::default(); + render_manager.get_state_matrix(&cmd.base.state, &mut m); + + render_manager.bind_pipeline( + &cmd.base.state, + &cmd.base.texture_index, + SubRenderPassAttributes::Additional( + MapPipelineNames::QuadGroupedPipeline as u64 + self.pipe_name_offset, + ), + ); + + render_manager.bind_vertex_buffer(); + + render_manager.bind_index_buffer(0); + + if render_manager.is_textured() { + render_manager.bind_texture_descriptor_sets(0, 0); + } + + let push_constant_vertex = UniformQuadGroupedGPos { + pos: m, + color: cmd.quad_info.color, + offset: cmd.quad_info.offsets, + rotation: cmd.quad_info.rotation, + }; + + render_manager.push_constants(BackendShaderStage::VERTEX, 0, unsafe { + std::slice::from_raw_parts( + &push_constant_vertex as *const UniformQuadGroupedGPos as *const u8, + std::mem::size_of::(), + ) + }); + + let draw_count = cmd.base.quad_num; + let render_offset: usize = 0; + + let index_offset = (cmd.base.quad_offset + render_offset) * 6; + + render_manager.draw_indexed( + draw_count.checked_mul(6).unwrap().try_into().unwrap(), + 1, + index_offset as u32, + 0, + 0, + ); + + Ok(()) + } + fn cmd_render_quad_layer( &self, render_manager: &mut dyn BackendRenderInterface, cmd: &CommandRenderQuadLayer, ) -> anyhow::Result<()> { let mut m: [f32; 4 * 2] = Default::default(); - render_manager.get_state_matrix(&cmd.state, &mut m); + render_manager.get_state_matrix(&cmd.base.state, &mut m); render_manager.bind_pipeline( - &cmd.state, - &cmd.texture_index, + &cmd.base.state, + &cmd.base.texture_index, SubRenderPassAttributes::Additional( MapPipelineNames::QuadPipeline as u64 + self.pipe_name_offset, ), @@ -773,7 +893,7 @@ impl MapPipeline { let push_constant_vertex = UniformQuadGPos { pos: m, - quad_offset: cmd.quad_offset as i32, + quad_offset: cmd.base.quad_offset as i32, }; render_manager.push_constants(BackendShaderStage::VERTEX, 0, unsafe { @@ -783,7 +903,7 @@ impl MapPipeline { ) }); - let mut draw_count = cmd.quad_num; + let mut draw_count = cmd.base.quad_num; let mut render_offset: usize = 0; while draw_count > 0 { @@ -793,13 +913,13 @@ impl MapPipeline { draw_count }; - let index_offset = (cmd.quad_offset + render_offset) * 6; + let index_offset = (cmd.base.quad_offset + render_offset) * 6; render_manager .bind_uniform_descriptor_sets(if render_manager.is_textured() { 2 } else { 0 }, 0); if render_offset > 0 { - let quad_offset: i32 = (cmd.quad_offset + render_offset) as i32; + let quad_offset: i32 = (cmd.base.quad_offset + render_offset) as i32; render_manager.push_constants( BackendShaderStage::VERTEX, (std::mem::size_of::() - std::mem::size_of::()) as u32, @@ -848,6 +968,9 @@ impl BackendCustomPipeline for MapPipeline { MapPipelineNames::EditorTilePipeline => Self::editor_tile_pipeline_layout(), MapPipelineNames::TileBorderPipeline => Self::border_tile_pipeline_layout(is_textured), MapPipelineNames::QuadPipeline => Self::quad_pipeline_layout(is_textured), + MapPipelineNames::QuadGroupedPipeline => { + Self::quad_grouped_pipeline_layout(is_textured) + } } } @@ -903,6 +1026,19 @@ impl BackendCustomPipeline for MapPipeline { )) } } + MapPipelineNames::QuadGroupedPipeline => { + if is_textured { + Some(( + "shader/vulkan/quad_grouped_textured.vert.spv".into(), + "shader/vulkan/quad_grouped_textured.frag.spv".into(), + )) + } else { + Some(( + "shader/vulkan/quad_grouped.vert.spv".into(), + "shader/vulkan/quad_grouped.frag.spv".into(), + )) + } + } } } @@ -929,6 +1065,9 @@ impl BackendCustomPipeline for MapPipeline { CommandsRenderMap::QuadLayer(cmd) => { Self::cmd_render_quad_layer_fill_execute_buffer(render_execute, &cmd); } + CommandsRenderMap::QuadLayerGrouped(cmd) => { + Self::cmd_render_quad_layer_grouped_fill_execute_buffer(render_execute, &cmd); + } } } @@ -949,6 +1088,9 @@ impl BackendCustomPipeline for MapPipeline { } CommandsRenderMap::BorderTile(cmd) => self.cmd_render_border_tile(render, &cmd), CommandsRenderMap::QuadLayer(cmd) => self.cmd_render_quad_layer(render, &cmd), + CommandsRenderMap::QuadLayerGrouped(cmd) => { + self.cmd_render_quad_layer_grouped(render, &cmd) + } } } @@ -1046,15 +1188,15 @@ impl BackendCustomPipeline for MapPipeline { CommandsRenderMap::QuadLayer(cmd) => f(GraphicsObjectRewriteFunc { textures_2d_array: &mut [], buffer_objects: &mut [GraphicsBufferObjectAccessAndRewrite { - buffer_object_index: &mut cmd.buffer_object_index, + buffer_object_index: &mut cmd.base.buffer_object_index, accesses: { let mut accesses = self.accesses_pool.new(); accesses.push(GraphicsBufferObjectAccess::Quad { - quad_offset: cmd.quad_offset, - quad_count: cmd.quad_num, + quad_offset: cmd.base.quad_offset, + quad_count: cmd.base.quad_num, buffer_byte_offset: 0, - vertex_byte_size: if cmd.texture_index.is_textured() { + vertex_byte_size: if cmd.base.texture_index.is_textured() { QUAD_LAYER_TEXTURED_VERTEX_SIZE } else { QUAD_LAYER_VERTEX_SIZE @@ -1065,14 +1207,40 @@ impl BackendCustomPipeline for MapPipeline { accesses }, }], - textures: &mut [&mut cmd.texture_index], + textures: &mut [&mut cmd.base.texture_index], uniform_instances: &mut [GraphicsUniformAccessAndRewrite { index: &mut cmd.quad_info_uniform_instance, - instance_count: cmd.quad_num, + instance_count: cmd.base.quad_num, single_instance_byte_size: std::mem::size_of::(), }], shader_storages: &mut [], }), + CommandsRenderMap::QuadLayerGrouped(cmd) => f(GraphicsObjectRewriteFunc { + textures_2d_array: &mut [], + buffer_objects: &mut [GraphicsBufferObjectAccessAndRewrite { + buffer_object_index: &mut cmd.base.buffer_object_index, + accesses: { + let mut accesses = self.accesses_pool.new(); + + accesses.push(GraphicsBufferObjectAccess::Quad { + quad_offset: cmd.base.quad_offset, + quad_count: cmd.base.quad_num, + buffer_byte_offset: 0, + vertex_byte_size: if cmd.base.texture_index.is_textured() { + QUAD_LAYER_TEXTURED_VERTEX_SIZE + } else { + QUAD_LAYER_VERTEX_SIZE + }, + alignment: 4.try_into().unwrap(), + }); + + accesses + }, + }], + textures: &mut [&mut cmd.base.texture_index], + uniform_instances: &mut [], + shader_storages: &mut [], + }), } cmd.clear(); bincode::serde::encode_into_std_write( @@ -1234,7 +1402,7 @@ impl MapGraphics { &self, state: &State, texture: TextureType, - buffer_object_index: &BufferObject, + buffer_object: &BufferObject, quad_info_uniform_instance: usize, quad_num: usize, quad_offset: usize, @@ -1245,11 +1413,13 @@ impl MapGraphics { // add the VertexArrays and draw let cmd = CommandRenderQuadLayer { - state: *state, - texture_index: texture.into(), - quad_num, - quad_offset, - buffer_object_index: buffer_object_index.get_index_unsafe(), + base: CommandRenderQuadLayerBase { + state: *state, + texture_index: texture.into(), + quad_num, + quad_offset, + buffer_object_index: buffer_object.get_index_unsafe(), + }, quad_info_uniform_instance, }; @@ -1272,4 +1442,48 @@ impl MapGraphics { }, ))); } + + pub fn render_quad_layer_grouped( + &self, + state: &State, + texture: TextureType, + buffer_object: &BufferObject, + quad_num: usize, + quad_offset: usize, + quad_info: QuadRenderInfo, + ) { + if quad_num == 0 { + return; + } + + // add the VertexArrays and draw + let cmd = CommandRenderQuadLayerGrouped { + base: CommandRenderQuadLayerBase { + state: *state, + texture_index: texture.into(), + quad_num, + quad_offset, + buffer_object_index: buffer_object.get_index_unsafe(), + }, + quad_info, + }; + + let mut pooled_cmd = self.cmd_pool.new(); + let pooled_write: &mut Vec<_> = &mut pooled_cmd; + bincode::serde::encode_into_std_write( + CommandsRenderMap::QuadLayerGrouped(cmd), + pooled_write, + bincode::config::standard(), + ) + .unwrap(); + let mut mod_name = self.mod_name.new(); + mod_name.push_str(MOD_NAME); + self.backend_handle + .add_cmd(AllCommands::Render(CommandsRender::Mod( + CommandsRenderMod { + cmd: pooled_cmd, + mod_name, + }, + ))); + } } diff --git a/game/client-render-base/src/map/map_sound.rs b/game/client-render-base/src/map/map_sound.rs index d397f2ab..d4b3a295 100644 --- a/game/client-render-base/src/map/map_sound.rs +++ b/game/client-render-base/src/map/map_sound.rs @@ -1,5 +1,6 @@ use std::{borrow::Borrow, time::Duration}; +use camera::{Camera, CameraInterface}; use hiarc::Hiarc; use map::{ map::groups::{layers::design::SoundShape, MapGroupAttr}, @@ -22,8 +23,6 @@ use super::{ map::RenderMap, map_buffered::{ClientMapBuffered, MapSoundProcessInfo, SoundLayerSounds}, map_with_visual::{MapVisual, MapVisualLayer}, - render_pipe::Camera, - render_tools::RenderTools, }; #[derive(Debug, Clone, Copy)] @@ -127,7 +126,7 @@ impl MapSoundProcess { sounds: &[MapResourceRefSkeleton>], group_attr: &MapGroupAttr, layer: &MapLayerSoundSkeleton, - camera: &Camera, + camera: &dyn CameraInterface, map_sound_volume: f64, ) where S: Borrow, @@ -174,7 +173,7 @@ impl MapSoundProcess { } let interact = Self::camera_sound_interaction( - &RenderTools::pos_to_group(camera.pos, Some(group_attr)), + &Camera::pos_to_group(camera.pos(), Some(group_attr)), &pos, rot, &sound.shape, @@ -182,9 +181,7 @@ impl MapSoundProcess { ); // check if the sound should play, else play or update let sounds: &SoundLayerSounds = layer.user.borrow(); - if interact.is_some() { - let (falloff, panning) = interact.unwrap(); - + if let Some((falloff, panning)) = interact { let panning = if sound.panning { panning } else { 0.5 }; let base_props = SoundPlayBaseProps { @@ -228,7 +225,7 @@ impl MapSoundProcess { map: &MapVisual, sound_layers: impl Iterator, layer_ty: SoundLayerType, - camera: &Camera, + camera: &dyn CameraInterface, map_sound_volume: f64, ) { let groups = match layer_ty { @@ -261,7 +258,7 @@ impl MapSoundProcess { include_last_anim_point: bool, map: &MapVisual, buffered_map: &ClientMapBuffered, - camera: &Camera, + camera: &dyn CameraInterface, map_sound_volume: f64, ) { map.user.sound_scene.stay_active(); @@ -283,7 +280,7 @@ impl MapSoundProcess { include_last_anim_point: bool, map: &MapVisual, buffered_map: &ClientMapBuffered, - camera: &Camera, + camera: &dyn CameraInterface, map_sound_volume: f64, ) { map.user.sound_scene.stay_active(); diff --git a/game/client-render-base/src/map/render_map_base.rs b/game/client-render-base/src/map/render_map_base.rs index fddefbac..d81e4644 100644 --- a/game/client-render-base/src/map/render_map_base.rs +++ b/game/client-render-base/src/map/render_map_base.rs @@ -37,7 +37,7 @@ use image_utils::{ png::{is_png_image_valid, load_png_image_as_rgba, resize_rgba, PngValidatorOptions}, utils::{highest_bit, texture_2d_to_3d}, }; -use map::map::Map; +use map::{file::MapFileReader, map::Map}; use math::math::vector::vec2; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use sound::{commands::SoundSceneCreateProps, scene_handle::SoundSceneHandle, sound::SoundManager}; @@ -99,8 +99,9 @@ impl RenderMapLoading { Self { task: io.rt.spawn(async move { let benchmark = Benchmark::new(do_benchmark); + let map_reader = MapFileReader::new(file)?; // open the map file - let (resources, resources_bytes_read) = Map::read_resources_and_header(&file)?; + let resources = Map::read_resources_and_header(&map_reader)?; benchmark.bench("opening the full map file"); // read content files @@ -269,11 +270,8 @@ impl RenderMapLoading { new_height as u32, ); log::warn!( - "3D/2D array texture had to be resized, {}x{} to {}x{}", - convert_width, - convert_height, - new_width, - new_height + "3D/2D array texture had to be resized, \ + {convert_width}x{convert_height} to {new_width}x{new_height}" ); convert_width = new_width; @@ -308,11 +306,12 @@ impl RenderMapLoading { if let Err(err) = graphics_mt.try_flush_mem(&mut tex_3d, false) { // Ignore the error, but log it. - log::debug!("err while flushing memory: {}", err); + log::debug!("err while flushing memory: {err}"); } (image_3d_width, image_3d_height, 256, tex_3d) }; + // load images, external images and do map buffering let (images_loading, sounds_loading, map_prepare) = runtime_tp.install(|| { join_all!( @@ -434,21 +433,28 @@ impl RenderMapLoading { ) }, || { - let map = Map::read_with_resources( - resources, - &file[resources_bytes_read..], - &runtime_tp, - )?; + let map = + Map::read_with_resources(resources, &map_reader, &runtime_tp)?; benchmark.bench_multi("initialzing the map layers"); - let physics_group = &map.groups.physics; - let collision = Collision::new(physics_group, false)?; - - let upload_data = ClientMapBuffered::prepare_upload(&graphics_mt, map); - benchmark.bench_multi("preparing the map buffering"); - - anyhow::Ok((collision, upload_data)) + let benchmark = Benchmark::new(do_benchmark); + let physics_group = map.groups.physics.clone(); + let (collision, upload_data) = runtime_tp.join( + || { + let collision = Collision::new(physics_group, false); + benchmark.bench_multi("preparing collisions"); + collision + }, + || { + let upload_data = + ClientMapBuffered::prepare_upload(&graphics_mt, map); + benchmark.bench_multi("preparing the map buffering"); + upload_data + }, + ); + + anyhow::Ok((collision?, upload_data)) } ) }); @@ -529,7 +535,7 @@ impl RenderMapLoading { pub enum ClientMapRender { UploadingBuffersAndTextures(RenderMapLoading), - Map(ClientMapRenderAndFile), + Map(Box), None, Err(anyhow::Error), } @@ -608,7 +614,7 @@ impl ClientMapRender { benchmark.bench("creating the map buffers graphics cmds"); - *self = Self::Map(ClientMapRenderAndFile { + *self = Self::Map(Box::new(ClientMapRenderAndFile { data: ClientMapFileData { collision: map_file.collision, buffered_map: map_buffered, @@ -618,7 +624,7 @@ impl ClientMapRender { &map_upload.canvas_handle, &map_upload.stream_handle, ), - }); + })); } else { *self = Self::UploadingBuffersAndTextures(map_upload) } diff --git a/game/client-render-base/src/map/render_pipe.rs b/game/client-render-base/src/map/render_pipe.rs index fae03111..6314aacd 100644 --- a/game/client-render-base/src/map/render_pipe.rs +++ b/game/client-render-base/src/map/render_pipe.rs @@ -1,23 +1,14 @@ use std::time::Duration; +use camera::CameraInterface; use client_containers::{container::ContainerKey, entities::EntitiesContainer}; use game_config::config::ConfigMap; use game_interface::types::game::NonZeroGameTickType; use hiarc::Hiarc; use serde::{Deserialize, Serialize}; -use math::math::vector::vec2; - use super::{map_buffered::ClientMapBuffered, map_with_visual::MapVisual}; -#[derive(Debug, Hiarc)] -pub struct Camera { - pub pos: vec2, - pub zoom: f32, - pub forced_aspect_ratio: Option, - pub parallax_aware_zoom: bool, -} - #[derive(Debug, Hiarc, Serialize, Deserialize)] pub struct GameTimeInfo { pub ticks_per_second: NonZeroGameTickType, @@ -31,11 +22,7 @@ pub struct RenderPipelineBase<'a> { pub cur_time: &'a Duration, pub cur_anim_time: &'a Duration, pub include_last_anim_point: bool, - pub camera: &'a Camera, - - pub entities_container: &'a mut EntitiesContainer, - pub entities_key: Option<&'a ContainerKey>, - pub physics_group_name: &'a str, + pub camera: &'a dyn CameraInterface, pub map_sound_volume: f64, } @@ -53,10 +40,7 @@ impl<'a> RenderPipeline<'a> { cur_time: &'a Duration, cur_anim_time: &'a Duration, include_last_anim_point: bool, - camera: &'a Camera, - entities_container: &'a mut EntitiesContainer, - entities_key: Option<&'a ContainerKey>, - physics_group_name: &'a str, + camera: &'a dyn CameraInterface, map_sound_volume: f64, ) -> RenderPipeline<'a> { RenderPipeline { @@ -67,12 +51,34 @@ impl<'a> RenderPipeline<'a> { cur_anim_time, include_last_anim_point, camera, - entities_container, - entities_key, - physics_group_name, map_sound_volume, }, buffered_map, } } } + +pub struct RenderPipelinePhysics<'a> { + pub base: &'a RenderPipelineBase<'a>, + + pub entities_container: &'a mut EntitiesContainer, + pub entities_key: Option<&'a ContainerKey>, + pub physics_group_name: &'a str, +} + +impl<'a> RenderPipelinePhysics<'a> { + pub fn new( + base: &'a RenderPipelineBase<'a>, + entities_container: &'a mut EntitiesContainer, + entities_key: Option<&'a ContainerKey>, + physics_group_name: &'a str, + ) -> RenderPipelinePhysics<'a> { + RenderPipelinePhysics { + base, + + entities_container, + entities_key, + physics_group_name, + } + } +} diff --git a/game/client-render-base/src/map/render_tools.rs b/game/client-render-base/src/map/render_tools.rs index 92452a31..8d5ad920 100644 --- a/game/client-render-base/src/map/render_tools.rs +++ b/game/client-render-base/src/map/render_tools.rs @@ -2,13 +2,12 @@ use std::{fmt::Debug, ops::IndexMut}; use fixed::traits::{FromFixed, ToFixed}; use graphics::handles::{ - canvas::canvas::GraphicsCanvasHandle, stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, stream_types::StreamedQuad, texture::texture::TextureContainer, }; use hiarc::hi_closure; -use map::map::{animations::AnimPoint, groups::MapGroupAttr}; +use map::map::animations::AnimPoint; use math::math::{ vector::{ubvec4, vec2}, @@ -26,178 +25,9 @@ pub enum TileRenderFlag { Extend = 4, } -pub enum CanvasType<'a> { - Handle(&'a GraphicsCanvasHandle), - Custom { aspect_ratio: f32 }, -} - pub struct RenderTools {} impl RenderTools { - pub fn calc_canvas_params(aspect: f32, zoom: f32, width: &mut f32, height: &mut f32) { - const AMOUNT: f32 = 1150.0 / 32.0 * 1000.0 / 32.0; - const WIDTH_MAX: f32 = 1500.0 / 32.0; - const HEIGHT_MAX: f32 = 1050.0 / 32.0; - - let f = AMOUNT.sqrt() / aspect.sqrt(); - *width = f * aspect; - *height = f; - - // limit the view - if *width > WIDTH_MAX { - *width = WIDTH_MAX; - *height = *width / aspect; - } - - if *height > HEIGHT_MAX { - *height = HEIGHT_MAX; - *width = *height * aspect; - } - - *width *= zoom; - *height *= zoom; - } - - pub fn map_pos_to_group_attr( - center_x: f32, - center_y: f32, - parallax_x: f32, - parallax_y: f32, - offset_x: f32, - offset_y: f32, - ) -> vec2 { - let center_x = center_x * parallax_x / 100.0; - let center_y = center_y * parallax_y / 100.0; - vec2::new(offset_x + center_x, offset_y + center_y) - } - - pub fn map_canvas_to_world( - center_x: f32, - center_y: f32, - parallax_x: f32, - parallax_y: f32, - offset_x: f32, - offset_y: f32, - aspect: f32, - zoom: f32, - parallax_aware_zoom: bool, - ) -> [f32; 4] { - let mut width = 0.0; - let mut height = 0.0; - Self::calc_canvas_params(aspect, zoom, &mut width, &mut height); - - let parallax_zoom = if parallax_aware_zoom { - parallax_x.max(parallax_y).clamp(0.0, 100.0) - } else { - 100.0 - }; - let scale = (parallax_zoom * (zoom - 1.0) + 100.0) / 100.0 / zoom; - width *= scale; - height *= scale; - - let center = Self::map_pos_to_group_attr( - center_x, center_y, parallax_x, parallax_y, offset_x, offset_y, - ); - let mut points: [f32; 4] = [0.0; 4]; - points[0] = center.x - width / 2.0; - points[1] = center.y - height / 2.0; - points[2] = points[0] + width; - points[3] = points[1] + height; - points - } - - pub fn canvas_points_of_group_attr( - canvas: CanvasType<'_>, - center_x: f32, - center_y: f32, - parallax_x: f32, - parallax_y: f32, - offset_x: f32, - offset_y: f32, - zoom: f32, - parallax_aware_zoom: bool, - ) -> [f32; 4] { - Self::map_canvas_to_world( - center_x, - center_y, - parallax_x, - parallax_y, - offset_x, - offset_y, - match canvas { - CanvasType::Handle(canvas_handle) => canvas_handle.canvas_aspect(), - CanvasType::Custom { aspect_ratio } => aspect_ratio, - }, - zoom, - parallax_aware_zoom, - ) - } - - pub fn para_and_offset_of_group(design_group: Option<&MapGroupAttr>) -> (vec2, vec2) { - if let Some(design_group) = design_group { - ( - vec2::new( - design_group.parallax.x.to_num::(), - design_group.parallax.y.to_num::(), - ), - vec2::new( - design_group.offset.x.to_num::(), - design_group.offset.y.to_num::(), - ), - ) - } else { - (vec2::new(100.0, 100.0), vec2::default()) - } - } - - pub fn canvas_points_of_group( - canvas: CanvasType<'_>, - center_x: f32, - center_y: f32, - design_group: Option<&MapGroupAttr>, - zoom: f32, - parallax_aware_zoom: bool, - ) -> [f32; 4] { - let (parallax, offset) = Self::para_and_offset_of_group(design_group); - Self::canvas_points_of_group_attr( - canvas, - center_x, - center_y, - parallax.x, - parallax.y, - offset.x, - offset.y, - zoom, - parallax_aware_zoom, - ) - } - - pub fn pos_to_group(inp: vec2, design_group: Option<&MapGroupAttr>) -> vec2 { - let (parallax, offset) = RenderTools::para_and_offset_of_group(design_group); - - RenderTools::map_pos_to_group_attr(inp.x, inp.y, parallax.x, parallax.y, offset.x, offset.y) - } - - pub fn map_canvas_of_group( - canvas: CanvasType<'_>, - state: &mut State, - center_x: f32, - center_y: f32, - design_group: Option<&MapGroupAttr>, - zoom: f32, - parallax_aware_zoom: bool, - ) { - let points = Self::canvas_points_of_group( - canvas, - center_x, - center_y, - design_group, - zoom, - parallax_aware_zoom, - ); - state.map_canvas(points[0], points[1], points[2], points[3]); - } - pub fn render_eval_anim< F, T: Debug + Copy + Default + IndexMut, @@ -257,7 +87,7 @@ impl RenderTools { color: &ubvec4, state: State, ) { - stream_handle.render_quads( + stream_handle.stream_quads( hi_closure!([ pos: &vec2, radius: f32, @@ -308,33 +138,15 @@ impl RenderTools { texture: Option<&TextureContainer>, ) { stream_handle.render_quads( - hi_closure!([ - center: &vec2, - size: &vec2, - color: &ubvec4, - texture: Option<&'a TextureContainer> - ], |mut stream_handle: QuadStreamHandle<'_>| -> () { - if let Some(texture) = texture { - stream_handle.set_texture(texture); - } - stream_handle - .add_vertices( - StreamedQuad::default() - .from_pos_and_size( - vec2::new( - center.x - size.x / 2.0, - center.y - size.y / 2.0 - ), - *size - ) - .color( - *color - ) - .tex_default() - .into() - ); - }), + &[StreamedQuad::default() + .from_pos_and_size( + vec2::new(center.x - size.x / 2.0, center.y - size.y / 2.0), + *size, + ) + .color(*color) + .tex_default()], state, + texture.into(), ); } @@ -344,21 +156,6 @@ impl RenderTools { state: State, texture: Option<&TextureContainer>, ) { - let quad = &quad; - stream_handle.render_quads( - hi_closure!([ - quad: &StreamedQuad, - texture: Option<&'a TextureContainer> - ], |mut stream_handle: QuadStreamHandle<'_>| -> () { - if let Some(texture) = texture { - stream_handle.set_texture(texture); - } - stream_handle - .add_vertices( - (*quad).into() - ); - }), - state, - ); + stream_handle.render_quads(&[quad], state, texture.into()); } } diff --git a/game/client-render-base/src/render/canvas_mapping.rs b/game/client-render-base/src/render/canvas_mapping.rs index c43ab48c..a66c4f49 100644 --- a/game/client-render-base/src/render/canvas_mapping.rs +++ b/game/client-render-base/src/render/canvas_mapping.rs @@ -1,9 +1,8 @@ +use camera::CameraInterface; use graphics::{graphics::graphics::Graphics, handles::canvas::canvas::GraphicsCanvasHandle}; use graphics_types::rendering::State; use hiarc::Hiarc; -use crate::map::render_tools::RenderTools; - #[derive(Debug, Hiarc)] pub struct CanvasMappingIngame { canvas_handle: GraphicsCanvasHandle, @@ -22,25 +21,7 @@ impl CanvasMappingIngame { } } - pub fn map_canvas_for_ingame_items( - &self, - state: &mut State, - center_x: f32, - center_y: f32, - zoom: f32, - forced_aspect_ratio: Option, - ) { - let points: [f32; 4] = RenderTools::map_canvas_to_world( - 0.0, - 0.0, - 100.0, - 100.0, - center_x, - center_y, - forced_aspect_ratio.unwrap_or(self.canvas_handle.canvas_aspect()), - zoom, - true, - ); - state.map_canvas(points[0], points[1], points[2], points[3]); + pub fn map_canvas_for_ingame_items(&self, state: &mut State, camera: &dyn CameraInterface) { + camera.project(&self.canvas_handle, state, None); } } diff --git a/game/client-render-base/src/render/default_anim.rs b/game/client-render-base/src/render/default_anim.rs index 396059af..77791745 100644 --- a/game/client-render-base/src/render/default_anim.rs +++ b/game/client-render-base/src/render/default_anim.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use math::math::vector::vec2; +use math::math::{vector::vec2, PI}; use super::animation::{TeeAnimation, TeeAnimationFrame, TeeAnimationFrames}; @@ -76,7 +76,7 @@ pub fn inair_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(-3.0 / 64.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -84,7 +84,7 @@ pub fn inair_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(3.0 / 64.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -105,7 +105,7 @@ pub fn sit_left_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(-6.0 / 32.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -113,7 +113,7 @@ pub fn sit_left_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(-4.0 / 32.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -134,7 +134,7 @@ pub fn sit_right_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(6.0 / 32.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -142,7 +142,7 @@ pub fn sit_right_anim() -> TeeAnimation { Duration::ZERO, TeeAnimationFrame { pos: vec2::new(4.0 / 32.0, 0.0), - rotation: -0.1, + rotation: -0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -222,7 +222,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(400), TeeAnimationFrame { pos: vec2::new(-5.0 / 32.0, -2.0 / 32.0), - rotation: 0.2, + rotation: 0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -230,7 +230,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(600), TeeAnimationFrame { pos: vec2::new(-4.0 / 32.0, -4.0 / 32.0), - rotation: 0.3, + rotation: 0.3 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -238,7 +238,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(800), TeeAnimationFrame { pos: vec2::new(2.0 / 32.0, -2.0 / 32.0), - rotation: -0.2, + rotation: -0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -256,7 +256,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(0), TeeAnimationFrame { pos: vec2::new(-5.0 / 32.0, -2.0 / 32.0), - rotation: 0.2, + rotation: 0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -264,7 +264,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(200), TeeAnimationFrame { pos: vec2::new(-4.0 / 32.0, -4.0 / 32.0), - rotation: 0.3, + rotation: 0.3 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -272,7 +272,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(400), TeeAnimationFrame { pos: vec2::new(2.0 / 32.0, -2.0 / 32.0), - rotation: -0.2, + rotation: -0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -296,7 +296,7 @@ pub fn walk_anim() -> TeeAnimation { Duration::from_millis(1000), TeeAnimationFrame { pos: vec2::new(-5.0 / 32.0, -2.0 / 32.0), - rotation: 0.2, + rotation: 0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -360,7 +360,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(0), TeeAnimationFrame { pos: vec2::new(9.0 / 32.0, -4.0 / 32.0), - rotation: -0.27, + rotation: -0.27 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -384,7 +384,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(600), TeeAnimationFrame { pos: vec2::new(-13.0 / 64.0, -4.5 / 64.0), - rotation: 0.05, + rotation: 0.05 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -392,7 +392,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(800), TeeAnimationFrame { pos: vec2::new(0.0, -4.0 / 32.0), - rotation: -0.2, + rotation: -0.2 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -400,7 +400,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(1000), TeeAnimationFrame { pos: vec2::new(9.0 / 32.0, -4.0 / 32.0), - rotation: -0.27, + rotation: -0.27 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -410,7 +410,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(0), TeeAnimationFrame { pos: vec2::new(-11.0 / 64.0, -2.5 / 64.0), - rotation: 0.05, + rotation: 0.05 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -418,7 +418,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(200), TeeAnimationFrame { pos: vec2::new(-7.0 / 32.0, -5.0 / 64.0), - rotation: 0.1, + rotation: 0.1 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -426,7 +426,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(400), TeeAnimationFrame { pos: vec2::new(11.0 / 64.0, -4.0 / 32.0), - rotation: -0.3, + rotation: -0.3 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -434,7 +434,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(600), TeeAnimationFrame { pos: vec2::new(9.0 / 32.0, -4.0 / 32.0), - rotation: -0.27, + rotation: -0.27 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -450,7 +450,7 @@ pub fn run_left_anim() -> TeeAnimation { Duration::from_millis(1000), TeeAnimationFrame { pos: vec2::new(-11.0 / 64.0, -2.5 / 64.0), - rotation: 0.05, + rotation: 0.05 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -498,7 +498,7 @@ pub fn hammer_swing_anim() -> TeeAnimationFrames { Duration::from_millis(0), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: -0.10, + rotation: -0.10 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -506,7 +506,7 @@ pub fn hammer_swing_anim() -> TeeAnimationFrames { Duration::from_millis(300), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.25, + rotation: 0.25 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -514,7 +514,7 @@ pub fn hammer_swing_anim() -> TeeAnimationFrames { Duration::from_millis(400), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.30, + rotation: 0.30 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -522,7 +522,7 @@ pub fn hammer_swing_anim() -> TeeAnimationFrames { Duration::from_millis(500), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.25, + rotation: 0.25 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -530,7 +530,7 @@ pub fn hammer_swing_anim() -> TeeAnimationFrames { Duration::from_millis(1000), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: -0.10, + rotation: -0.10 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -543,7 +543,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(0), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: -0.25, + rotation: -0.25 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -551,7 +551,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(100), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: -0.05, + rotation: -0.05 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -559,7 +559,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(150), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.35, + rotation: 0.35 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -567,7 +567,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(420), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.40, + rotation: 0.40 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -575,7 +575,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(500), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: 0.35, + rotation: 0.35 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); @@ -583,7 +583,7 @@ pub fn ninja_swing_anim() -> TeeAnimationFrames { Duration::from_millis(1000), TeeAnimationFrame { pos: vec2::new(0.0, 0.0), - rotation: -0.25, + rotation: -0.25 * PI * 2.0, scale: vec2::new(1.0, 1.0), }, )); diff --git a/game/client-render-base/src/render/particle_manager.rs b/game/client-render-base/src/render/particle_manager.rs index d905c7cb..b85a3e4c 100644 --- a/game/client-render-base/src/render/particle_manager.rs +++ b/game/client-render-base/src/render/particle_manager.rs @@ -2,8 +2,9 @@ use std::{cell::RefCell, collections::VecDeque, sync::Arc, time::Duration}; -use crate::{map::render_pipe::Camera, render::canvas_mapping::CanvasMappingIngame}; +use crate::render::canvas_mapping::CanvasMappingIngame; use base::linked_hash_map_view::FxLinkedHashMap; +use camera::CameraInterface; use client_containers::{ container::ContainerKey, particles::{ParticleType, ParticlesContainer}, @@ -243,18 +244,12 @@ impl ParticleManager { group: ParticleGroup, particle_container: &mut ParticlesContainer, character_infos: &FxLinkedHashMap, - camera: &Camera, + camera: &dyn CameraInterface, ) { if !self.particle_groups[group as usize].is_empty() { let mut state = State::new(); - let center = camera.pos; - self.canvas_mapping.map_canvas_for_ingame_items( - &mut state, - center.x, - center.y, - camera.zoom, - camera.forced_aspect_ratio, - ); + self.canvas_mapping + .map_canvas_for_ingame_items(&mut state, camera); let p = &self.particle_groups[group as usize][0]; let mut alpha = p.color.a; @@ -376,7 +371,7 @@ impl ParticleManager { start_group: ParticleGroup, particle_container: &mut ParticlesContainer, character_infos: &FxLinkedHashMap, - camera: &Camera, + camera: &dyn CameraInterface, ) { for i in start_group as usize..ParticleGroup::Count as usize { self.render_group( diff --git a/game/client-render-base/src/render/tee.rs b/game/client-render-base/src/render/tee.rs index 46fe7865..f158823b 100644 --- a/game/client-render-base/src/render/tee.rs +++ b/game/client-render-base/src/render/tee.rs @@ -1,8 +1,10 @@ use client_containers::skins::{Skin, SkinMetrics, SkinTextures}; use game_interface::types::render::character::TeeEye; use graphics::{ - graphics::graphics::Graphics, handles::quad_container::quad_container::QuadContainer, - quad_container::Quad, streaming::quad_scope_begin, + graphics::graphics::Graphics, + handles::quad_container::quad_container::QuadContainer, + quad_container::Quad, + streaming::{quad_scope_begin, rotate_pos}, }; use graphics_types::rendering::{ColorRgba, State}; @@ -10,7 +12,6 @@ use hiarc::{hiarc_safer_rc_refcell, Hiarc}; use math::math::{ angle, vector::{ubvec4, vec2}, - PI, }; use super::animation::AnimState; @@ -246,15 +247,19 @@ impl RenderTeeMath { y: -0.05 + direction.y * 0.10, } * render_size; - let eye_pos_left = vec2 { - x: body_pos.x - eye_separation + offset.x, - y: body_pos.y + offset.y, - }; + let eye_left = rotate_pos( + &Default::default(), + body_rotation, + vec2::new(-eye_separation + offset.x, offset.y), + ); + let eye_pos_left = body_pos + eye_left; - let eye_pos_right = vec2 { - x: body_pos.x + eye_separation + offset.x, - y: body_pos.y + offset.y, - }; + let eye_right = rotate_pos( + &Default::default(), + body_rotation, + vec2::new(eye_separation + offset.x, offset.y), + ); + let eye_pos_right = body_pos + eye_right; let eye_scale_left = vec2 { x: eye_scale / 0.4 * anim.left_eye.scale.x, @@ -266,8 +271,8 @@ impl RenderTeeMath { y: eye_right_height / 0.4 * anim.right_eye.scale.y, }; - let eye_rotation_left = anim.left_eye.rotation; - let eye_rotation_right = anim.right_eye.rotation; + let eye_rotation_left = anim.left_eye.rotation + body_rotation; + let eye_rotation_right = anim.right_eye.rotation - body_rotation; // feet let feet_width = render_size; @@ -403,7 +408,7 @@ impl RenderTee { ) { let mut quad_scope = quad_scope_begin(); quad_scope.set_state(state); - quad_scope.set_rotation(body_rotation * PI * 2.0); + quad_scope.set_rotation(body_rotation); let render_skin = skin.render_skin(body_color); let body_color = body_color.unwrap(alpha); @@ -450,7 +455,7 @@ impl RenderTee { let mut quad_scope = quad_scope_begin(); quad_scope.set_state(state); - quad_scope.set_rotation(eye_left_rotation * PI * 2.0); + quad_scope.set_rotation(eye_left_rotation); let render_skin = skin.render_skin(eye_left_color); let eye_left_color = eye_left_color.unwrap(alpha); let texture = &render_skin.left_eyes[tee_eye_index]; @@ -472,7 +477,7 @@ impl RenderTee { let tee_eye_index = eye_right as usize - TeeEye::Normal as usize; let mut quad_scope = quad_scope_begin(); quad_scope.set_state(state); - quad_scope.set_rotation(eye_right_rotation * PI * 2.0); + quad_scope.set_rotation(eye_right_rotation); let render_skin = skin.render_skin(eye_right_color); let eye_right_color = eye_right_color.unwrap(alpha); let texture = &render_skin.right_eyes[tee_eye_index]; @@ -533,7 +538,7 @@ impl RenderTee { let mut quad_scope = quad_scope_begin(); quad_scope.set_state(state); - quad_scope.set_rotation(foot_rotation * PI * 2.0); + quad_scope.set_rotation(foot_rotation); let indicate = !got_air_jump; let mut color_scale = 1.0; diff --git a/game/client-render-base/src/render/toolkit.rs b/game/client-render-base/src/render/toolkit.rs index 60a5ba1c..f92f9ecd 100644 --- a/game/client-render-base/src/render/toolkit.rs +++ b/game/client-render-base/src/render/toolkit.rs @@ -18,7 +18,7 @@ use graphics::{ graphics::graphics::Graphics, handles::{ quad_container::quad_container::QuadContainer, - stream::stream::{GraphicsStreamHandle, LinesStreamHandle, StreamedSprites}, + stream::stream::{GraphicsStreamHandle, StreamedSprites}, stream_types::StreamedLine, }, quad_container::Quad, @@ -224,25 +224,16 @@ impl ToolkitRender { hook_collision: &HookCollisionLine, base_state: State, ) { - self.stream_handle.render_lines( - hi_closure!( - [hook_collision: &HookCollisionLine], - |mut stream_handle: LinesStreamHandle<'_>| -> () { - let mut line = StreamedLine::new().with_color(match hook_collision.color { - HookCollisionLineColor::Nothing => ubvec4::new(255, 0, 0, 255), - HookCollisionLineColor::Player => ubvec4::new(255, 255, 0, 255), - HookCollisionLineColor::Hookable => ubvec4::new(100, 255, 100, 255), - HookCollisionLineColor::Unhookable => ubvec4::new(255, 0, 0, 255), - HookCollisionLineColor::Custom(color) => color, - }); - line = line.from_pos( - [hook_collision.start, hook_collision.end] - ); - stream_handle.add_vertices(line.into()); - } - ), - base_state, - ); + let line = StreamedLine::new() + .with_color(match hook_collision.color { + HookCollisionLineColor::Nothing => ubvec4::new(255, 0, 0, 255), + HookCollisionLineColor::Player => ubvec4::new(255, 255, 0, 255), + HookCollisionLineColor::Hookable => ubvec4::new(100, 255, 100, 255), + HookCollisionLineColor::Unhookable => ubvec4::new(255, 0, 0, 255), + HookCollisionLineColor::Custom(color) => color, + }) + .from_pos([hook_collision.start, hook_collision.end]); + self.stream_handle.render_lines(&[line], base_state); } pub fn render_hook( @@ -445,9 +436,9 @@ impl ToolkitRender { &mut frame, ); if cursor_dir.x < 0.0 { - quad_scope.set_rotation(-PI / 2.0 - frame.rotation * PI * 2.0); + quad_scope.set_rotation(-PI / 2.0 - frame.rotation); } else { - quad_scope.set_rotation(-PI / 2.0 + frame.rotation * PI * 2.0); + quad_scope.set_rotation(-PI / 2.0 + frame.rotation); } } else { quad_scope.set_rotation(if cursor_dir.x < 0.0 { 100.0 } else { 500.0 }); @@ -602,7 +593,7 @@ impl ToolkitRender { quad_scope.set_colors_from_single(1.0, 1.0, 1.0, phased_alpha); let quad_offset = if character_render_info.lerped_cursor_pos.x < 0.0 { - quad_scope.set_rotation(-PI / 2.0 - frame.rotation * PI * 2.0); + quad_scope.set_rotation(-PI / 2.0 - frame.rotation); weapon_pos.x -= spec.offset_x; Effects::new(particle_manager, cur_time).powerup_shine( &(weapon_pos + vec2::new(1.0, 0.0)), @@ -611,7 +602,7 @@ impl ToolkitRender { ); self.ninja_quad_offsets.0 } else { - quad_scope.set_rotation(-PI / 2.0 + frame.rotation * PI * 2.0); + quad_scope.set_rotation(-PI / 2.0 + frame.rotation); Effects::new(particle_manager, cur_time).powerup_shine( &(weapon_pos - vec2::new(1.0, 0.0)), &vec2::new(1.0, 12.0 / 32.0), diff --git a/game/client-render-game/Cargo.toml b/game/client-render-game/Cargo.toml index f4e98bbc..533eb483 100644 --- a/game/client-render-game/Cargo.toml +++ b/game/client-render-game/Cargo.toml @@ -11,7 +11,6 @@ math = { path = "../../lib/math" } config = { path = "../../lib/config" } graphics = { path = "../../lib/graphics" } graphics-types = { path = "../../lib/graphics-types" } -hiarc = { path = "../../lib/hiarc", features = ["derive"] } pool = { path = "../../lib/pool" } sound = { path = "../../lib/sound" } @@ -25,6 +24,7 @@ game-base = { path = "../game-base" } vanilla = { path = "../vanilla" } game-interface = { path = "../game-interface" } map = { path = "../map" } +camera = { path = "../camera" } num-traits = "0.2.19" rayon = "1.10.0" diff --git a/game/client-render-game/src/components/cursor.rs b/game/client-render-game/src/components/cursor.rs index 908c24e0..86d31a11 100644 --- a/game/client-render-game/src/components/cursor.rs +++ b/game/client-render-game/src/components/cursor.rs @@ -1,7 +1,7 @@ +use camera::Camera; use client_containers::{container::ContainerKey, ninja::NinjaContainer, weapons::WeaponContainer}; -use client_render_base::{ - map::render_pipe::Camera, - render::{canvas_mapping::CanvasMappingIngame, toolkit::get_sprite_scale_impl}, +use client_render_base::render::{ + canvas_mapping::CanvasMappingIngame, toolkit::get_sprite_scale_impl, }; use game_interface::types::weapons::WeaponType; use graphics::{ @@ -46,13 +46,14 @@ impl RenderCursor { pub fn render(&self, pipe: &mut RenderCursorPipe) { let mut state = State::default(); - self.canvas_mapping.map_canvas_for_ingame_items( - &mut state, - 0.0, - 0.0, + let camera = Camera::new( + Default::default(), 1.0, pipe.camera.forced_aspect_ratio, + pipe.camera.parallax_aware_zoom, ); + self.canvas_mapping + .map_canvas_for_ingame_items(&mut state, &camera); let mut draw_scope = quad_scope_begin(); draw_scope.set_state(&state); diff --git a/game/client-render-game/src/components/game_objects.rs b/game/client-render-game/src/components/game_objects.rs index 59d37371..18e34de2 100644 --- a/game/client-render-game/src/components/game_objects.rs +++ b/game/client-render-game/src/components/game_objects.rs @@ -1,11 +1,12 @@ use std::time::Duration; use base::linked_hash_map_view::FxLinkedHashMap; +use camera::CameraInterface; use client_containers::{ ctf::CtfContainer, game::GameContainer, ninja::NinjaContainer, weapons::WeaponContainer, }; use client_render_base::{ - map::render_pipe::{Camera, GameTimeInfo}, + map::render_pipe::GameTimeInfo, render::{ canvas_mapping::CanvasMappingIngame, effects::Effects, @@ -32,15 +33,13 @@ use game_interface::types::{ use graphics::{ graphics::graphics::Graphics, handles::{ - quad_container::quad_container::QuadContainer, - stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, - stream_types::StreamedQuad, + quad_container::quad_container::QuadContainer, stream::stream::GraphicsStreamHandle, + stream_types::StreamedQuad, texture::texture::TextureType, }, quad_container::Quad, streaming::quad_scope_begin, }; use graphics_types::rendering::{ColorRgba, State}; -use hiarc::hi_closure; use math::math::{ angle, distance, length, normalize_pre_length, vector::{ubvec4, vec2, vec4}, @@ -81,7 +80,7 @@ pub struct GameObjectsRenderPipe<'a> { pub local_character_id: Option<&'a CharacterId>, - pub camera: &'a Camera, + pub camera: &'a dyn CameraInterface, pub phased_alpha: f32, pub phased: bool, } @@ -160,14 +159,8 @@ impl GameObjectsRender { pub fn render(&mut self, pipe: &mut GameObjectsRenderPipe) { let mut base_state = State::default(); - let center = pipe.camera.pos; - self.canvas_mapping.map_canvas_for_ingame_items( - &mut base_state, - center.x, - center.y, - pipe.camera.zoom, - pipe.camera.forced_aspect_ratio, - ); + self.canvas_mapping + .map_canvas_for_ingame_items(&mut base_state, pipe.camera); pipe.projectiles.values().for_each(|proj| { self.render_projectile(pipe, proj, pipe.character_infos, &base_state); @@ -487,47 +480,38 @@ impl GameObjectsRender { let ia = 1.0 - a; // do outline - self.stream_handle.render_quads( - hi_closure!([from: vec2, pos: vec2, dir: vec2, outer_color: ColorRgba, inner_color: ColorRgba, phased_alpha: f32, ia: f32], |mut stream_handle: QuadStreamHandle<'_>| -> () { - let out = vec2::new(dir.y, -dir.x) * (7.0 / 32.0 * ia); - stream_handle.add_vertices( - StreamedQuad::default() - .pos_free_form( - vec2::new(from.x - out.x, from.y - out.y), - vec2::new(from.x + out.x, from.y + out.y), - vec2::new(pos.x - out.x, pos.y - out.y), - vec2::new(pos.x + out.x, pos.y + out.y), - ) - .colorf(vec4::new( - outer_color.r, - outer_color.g, - outer_color.b, - outer_color.a * phased_alpha, - )) - .into(), - ); - - // do inner - let out = vec2::new(dir.y, -dir.x) * (5.0 / 32.0 * ia); - stream_handle.add_vertices( - StreamedQuad::default() - .pos_free_form( - vec2::new(from.x - out.x, from.y - out.y), - vec2::new(from.x + out.x, from.y + out.y), - vec2::new(pos.x - out.x, pos.y - out.y), - vec2::new(pos.x + out.x, pos.y + out.y), - ) - .colorf(vec4::new( - inner_color.r, - inner_color.g, - inner_color.b, - inner_color.a * phased_alpha, - )) - .into(), - ); - }), - *base_state, - ); + let out = vec2::new(dir.y, -dir.x) * (7.0 / 32.0 * ia); + let outter = StreamedQuad::default() + .pos_free_form( + vec2::new(from.x - out.x, from.y - out.y), + vec2::new(from.x + out.x, from.y + out.y), + vec2::new(pos.x - out.x, pos.y - out.y), + vec2::new(pos.x + out.x, pos.y + out.y), + ) + .colorf(vec4::new( + outer_color.r, + outer_color.g, + outer_color.b, + outer_color.a * phased_alpha, + )); + + // do inner + let out = vec2::new(dir.y, -dir.x) * (5.0 / 32.0 * ia); + let inner = StreamedQuad::default() + .pos_free_form( + vec2::new(from.x - out.x, from.y - out.y), + vec2::new(from.x + out.x, from.y + out.y), + vec2::new(pos.x - out.x, pos.y - out.y), + vec2::new(pos.x + out.x, pos.y + out.y), + ) + .colorf(vec4::new( + inner_color.r, + inner_color.g, + inner_color.b, + inner_color.a * phased_alpha, + )); + self.stream_handle + .render_quads(&[outter, inner], *base_state, TextureType::None); } // render head diff --git a/game/client-render-game/src/components/players.rs b/game/client-render-game/src/components/players.rs index 56be446f..6287a22d 100644 --- a/game/client-render-game/src/components/players.rs +++ b/game/client-render-game/src/components/players.rs @@ -1,6 +1,7 @@ use std::{borrow::Borrow, time::Duration}; use base::linked_hash_map_view::FxLinkedHashMap; +use camera::CameraInterface; use client_containers::{ emoticons::EmoticonsContainer, freezes::FreezeContainer, @@ -14,7 +15,7 @@ use client_render::{ nameplates::render::{NameplatePlayer, NameplateRender, NameplateRenderPipe}, }; use client_render_base::{ - map::render_pipe::{Camera, GameTimeInfo}, + map::render_pipe::GameTimeInfo, render::{ animation::AnimState, canvas_mapping::CanvasMappingIngame, @@ -60,7 +61,7 @@ pub struct PlayerRenderPipe<'a> { pub particle_manager: &'a mut ParticleManager, pub collision: &'a Collision, - pub camera: &'a Camera, + pub camera: &'a dyn CameraInterface, pub spatial_sound: bool, pub sound_playback_speed: f64, @@ -101,16 +102,10 @@ impl Players { } } - fn base_state(&self, camera: &Camera) -> State { + fn base_state(&self, camera: &dyn CameraInterface) -> State { let mut base_state = State::default(); - let center = camera.pos; - self.canvas_mapping.map_canvas_for_ingame_items( - &mut base_state, - center.x, - center.y, - camera.zoom, - camera.forced_aspect_ratio, - ); + self.canvas_mapping + .map_canvas_for_ingame_items(&mut base_state, camera); base_state } @@ -159,7 +154,7 @@ impl Players { let phased_alpha = *phased_alpha; let phased = *phased; - let state = self.base_state(camera); + let state = self.base_state(*camera); const RENDER_TEE_SIZE: f32 = 2.0; @@ -469,7 +464,7 @@ impl Players { pub fn render_nameplates( &mut self, cur_time: &Duration, - camera: &Camera, + camera: &dyn CameraInterface, render_infos: &PoolFxLinkedHashMap, character_infos: &PoolFxLinkedHashMap, nameplates: bool, @@ -482,7 +477,7 @@ impl Players { self.nameplate_renderer.render(&mut NameplateRenderPipe { cur_time, state: &state, - camera_zoom: camera.zoom, + camera_zoom: camera.zoom(), players: &mut Self::render_info_iter(render_infos, &own_character).filter_map( |(character_id, player_render_info)| { let pos = &player_render_info.lerped_pos; diff --git a/game/client-render-game/src/render_game.rs b/game/client-render-game/src/render_game.rs index 2992ca34..5c36d371 100644 --- a/game/client-render-game/src/render_game.rs +++ b/game/client-render-game/src/render_game.rs @@ -17,6 +17,7 @@ use base::{ reduced_ascii_str::ReducedAsciiString, }; use base_io::io::Io; +use camera::Camera; use client_containers::utils::{load_containers, RenderGameContainers}; pub use client_render::emote_wheel::render::EmoteWheelInput; use client_render::{ @@ -32,7 +33,7 @@ use client_render_base::{ map::{ map::RenderMap, render_map_base::{ClientMapRender, RenderMapLoading}, - render_pipe::{Camera, GameTimeInfo, RenderPipeline}, + render_pipe::{GameTimeInfo, RenderPipeline, RenderPipelinePhysics}, }, render::{ effects::Effects, @@ -582,12 +583,12 @@ impl RenderGame { ) { let map = self.map.try_get().unwrap(); - let mut cam = Camera { - pos: Default::default(), - zoom: 1.0, - forced_aspect_ratio: render_info.settings.ingame_aspect, - parallax_aware_zoom: true, - }; + let mut cam = Camera::new( + Default::default(), + 1.0, + render_info.settings.ingame_aspect, + true, + ); let camera_player = player_info.and_then(|(player_id, p)| match &p.cam_mode { RenderPlayerCameraMode::Default | RenderPlayerCameraMode::AtPos { .. } => Some(( @@ -681,7 +682,7 @@ impl RenderGame { let render_map = map; // map + ingame objects - let mut render_pipe = RenderPipeline::new( + let render_pipe = RenderPipeline::new( &render_map.data.buffered_map.map_visual, &render_map.data.buffered_map, config_map, @@ -689,12 +690,9 @@ impl RenderGame { &cur_anim_time, false, &cam, - &mut self.containers.entities_container, - camera_character_info.map(|c| c.info.entities.borrow()), - self.physics_group_name.as_str(), render_info.settings.map_sound_volume, ); - render_map.render.render_background(&mut render_pipe); + render_map.render.render_background(&render_pipe); self.particles.render_group( ParticleGroup::ProjectileTrail, &mut self.containers.particles_container, @@ -767,7 +765,7 @@ impl RenderGame { phased: !local_characters_stage && !forced_non_phased_rendering, }); } - let mut render_pipe = RenderPipeline::new( + let render_pipe = RenderPipeline::new( &render_map.data.buffered_map.map_visual, &render_map.data.buffered_map, config_map, @@ -775,16 +773,18 @@ impl RenderGame { &cur_anim_time, false, &cam, - &mut self.containers.entities_container, - camera_character_info.map(|c| c.info.entities.borrow()), - self.physics_group_name.as_str(), render_info.settings.map_sound_volume, ); render_map.render.render_physics_layers( - &mut render_pipe.base, + &mut RenderPipelinePhysics::new( + &render_pipe.base, + &mut self.containers.entities_container, + camera_character_info.map(|c| c.info.entities.borrow()), + self.physics_group_name.as_str(), + ), &render_map.data.buffered_map.render.physics_render_layers, ); - render_map.render.render_foreground(&mut render_pipe); + render_map.render.render_foreground(&render_pipe); for (stage_id, stage) in render_info.stages.iter() { let local_characters_stage = camera_character_info diff --git a/game/client-ui/src/connect/main_frame.rs b/game/client-ui/src/connect/main_frame.rs index e09c4d18..f1e6d053 100644 --- a/game/client-ui/src/connect/main_frame.rs +++ b/game/client-ui/src/connect/main_frame.rs @@ -18,7 +18,7 @@ pub fn render_modes(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { match mode { ConnectModes::Connecting { addr } => { ui.vertical(|ui| { - ui.label(format!("Connecting to:\n{}", addr)); + ui.label(format!("Connecting to:\n{addr}")); if ui.button("Cancel").clicked() { pipe.user_data.events.push(UiEvent::Disconnect); pipe.user_data.config.engine.ui.path.route(""); @@ -28,9 +28,8 @@ pub fn render_modes(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { ConnectModes::ConnectingErr { msg } => { ui.vertical(|ui| { ui.label(format!( - "Connecting to {} failed:\n{}", - pipe.user_data.config.storage::("server-addr"), - msg + "Connecting to {} failed:\n{msg}", + pipe.user_data.config.storage::("server-addr") )); if ui.button("Return").clicked() { pipe.user_data.events.push(UiEvent::Disconnect); @@ -44,7 +43,7 @@ pub fn render_modes(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { "Connecting to {}", pipe.user_data.config.storage::("server-addr") )); - ui.label(format!("Waiting in queue: {}", msg)); + ui.label(format!("Waiting in queue: {msg}")); if ui.button("Cancel").clicked() { pipe.user_data.events.push(UiEvent::Disconnect); pipe.user_data.config.engine.ui.path.route(""); @@ -54,9 +53,8 @@ pub fn render_modes(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { ConnectModes::DisconnectErr { msg } => { ui.vertical(|ui| { ui.label(format!( - "Connection to {} lost:\n{}", - pipe.user_data.config.storage::("server-addr"), - msg + "Connection to {} lost:\n{msg}", + pipe.user_data.config.storage::("server-addr") )); if ui.button("Return").clicked() { pipe.user_data.events.push(UiEvent::Disconnect); diff --git a/game/client-ui/src/console/utils.rs b/game/client-ui/src/console/utils.rs index af5e9bf4..f580553e 100644 --- a/game/client-ui/src/console/utils.rs +++ b/game/client-ui/src/console/utils.rs @@ -477,7 +477,7 @@ pub fn try_apply_config_val( let mut msgs: String = Default::default(); match err { ConfigFromStrErr::PathErr(_) => { - msgs.push_str(&format!("Parsing error: {}\n", err,)); + msgs.push_str(&format!("Parsing error: {err}\n",)); } ConfigFromStrErr::FatalErr(_) => { was_fatal = true; @@ -485,13 +485,13 @@ pub fn try_apply_config_val( } match err_game { ConfigFromStrErr::PathErr(_) => { - msgs.push_str(&format!("Parsing error: {}\n", err_game,)); + msgs.push_str(&format!("Parsing error: {err_game}\n",)); was_fatal = false; } ConfigFromStrErr::FatalErr(_) => {} } if was_fatal { - msgs.push_str(&format!("Parsing errors: {}, {}\n", err, err_game,)); + msgs.push_str(&format!("Parsing errors: {err}, {err_game}\n",)); } msgs }) @@ -529,7 +529,7 @@ pub fn run_command( let cmd = cmd.unwrap_full_or_partial_cmd_ref(); match (entry_cmd.cmd)(config_engine, config_game, &cmd.cmd_text, &cmd.args) { Ok(msg) => Ok(msg), - Err(err) => Err(format!("Parsing error: {}\n", err)), + Err(err) => Err(format!("Parsing error: {err}\n")), } } else { let Some((args, cmd_text)) = (match cmd { @@ -542,7 +542,7 @@ pub fn run_command( } } }) else { - return Err(format!("Invalid argument: {:?}", cmd)); + return Err(format!("Invalid argument: {cmd:?}")); }; let set_val = syn_vec_to_config_val(&args); @@ -564,15 +564,15 @@ pub fn run_command( { (var.on_set)(cmd_text); } - Ok(format!("Updated value for \"{}\": {}\n", cmd_text, cur_val)) + Ok(format!("Updated value for \"{cmd_text}\": {cur_val}\n")) } else { - Ok(format!("Current value for \"{}\": {}\n", cmd_text, cur_val)) + Ok(format!("Current value for \"{cmd_text}\": {cur_val}\n")) } } Err(err) => Err(err), } } else { - Err(format!("Failed to apply command: {:?}", cmd)) + Err(format!("Failed to apply command: {cmd:?}")) } } } diff --git a/game/client-ui/src/emote_wheel/main_frame.rs b/game/client-ui/src/emote_wheel/main_frame.rs index 7fa38a34..f5c2c040 100644 --- a/game/client-ui/src/emote_wheel/main_frame.rs +++ b/game/client-ui/src/emote_wheel/main_frame.rs @@ -167,7 +167,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m let start_rot = |pos: &mut vec2| { rotate( &vec2::default(), - -1.0 * 3.0 / TeeEye::COUNT as f32 * PI, + -3.0 / TeeEye::COUNT as f32 * PI, std::slice::from_mut(pos), ) }; diff --git a/game/client-ui/src/hud/main_frame.rs b/game/client-ui/src/hud/main_frame.rs index 91ff24d8..b009cb88 100644 --- a/game/client-ui/src/hud/main_frame.rs +++ b/game/client-ui/src/hud/main_frame.rs @@ -245,7 +245,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m |ui| { ui.colored_label( Color32::WHITE, - format!("{}", score), + format!("{score}"), ); }, ); @@ -320,7 +320,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m .show(ui, |ui| { ui.colored_label( Color32::WHITE, - format!("{}", score_red), + format!("{score_red}"), ); }); }); @@ -335,7 +335,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m .show(ui, |ui| { ui.colored_label( Color32::WHITE, - format!("{}", score_blue), + format!("{score_blue}"), ); }); }); diff --git a/game/client-ui/src/main_menu/content/browser/list/server_list/frame.rs b/game/client-ui/src/main_menu/content/browser/list/server_list/frame.rs index 7a6a963a..cea0061c 100644 --- a/game/client-ui/src/main_menu/content/browser/list/server_list/frame.rs +++ b/game/client-ui/src/main_menu/content/browser/list/server_list/frame.rs @@ -40,15 +40,11 @@ pub fn render(mut body: TableBody<'_>, pipe: &mut UiRenderPipe, cur_pa let server_info = &pipe.user_data.server_info; let (sock_addr, rcon_secret, server_cert_hash, server_browser_info, starting) = match &*server_info.state.lock().unwrap() { - LocalServerState::Ready { - connect_info, - browser_info, - .. - } => ( - Some(connect_info.sock_addr), - Some(connect_info.rcon_secret), - Some(connect_info.server_cert_hash), - browser_info.clone(), + LocalServerState::Ready(ready) => ( + Some(ready.connect_info.sock_addr), + Some(ready.connect_info.rcon_secret), + Some(ready.connect_info.server_cert_hash), + ready.browser_info.clone(), false, ), LocalServerState::Starting { .. } => (None, None, None, None, true), diff --git a/game/client-ui/src/main_menu/content/browser/search.rs b/game/client-ui/src/main_menu/content/browser/search.rs index 4a07f391..508aafc1 100644 --- a/game/client-ui/src/main_menu/content/browser/search.rs +++ b/game/client-ui/src/main_menu/content/browser/search.rs @@ -1,3 +1,4 @@ +use egui::{Key, KeyboardShortcut, Modifiers}; use egui_extras::{Size, StripBuilder}; use game_base::server_browser::ServerFilter; @@ -35,10 +36,15 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { .user_data .config .storage::("browser_filter"); - if clearable_edit_field(ui, &mut filter.search, None, None) - .is_some_and(|r| r.changed()) - { - pipe.user_data.config.set_storage("browser_filter", &filter); + if let Some(res) = clearable_edit_field(ui, &mut filter.search, None, None) { + if res.changed() { + pipe.user_data.config.set_storage("browser_filter", &filter); + } + if ui.input_mut(|i| { + i.consume_shortcut(&KeyboardShortcut::new(Modifiers::CTRL, Key::F)) + }) { + res.request_focus(); + } } }); strip.cell(|ui| { diff --git a/game/client-ui/src/main_menu/leftbar/main_frame.rs b/game/client-ui/src/main_menu/leftbar/main_frame.rs index 02d6e9e4..415f4a15 100644 --- a/game/client-ui/src/main_menu/leftbar/main_frame.rs +++ b/game/client-ui/src/main_menu/leftbar/main_frame.rs @@ -127,7 +127,7 @@ pub fn render( ui_state: &mut UiState, ui_page_query_name: &str, ) { - let activate_text = format!("{}{}", prefix, text); + let activate_text = format!("{prefix}{text}"); let selected = activate_text.as_str() == current_active; ui.allocate_ui_with_layout( vec2(size, size), @@ -137,7 +137,7 @@ pub fn render( if selected { let highlight_rect = rect - .translate(vec2(rect.width() / 2.0 * -1.0, 0.0)) + .translate(vec2(-(rect.width() / 2.0), 0.0)) .scale_from_center2(vec2(5.0 / size, 0.5)); ui.painter().add(Shape::rect_filled( highlight_rect, diff --git a/game/client-ui/src/main_menu/settings/graphics/main_frame.rs b/game/client-ui/src/main_menu/settings/graphics/main_frame.rs index b6d2bb12..f29c25c8 100644 --- a/game/client-ui/src/main_menu/settings/graphics/main_frame.rs +++ b/game/client-ui/src/main_menu/settings/graphics/main_frame.rs @@ -79,7 +79,7 @@ fn render_settings(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { if samples == 1 { "off".to_string() } else { - format!("{}", samples) + format!("{samples}") } }), ) @@ -149,7 +149,7 @@ fn render_settings(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { if let Some(ratio) = ratio { ui.label(format!("{} / {}", ratio.numer(), ratio.denom())); } else { - ui.label(format!("{}", aspect_ratio)); + ui.label(format!("{aspect_ratio}")); } ui.end_row(); diff --git a/game/client-ui/src/main_menu/settings/list/list.rs b/game/client-ui/src/main_menu/settings/list/list.rs index def3f29e..f50c3be8 100644 --- a/game/client-ui/src/main_menu/settings/list/list.rs +++ b/game/client-ui/src/main_menu/settings/list/list.rs @@ -70,15 +70,13 @@ pub fn render<'a>( .unwrap_or_else(|| { if matches!(ty, ContainerItemIndexType::Http) { format!( - "{}\n\ + "{entry_name}\n\ \u{f019} downloaded from the \ - assets database.", - entry_name + assets database." ) } else { format!( - "{}\nStored as local asset in on your disk.", - entry_name + "{entry_name}\nStored as local asset in on your disk." ) } }), diff --git a/game/client-ui/src/main_menu/settings/player/controls/main_frame.rs b/game/client-ui/src/main_menu/settings/player/controls/main_frame.rs index 00b44ccb..64d23765 100644 --- a/game/client-ui/src/main_menu/settings/player/controls/main_frame.rs +++ b/game/client-ui/src/main_menu/settings/player/controls/main_frame.rs @@ -107,10 +107,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { }) .collect::>() .join("\n"); - Some(format!( - "This control/command has multiple binds:\n{}", - binds - )) + Some(format!("This control/command has multiple binds:\n{binds}")) } else { None }; @@ -128,8 +125,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { .join("\n"); Some(format!( "This control/command is part of binds \ - with other controls/commands:\n{}", - binds + with other controls/commands:\n{binds}" )) } else { None @@ -139,7 +135,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { if actions.len() == 1 { ( Some(bind_keys_str.clone()), - format!("Bound on {}", bind_keys_str), + format!("Bound on {bind_keys_str}"), multibind_text, info_text, ) diff --git a/game/client-ui/src/main_menu/settings/player/main_frame.rs b/game/client-ui/src/main_menu/settings/player/main_frame.rs index 2e813b0c..66e16cbb 100644 --- a/game/client-ui/src/main_menu/settings/player/main_frame.rs +++ b/game/client-ui/src/main_menu/settings/player/main_frame.rs @@ -197,7 +197,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m .filter(|p| p.name.to_lowercase().starts_with("new tee")) .count(); config.players.push(if new_tee_count > 0 { - ConfigPlayer::new(&format!("new tee ({})", new_tee_count)) + ConfigPlayer::new(&format!("new tee ({new_tee_count})")) } else { ConfigPlayer::new("new tee") }); diff --git a/game/client-ui/src/main_menu/settings/search_settings/main_frame.rs b/game/client-ui/src/main_menu/settings/search_settings/main_frame.rs index 035e36a6..e0d9443b 100644 --- a/game/client-ui/src/main_menu/settings/search_settings/main_frame.rs +++ b/game/client-ui/src/main_menu/settings/search_settings/main_frame.rs @@ -78,13 +78,10 @@ fn render_conf_val( v.config.name = v .config .name - .replacen("$INDEX$", &format!("[{}]", modifier), 1); + .replacen("$INDEX$", &format!("[{modifier}]"), 1); } ModifierTy::Key(modifier) => { - v.config.name = v - .config - .name - .replacen("$KEY$", &format!("[{}]", modifier), 1); + v.config.name = v.config.name.replacen("$KEY$", &format!("[{modifier}]"), 1); } } @@ -268,7 +265,7 @@ fn render_conf_val( return; }; for (index, _) in array.into_iter().enumerate() { - let modifier = format!("{}", index); + let modifier = format!("{index}"); CollapsingHeader::new(&modifier) .id_salt(format!("conf-val-array-{}-{}", value.config.name, index)) .default_open(false) diff --git a/game/client-ui/src/main_menu/settings/sound/spatial_chat/main_frame.rs b/game/client-ui/src/main_menu/settings/sound/spatial_chat/main_frame.rs index fcba4fb3..14b762c4 100644 --- a/game/client-ui/src/main_menu/settings/sound/spatial_chat/main_frame.rs +++ b/game/client-ui/src/main_menu/settings/sound/spatial_chat/main_frame.rs @@ -285,7 +285,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { if loudest_report < 0.00001 { "silent".to_string() } else { - format!("{} db", loudest_db_real) + format!("{loudest_db_real} db") } )); ui.end_row(); @@ -427,7 +427,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { let acc_certs = &mut settings.account_certs; match player.unique_id { PlayerUniqueId::Account(account_id) => { - acc_players.entry(format!("acc_{}", account_id)) + acc_players.entry(format!("acc_{account_id}")) } PlayerUniqueId::CertFingerprint(hash) => { acc_certs.entry(format!("cert_{}", fmt_hash(&hash))) @@ -462,7 +462,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { let acc_certs = &mut settings.account_certs; match player.unique_id { PlayerUniqueId::Account(account_id) => { - acc_players.remove(&format!("acc_{}", account_id)); + acc_players.remove(&format!("acc_{account_id}")); } PlayerUniqueId::CertFingerprint(hash) => { acc_certs.remove(&format!("cert_{}", fmt_hash(&hash))); diff --git a/game/client-ui/src/scoreboard/content/main_frame.rs b/game/client-ui/src/scoreboard/content/main_frame.rs index 21a70cc8..b7e16e67 100644 --- a/game/client-ui/src/scoreboard/content/main_frame.rs +++ b/game/client-ui/src/scoreboard/content/main_frame.rs @@ -99,7 +99,7 @@ pub fn render_players( score_limit, time_limit, } => ( - format!("Score limit: {}", score_limit), + format!("Score limit: {score_limit}"), if let Some(time_limit) = time_limit { format!("Time limit: {}", time_limit.to_race_string()) } else { diff --git a/game/client-ui/src/utils.rs b/game/client-ui/src/utils.rs index 902b5048..ba8790cb 100644 --- a/game/client-ui/src/utils.rs +++ b/game/client-ui/src/utils.rs @@ -29,14 +29,13 @@ use graphics::{ handles::{ canvas::canvas::GraphicsCanvasHandle, shader_storage::shader_storage::ShaderStorage, - stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, + stream::stream::GraphicsStreamHandle, stream_types::StreamedQuad, texture::texture::{TextureContainer, TextureContainer2dArray}, }, streaming::quad_scope_begin, }; use graphics_types::rendering::{ColorRgba, State}; -use hiarc::hi_closure; use math::math::vector::{dvec2, ubvec4, vec2}; use pool::mt_datatypes::PoolVec; use ui_base::{custom_callback::CustomCallbackTrait, types::UiState}; @@ -387,33 +386,20 @@ pub fn render_emoticon_for_ui( texture: &TextureContainer, ) { stream_handle.render_quads( - hi_closure!([ - center: &vec2, - size: &vec2, - texture: &TextureContainer - ], |mut stream_handle: QuadStreamHandle<'_>| -> () { - stream_handle.set_texture(texture); - stream_handle - .add_vertices( - StreamedQuad::default() - .from_pos_and_size( - vec2::new( - center.x - size.x / 2.0, - center.y - size.y / 2.0 - ), - *size - ) - .color(ubvec4::new(255, 255, 255, 255)) - .tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ) - .into() - ); - }), + &[StreamedQuad::default() + .from_pos_and_size( + vec2::new(center.x - size.x / 2.0, center.y - size.y / 2.0), + *size, + ) + .color(ubvec4::new(255, 255, 255, 255)) + .tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + )], state, + texture.into(), ); } @@ -661,43 +647,23 @@ pub fn render_texture_for_ui( state: State, texture: &TextureContainer, ) { - stream_handle.render_quads( - hi_closure!([ - center: &vec2, - size: &vec2, - uv: &Option<(vec2, vec2, vec2, vec2)>, - texture: &TextureContainer - ], |mut stream_handle: QuadStreamHandle<'_>| -> () { - stream_handle.set_texture(texture); - let quad = - StreamedQuad::default() - .from_pos_and_size( - vec2::new( - center.x - size.x / 2.0, - center.y - size.y / 2.0 - ), - *size - ) - .color(ubvec4::new(255, 255, 255, 255)); - let quad = if let Some((tl, tr, br, bl)) = *uv { - quad.tex_free_form( - tl, tr, br, bl - ) - } else { - quad.tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ) - }; - stream_handle - .add_vertices( - quad.into() - ); - }), - state, - ); + let quad = StreamedQuad::default() + .from_pos_and_size( + vec2::new(center.x - size.x / 2.0, center.y - size.y / 2.0), + *size, + ) + .color(ubvec4::new(255, 255, 255, 255)); + let quad = if let Some((tl, tr, br, bl)) = *uv { + quad.tex_free_form(tl, tr, br, bl) + } else { + quad.tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + ) + }; + stream_handle.render_quads(&[quad], state, texture.into()); } render_rect( diff --git a/game/demo/src/recorder.rs b/game/demo/src/recorder.rs index 9fd361c5..92a535f6 100644 --- a/game/demo/src/recorder.rs +++ b/game/demo/src/recorder.rs @@ -454,7 +454,7 @@ impl DemoRecorder { if let Some(tmp_file) = tmp_file.take() { let (_, path) = tmp_file.keep()?; - std::fs::rename(path, final_path.join(format!("{}.twdemo", demo_name)))?; + std::fs::rename(path, final_path.join(format!("{demo_name}.twdemo")))?; } } // else the demo is invalid and can be dropped. diff --git a/game/editor-wasm/src/editor/editor_wasm_manager.rs b/game/editor-wasm/src/editor/editor_wasm_manager.rs index 959944d6..e486f94f 100644 --- a/game/editor-wasm/src/editor/editor_wasm_manager.rs +++ b/game/editor-wasm/src/editor/editor_wasm_manager.rs @@ -15,17 +15,17 @@ use wasm_runtime::WasmManager; use super::{editor_lib::editor_lib::EditorLib, editor_wasm::editor_wasm::EditorWasm}; pub enum EditorWrapper { - Native(Editor), + Native(Box), NativeLib(EditorLib), - Wasm(EditorWasm), + Wasm(Box), } impl AsRef for EditorWrapper { fn as_ref(&self) -> &(dyn EditorInterface + 'static) { match self { - Self::Native(state) => state, + Self::Native(state) => state.as_ref(), Self::NativeLib(state) => state, - Self::Wasm(state) => state, + Self::Wasm(state) => state.as_ref(), } } } @@ -33,9 +33,9 @@ impl AsRef for EditorWrapper { impl AsMut for EditorWrapper { fn as_mut(&mut self) -> &mut (dyn EditorInterface + 'static) { match self { - Self::Native(state) => state, + Self::Native(state) => state.as_mut(), Self::NativeLib(state) => state, - Self::Wasm(state) => state, + Self::Wasm(state) => state.as_mut(), } } } @@ -81,7 +81,7 @@ impl EditorWasmManager { }); let state = if let Ok(wasm_module) = task.get_storage() { let state = EditorWasm::new(sound, graphics, backend, io, font_data, &wasm_module); - EditorWrapper::Wasm(state) + EditorWrapper::Wasm(Box::new(state)) } else { let path_str = MODS_PATH.to_string() + "/libeditor.so"; let save_path: PathBuf = path_str.into(); @@ -100,11 +100,11 @@ impl EditorWasmManager { EditorWrapper::NativeLib(EditorLib::new(sound, graphics, io, font_data, lib)) } else { let state = Editor::new(sound, graphics, io, thread_pool, font_data); - EditorWrapper::Native(state) + EditorWrapper::Native(Box::new(state)) } } else { let state = Editor::new(sound, graphics, io, thread_pool, font_data); - EditorWrapper::Native(state) + EditorWrapper::Native(Box::new(state)) } }; Self { diff --git a/game/editor/Cargo.toml b/game/editor/Cargo.toml index 5d28f9eb..cca6f628 100644 --- a/game/editor/Cargo.toml +++ b/game/editor/Cargo.toml @@ -28,8 +28,10 @@ client-notifications = { path = "../client-notifications" } game-config = { path = "../game-config" } game-interface = { path = "../game-interface" } game-base = { path = "../game-base" } +legacy-map = { path = "../legacy-map" } editor-interface = { path = "../editor-interface" } editor-auto-mapper-wasm = { path = "../editor-auto-mapper-wasm" } +camera = { path = "../camera" } map-convert-lib = { path = "../map-convert-lib" } diff --git a/game/editor/src/editor.rs b/game/editor/src/editor.rs index e9ec3231..8227addd 100644 --- a/game/editor/src/editor.rs +++ b/game/editor/src/editor.rs @@ -16,6 +16,7 @@ use base::{ }; use base_io::{io::Io, runtime::IoRuntimeTask}; use base_io_traits::fs_traits::FileSystemInterface; +use camera::CameraInterface; use client_containers::entities::{EntitiesContainer, ENTITIES_CONTAINER_PATH}; use client_notifications::overlay::ClientNotifications; use client_render_base::map::{ @@ -23,8 +24,6 @@ use client_render_base::map::{ map_buffered::{ ClientMapBufferQuadLayer, MapBufferPhysicsTileLayer, MapBufferTileLayer, SoundLayerSounds, }, - render_pipe::Camera, - render_tools::{CanvasType, RenderTools}, }; use config::config::ConfigEngine; use ed25519_dalek::pkcs8::spki::der::Encode; @@ -38,15 +37,15 @@ use graphics::{ backend::backend::GraphicsBackendHandle, buffer_object::buffer_object::GraphicsBufferObjectHandle, shader_storage::shader_storage::GraphicsShaderStorageHandle, - stream::stream::LinesStreamHandle, stream_types::StreamedLine, texture::texture::{GraphicsTextureHandle, TextureContainer, TextureContainer2dArray}, }, }; use graphics_types::{commands::TexFlags, rendering::State, types::GraphicsMemoryAllocationType}; -use hiarc::{hi_closure, HiarcTrait}; +use hiarc::HiarcTrait; use image_utils::{png::load_png_image_as_rgba, utils::texture_2d_to_3d}; use map::{ + file::MapFileReader, map::{ animations::{AnimBase, AnimPoint, AnimPointCurveType}, config::Config, @@ -74,6 +73,7 @@ use map::{ }, }, types::NonZeroU16MinusOne, + utils::file_ext_or_twmap_tar, }; use math::math::vector::{ffixed, fvec2, ubvec4, vec2}; use network::network::types::{ @@ -419,7 +419,7 @@ impl Editor { sys, }; - res.load_map("map/maps/ctf1.twmap".as_ref(), Default::default()); + res.load_map("map/maps/ctf1.twmap.tar".as_ref(), Default::default()); res } @@ -1129,11 +1129,12 @@ impl Editor { } fn path_to_tab_name(path: &Path) -> anyhow::Result { - Ok(path + let name = path .file_stem() .ok_or_else(|| anyhow!("{path:?} is not a valid file"))? .to_string_lossy() - .to_string()) + .to_string(); + Ok(name.replace(".twmap", "")) } fn load_legacy_map( @@ -1266,7 +1267,7 @@ impl Editor { .rt .spawn(async move { let file = read_file_editor(&fs, &path).await?; - let map = Map::read(&file, &tp)?; + let map = Map::read(&MapFileReader::new(file)?, &tp)?; let mut resource_files: HashMap> = Default::default(); for (ty, i) in map .resources @@ -1409,8 +1410,7 @@ impl Editor { let tp = tp.clone(); let fs = io.fs.clone(); Ok(io.rt.spawn(async move { - let mut file: Vec = Default::default(); - map.write(&mut file, &tp)?; + let file: Vec = map.write(&tp)?; let map_legacy = map_convert_lib::new_to_legacy::new_to_legacy_from_buf_async( &file, |map| { @@ -1577,8 +1577,7 @@ impl Editor { fs.create_dir("map/resources/images".as_ref()).await?; fs.create_dir("map/resources/sounds".as_ref()).await?; - let mut file: Vec = Default::default(); - map.write(&mut file, &tp)?; + let file: Vec = map.write(&tp)?; write_file_editor(&fs, path.as_ref(), file).await?; // now write all resources @@ -1693,7 +1692,7 @@ impl Editor { live_edited_layers, })) = update_res { - let map = Map::read(&map, &self.thread_pool).unwrap(); + let map = Map::read(&MapFileReader::new(map).unwrap(), &self.thread_pool).unwrap(); tab.map = Self::map_to_editor_map_impl( self.graphics.get_graphics_mt(), self.sound_mt.clone(), @@ -2048,12 +2047,7 @@ impl Editor { &map.resources.sounds, &group.attr, layer, - &Camera { - pos: map.groups.user.pos, - zoom: map.groups.user.zoom, - parallax_aware_zoom: map.groups.user.parallax_aware_zoom, - forced_aspect_ratio: None, - }, + &map.game_camera(), 0.3, ); } @@ -2086,12 +2080,7 @@ impl Editor { // TODO: "ddnet", layer, - &Camera { - pos: map.groups.user.pos, - zoom: map.groups.user.zoom, - parallax_aware_zoom: map.groups.user.parallax_aware_zoom, - forced_aspect_ratio: None, - }, + &map.game_camera(), &time, &time, map.user.include_last_anim_point(), @@ -2579,15 +2568,8 @@ impl Editor { let grid_size = grid_size as f32; let mut state = State::new(); - RenderTools::map_canvas_of_group( - CanvasType::Handle(&self.graphics.canvas_handle), - &mut state, - tab.map.groups.user.pos.x, - tab.map.groups.user.pos.y, - Some(&attr), - tab.map.groups.user.zoom, - tab.map.groups.user.parallax_aware_zoom, - ); + let camera = tab.map.game_camera(); + camera.project(&self.graphics.canvas_handle, &mut state, Some(&attr)); let (width, height) = (state.get_canvas_width(), state.get_canvas_height()); let offset = state.canvas_tl; @@ -2615,17 +2597,7 @@ impl Editor { y += grid_size; } - self.graphics.stream_handle.render_lines( - hi_closure!( - [lines: Vec], - |mut stream_handle: LinesStreamHandle<'_>| -> () { - for line in lines { - stream_handle.add_vertices(line.into()); - } - } - ), - state, - ); + self.graphics.stream_handle.render_lines(&lines, state); } fn render_ui( @@ -2936,7 +2908,7 @@ impl EditorInterface for Editor { None } }) { - log::info!("[Editor] Copied the following text: {}", text); + log::info!("[Editor] Copied the following text: {text}"); } // handle save tasks @@ -2992,13 +2964,13 @@ impl EditorInterface for Editor { } fn file_dropped(&mut self, file: PathBuf) { - let Some(ext) = file.extension().and_then(|e| e.to_str()) else { + let Some(ext) = file_ext_or_twmap_tar(&file) else { return; }; let fs = self.io.fs.clone(); match ext { - "map" | "twmap" => { + "map" | "twmap.tar" => { self.load_map(&file, Default::default()); } "png" => { diff --git a/game/editor/src/map.rs b/game/editor/src/map.rs index ed582d15..c213aef8 100644 --- a/game/editor/src/map.rs +++ b/game/editor/src/map.rs @@ -9,9 +9,10 @@ use std::{ use base::{hash::Hash, linked_hash_map_view::FxLinkedHashMap}; use base_io::runtime::IoRuntimeTask; +use camera::Camera; use client_render_base::map::{ map_buffered::{PhysicsTileLayerVisuals, QuadLayerVisuals, SoundLayerSounds, TileLayerVisuals}, - render_pipe::{Camera, GameTimeInfo}, + render_pipe::GameTimeInfo, }; use egui_file_dialog::FileDialog; use egui_timeline::timeline::Timeline; @@ -785,11 +786,11 @@ pub type EditorMap = MapSkeleton< >; impl EditorMapGroupsInterface for EditorGroups { - fn active_layer(&self) -> Option { + fn active_layer(&self) -> Option> { fn find_layer( is_background: bool, (group_index, group): (usize, &EditorGroup), - ) -> Option { + ) -> Option> { group .layers .iter() @@ -846,11 +847,11 @@ impl EditorMapGroupsInterface for EditorGroups { None } - fn active_layer_mut(&mut self) -> Option { + fn active_layer_mut(&mut self) -> Option> { fn find_layer( is_background: bool, - (group_index, group): (usize, &mut EditorGroup), - ) -> Option { + (group_index, group): (usize, &'_ mut EditorGroup), + ) -> Option> { group .layers .iter_mut() @@ -902,7 +903,7 @@ impl EditorMapGroupsInterface for EditorGroups { None } - fn selected_layers(&self) -> Vec { + fn selected_layers(&self) -> Vec> { fn collect_group( group: &[EditorGroup], is_background: bool, @@ -985,11 +986,11 @@ impl EditorMapGroupsInterface for EditorGroups { } impl EditorMapInterface for EditorMap { - fn active_layer(&self) -> Option { + fn active_layer(&self) -> Option> { self.groups.active_layer() } - fn active_layer_mut(&mut self) -> Option { + fn active_layer_mut(&mut self) -> Option> { self.groups.active_layer_mut() } diff --git a/game/editor/src/server.rs b/game/editor/src/server.rs index b5373bbf..63761ccd 100644 --- a/game/editor/src/server.rs +++ b/game/editor/src/server.rs @@ -21,7 +21,7 @@ use graphics::{ texture::texture::GraphicsTextureHandle, }, }; -use map::map::Map; +use map::{file::MapFileReader, map::Map}; use math::math::vector::vec2; use network::network::{ connection::NetworkConnectionId, @@ -356,8 +356,7 @@ impl EditorServer { let send_map: Map = map.clone().into(); - let mut map_bytes = Vec::new(); - send_map.write(&mut map_bytes, tp).unwrap(); + let map_bytes = send_map.write(tp).unwrap(); self.network.send_to( &id, @@ -561,7 +560,7 @@ impl EditorServer { if is_undo { "undo" } else { "redo" } ); log::error!("{err}{}", act_err.backtrace()); - log::error!("current action: {}", act_label); + log::error!("current action: {act_label}"); log::error!( "latest action log starting with \ the most recent:\n{}", @@ -792,9 +791,8 @@ impl EditorServer { || props.full_map_validation_probability == u8::MAX { let map: Map = map.clone().into(); - let mut map_file: Vec<_> = Default::default(); - map.write(&mut map_file, tp).unwrap(); - Map::read(&map_file, tp).unwrap(); + let map_file: Vec<_> = map.write(tp).unwrap(); + Map::read(&MapFileReader::new(map_file).unwrap(), tp).unwrap(); } } } @@ -836,14 +834,15 @@ impl EditorServer { ) .ok() }) - .map(|r| (name, TileLayerAutoMapperRuleType::Wasm(r))) + .map(|r| { + (name, TileLayerAutoMapperRuleType::Wasm(Box::new(r))) + }) }) .flatten() .into_iter() .collect(), - EditorEventRuleTy::LegacyRules(rules) => (generate_hash_for(&rules) - == hash) - .then(|| { + EditorEventRuleTy::LegacyRules(rules) => { + if generate_hash_for(&rules) == hash { LegacyRulesLoading::new(&rules) .ok() .map(|rules| { @@ -867,8 +866,10 @@ impl EditorServer { .collect() }) .unwrap_or_default() - }) - .unwrap_or_default(), + } else { + Default::default() + } + } }; if !rules.is_empty() { for (name, rule) in rules { diff --git a/game/editor/src/tools/quad_layer/brush.rs b/game/editor/src/tools/quad_layer/brush.rs index 9ef4d4dd..8afaeb93 100644 --- a/game/editor/src/tools/quad_layer/brush.rs +++ b/game/editor/src/tools/quad_layer/brush.rs @@ -1,10 +1,10 @@ -use std::{cell::Cell, collections::HashSet, time::Duration}; +use std::{cell::Cell, collections::HashSet}; +use camera::CameraInterface; use client_render_base::map::{ - map::RenderMap, + map::{QuadAnimEvalResult, RenderMap}, map_buffered::QuadLayerVisuals, map_pipeline::{MapGraphics, QuadRenderInfo}, - render_tools::{CanvasType, RenderTools}, }; use graphics::{ graphics_mt::GraphicsMultiThreaded, @@ -18,8 +18,10 @@ use graphics::{ }; use graphics_types::rendering::State; use hiarc::{hi_closure, Hiarc}; -use map::{map::groups::layers::design::Quad, skeleton::animations::AnimationsSkeleton}; -use math::math::vector::{dvec2, ffixed, ubvec4, vec2}; +use map::map::groups::layers::design::Quad; +use math::math::vector::{dvec2, ffixed, fvec3, nfvec4, ubvec4, vec2}; +use pool::pool::Pool; +use rustc_hash::FxHashMap; use crate::{ actions::actions::{ @@ -495,7 +497,7 @@ impl QuadBrush { quads, }, }), - format!("quad-brush design {}", layer_index), + format!("quad-brush design {layer_index}"), )) } else { None @@ -522,10 +524,10 @@ impl QuadBrush { } else { Default::default() }; - let is_primary_allowed_pressed = !latest_modifiers.ctrl && latest_pointer.primary_pressed(); + let is_primary_allowed_down = !latest_modifiers.ctrl && latest_pointer.primary_down(); // if pointer was already down if let QuadPointerDownState::Selection(pointer_down) = &self.pointer_down_state { - if is_primary_allowed_pressed { + if is_primary_allowed_down { let pos = current_pointer_pos; let pos = ui_pos_to_world_pos( canvas_handle, @@ -545,7 +547,7 @@ impl QuadBrush { let down_pos = pointer_down; let down_pos = egui::pos2(down_pos.x, down_pos.y); - let rect = egui::Rect::from_min_max(pos, down_pos); + let rect = egui::Rect::from_two_pos(pos, down_pos); render_rect( canvas_handle, @@ -595,16 +597,12 @@ impl QuadBrush { let pos = egui::pos2(pos.x, pos.y); let mut state = State::new(); - - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), + map.game_camera().project( + canvas_handle, &mut state, - map.groups.user.pos.x, - map.groups.user.pos.y, layer.map(|layer| layer.get_or_fake_group_attr()).as_ref(), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, ); + let center = -pos_on_map; state.canvas_br.x += center.x; state.canvas_br.y += center.y; @@ -618,31 +616,46 @@ impl QuadBrush { let cur_quad_offset = &cur_quad_offset_cell; let animations = map.active_animations(); let include_last_anim_point = map.user.include_last_anim_point(); + + let QuadAnimEvalResult { + pos_anims_values, + color_anims_values, + } = RenderMap::prepare_quad_anims( + &Pool::with_capacity(8), + &Pool::with_capacity(8), + cur_time, + cur_anim_time, + include_last_anim_point, + &brush.render, + animations, + ); + + let pos_anims_values = &*pos_anims_values; + let color_anims_values = &*color_anims_values; + stream_handle.fill_uniform_instance( hi_closure!( - , [ - cur_time: &Duration, - cur_anim_time: &Duration, - include_last_anim_point: bool, - cur_quad_offset: &Cell, - animations: &AnimationsSkeleton, - quads: &Vec, - ], |stream_handle: StreamedUniforms< - '_, - QuadRenderInfo, - >| - -> () { - RenderMap::prepare_quad_rendering( - stream_handle, - cur_time, - cur_anim_time, - include_last_anim_point, - cur_quad_offset, - animations, - quads - ); - }), + pos_anims_values: &FxHashMap<(usize, time::Duration), fvec3>, + color_anims_values: &FxHashMap<(usize, time::Duration), nfvec4>, + cur_quad_offset: &Cell, + quads: &Vec, + ], + |stream_handle: StreamedUniforms< + '_, + QuadRenderInfo, + >| + -> () { + RenderMap::prepare_quad_rendering( + stream_handle, + color_anims_values, + pos_anims_values, + cur_quad_offset, + quads, + 0 + ); + } + ), hi_closure!([ brush: &QuadBrushQuads, state: State, diff --git a/game/editor/src/tools/quad_layer/selection.rs b/game/editor/src/tools/quad_layer/selection.rs index d748c1c4..208da877 100644 --- a/game/editor/src/tools/quad_layer/selection.rs +++ b/game/editor/src/tools/quad_layer/selection.rs @@ -1,6 +1,6 @@ use std::collections::{BTreeMap, HashSet}; -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::CameraInterface; use graphics::handles::{ canvas::canvas::GraphicsCanvasHandle, stream::stream::GraphicsStreamHandle, }; @@ -302,8 +302,8 @@ impl QuadSelection { index, })), Some(&format!( - "change-quad-attr-{}-{}-{}-{}", - is_background, group_index, layer_index, index + "change-quad-attr-\ + {is_background}-{group_index}-{layer_index}-{index}" )), ); } @@ -455,19 +455,9 @@ impl QuadSelection { let range = self.range.as_ref().unwrap(); - let (center, group_attr) = ( - map.groups.user.pos, - layer.map(|layer| layer.get_or_fake_group_attr()), - ); - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - center.x, - center.y, - group_attr.as_ref(), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, - ); + let group_attr = layer.map(|layer| layer.get_or_fake_group_attr()); + map.game_camera() + .project(canvas_handle, &mut state, group_attr.as_ref()); let range_size = vec2::new(range.w, range.h); let rect = egui::Rect::from_min_max( diff --git a/game/editor/src/tools/quad_layer/shared.rs b/game/editor/src/tools/quad_layer/shared.rs index 15e1dae6..3e1a6c26 100644 --- a/game/editor/src/tools/quad_layer/shared.rs +++ b/game/editor/src/tools/quad_layer/shared.rs @@ -1,6 +1,7 @@ use std::{collections::BTreeMap, time::Duration}; -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::CameraInterface; +use client_render_base::map::render_tools::RenderTools; use graphics::handles::{ canvas::canvas::GraphicsCanvasHandle, stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, @@ -146,17 +147,10 @@ pub fn render_quad_points( let points = get_quad_points_animated(quad, map, map.user.render_time()); let mut state = State::new(); - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - map.groups.user.pos.x, - map.groups.user.pos.y, - Some(&group.attr), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, - ); + map.game_camera() + .project(canvas_handle, &mut state, Some(&group.attr)); let h = state.get_canvas_height() / canvas_handle.canvas_height() as f32; - stream_handle.render_quads( + stream_handle.stream_quads( hi_closure!([points: [fvec2; 5], x: f32, y: f32, h: f32, render_corner_points: bool], |mut stream_handle: QuadStreamHandle<'_>| -> () { let hit_size = QUAD_POINT_RADIUS_FACTOR * h; let point_size = QUAD_POINT_RADIUS_FACTOR * 0.7 * h; diff --git a/game/editor/src/tools/sound_layer/brush.rs b/game/editor/src/tools/sound_layer/brush.rs index e11419ed..8c55390c 100644 --- a/game/editor/src/tools/sound_layer/brush.rs +++ b/game/editor/src/tools/sound_layer/brush.rs @@ -1,7 +1,5 @@ -use client_render_base::map::{ - map::RenderMap, - render_tools::{CanvasType, RenderTools}, -}; +use camera::{Camera, CameraInterface}; +use client_render_base::map::map::RenderMap; use graphics::handles::{ canvas::canvas::GraphicsCanvasHandle, stream::stream::GraphicsStreamHandle, }; @@ -395,7 +393,7 @@ impl SoundBrush { sounds, }, }), - format!("sound-brush design {}", layer_index), + format!("sound-brush design {layer_index}"), )) } else { None @@ -516,15 +514,14 @@ impl SoundBrush { ); (map.groups.user.pos - pos_on_map, None) }; - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - center.x, - center.y, - group_attr.as_ref(), + Camera::new( + center, map.groups.user.zoom, + None, map.groups.user.parallax_aware_zoom, - ); + ) + .project(canvas_handle, &mut state, group_attr.as_ref()); + let time = map.user.render_time(); RenderMap::render_sounds( stream_handle, diff --git a/game/editor/src/tools/sound_layer/shared.rs b/game/editor/src/tools/sound_layer/shared.rs index 990d3ce5..03c10d0b 100644 --- a/game/editor/src/tools/sound_layer/shared.rs +++ b/game/editor/src/tools/sound_layer/shared.rs @@ -1,11 +1,11 @@ -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::CameraInterface; +use client_render_base::map::render_tools::RenderTools; use graphics::handles::{ - canvas::canvas::GraphicsCanvasHandle, - stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, - stream_types::StreamedQuad, + canvas::canvas::GraphicsCanvasHandle, stream::stream::GraphicsStreamHandle, + stream_types::StreamedQuad, texture::texture::TextureType, }; use graphics_types::rendering::State; -use hiarc::{hi_closure, Hiarc}; +use hiarc::Hiarc; use map::map::groups::layers::design::Sound; use math::math::vector::{ffixed, fvec2, ubvec4, vec2}; use std::time::Duration; @@ -77,36 +77,29 @@ pub fn render_sound_points( let point = get_sound_point_animated(sound, map, map.user.render_time()); let mut state = State::new(); - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - map.groups.user.pos.x, - map.groups.user.pos.y, - Some(&group.attr), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, - ); + map.game_camera() + .project(canvas_handle, &mut state, Some(&group.attr)); + let h = state.get_canvas_height() / canvas_handle.canvas_height() as f32; + let hit_size = SOUND_POINT_RADIUS_FACTOR * h; + let point_size = SOUND_POINT_RADIUS_FACTOR * 0.7 * h; + let color = if in_radius(&point, &vec2::new(x, y), hit_size) { + ubvec4::new(150, 255, 150, 255) + } else { + ubvec4::new(0, 255, 0, 255) + }; stream_handle.render_quads( - hi_closure!([point: fvec2, x: f32, y: f32, h: f32], |mut stream_handle: QuadStreamHandle<'_>| -> () { - let hit_size = SOUND_POINT_RADIUS_FACTOR * h; - let point_size = SOUND_POINT_RADIUS_FACTOR * 0.7 * h; - let color = if in_radius(&point, &vec2::new(x, y), hit_size) { - ubvec4::new(150, 255, 150, 255) - } - else { - ubvec4::new(0, 255, 0, 255) - }; - stream_handle.add_vertices( - StreamedQuad::default().from_pos_and_size( - vec2::new(point.x.to_num::() - point_size / 2.0, point.y.to_num::() - point_size / 2.0), - vec2::new(point_size, point_size) - ) - .color(color) - .into() - ); - }), + &[StreamedQuad::default() + .from_pos_and_size( + vec2::new( + point.x.to_num::() - point_size / 2.0, + point.y.to_num::() - point_size / 2.0, + ), + vec2::new(point_size, point_size), + ) + .color(color)], state, + TextureType::None, ); } } diff --git a/game/editor/src/tools/tile_layer/auto_mapper.rs b/game/editor/src/tools/tile_layer/auto_mapper.rs index 675a86b9..42608322 100644 --- a/game/editor/src/tools/tile_layer/auto_mapper.rs +++ b/game/editor/src/tools/tile_layer/auto_mapper.rs @@ -481,7 +481,7 @@ impl TileLayerAutoMapperWasm { pub enum TileLayerAutoMapperRuleType { EditorRule(TileLayerAutoMapperEditorRule), - Wasm(TileLayerAutoMapperWasm), + Wasm(Box), LegacyRules { rule: LegacyRule, loading_data: Arc>, @@ -1062,7 +1062,9 @@ impl TileLayerAutoMapper { resource.rules.insert( task.name, ( - TileLayerAutoMapperRuleType::Wasm(manager), + TileLayerAutoMapperRuleType::Wasm(Box::new( + manager, + )), ResourceHashTy::Hashed, ), ); @@ -1144,7 +1146,7 @@ impl TileLayerAutoMapper { Ok(manager) => { let v = ( TileLayerAutoMapperRuleType::Wasm( - manager, + Box::new(manager), ), hash_ty, ); diff --git a/game/editor/src/tools/tile_layer/brush.rs b/game/editor/src/tools/tile_layer/brush.rs index 419b9eb3..458321ea 100644 --- a/game/editor/src/tools/tile_layer/brush.rs +++ b/game/editor/src/tools/tile_layer/brush.rs @@ -1,5 +1,6 @@ use std::{cell::Cell, collections::HashSet, rc::Rc, sync::Arc}; +use camera::CameraInterface; use client_containers::{container::ContainerKey, entities::EntitiesContainer}; use client_render_base::map::{ map_buffered::{ @@ -7,10 +8,8 @@ use client_render_base::map::{ TileLayerBufferedVisuals, TileLayerVisuals, }, map_pipeline::{MapGraphics, TileLayerDrawInfo}, - render_tools::{CanvasType, RenderTools}, }; use egui::{pos2, Rect}; -use game_base::mapdef_06::DdraceTileNum; use graphics::{ graphics_mt::GraphicsMultiThreaded, handles::{ @@ -24,6 +23,7 @@ use graphics::{ }; use graphics_types::rendering::State; use hiarc::Hiarc; +use legacy_map::mapdef_06::DdraceTileNum; use map::{ map::groups::{ layers::{ @@ -1409,12 +1409,28 @@ impl TileBrush { w, h, negative_offset: usvec2::new( - x_needs_offset.then_some(count_x - 1).unwrap_or_default(), - y_needs_offset.then_some(count_y - 1).unwrap_or_default(), + if x_needs_offset { + count_x - 1 + } else { + Default::default() + }, + if y_needs_offset { + count_y - 1 + } else { + Default::default() + }, ), negative_offsetf: dvec2::new( - x_needs_offset.then_some(count_x as f64).unwrap_or_default(), - y_needs_offset.then_some(count_y as f64).unwrap_or_default(), + if x_needs_offset { + count_x as f64 + } else { + Default::default() + }, + if y_needs_offset { + count_y as f64 + } else { + Default::default() + }, ), render, map_render: MapGraphics::new(backend_handle), @@ -1522,7 +1538,7 @@ impl TileBrush { }, )); - (actions, format!("tile-brush phy {}", layer_index)) + (actions, format!("tile-brush phy {layer_index}")) } EditorLayerUnionRef::Design { layer, @@ -1564,8 +1580,7 @@ impl TileBrush { }, )], format!( - "tile-brush {}-{}-{}", - group_index, layer_index, is_background + "tile-brush {group_index}-{layer_index}-{is_background}" ), ) } @@ -1907,7 +1922,7 @@ impl TileBrush { ( actions, - format!("tile-brush phy {}", layer_index), + format!("tile-brush phy {layer_index}"), TileBrushLastApplyLayer::Physics { layer_index: *layer_index, }, @@ -1986,10 +2001,7 @@ impl TileBrush { ( actions, - format!( - "tile-brush {}-{}-{}", - group_index, layer_index, is_background - ), + format!("tile-brush {group_index}-{layer_index}-{is_background}"), TileBrushLastApplyLayer::Design { group_index: *group_index, layer_index: *layer_index, @@ -2570,15 +2582,8 @@ impl TileBrush { let mut state = State::new(); let pos_x = off_x - tile_offset_x as f32 * TILE_VISUAL_SIZE; let pos_y = off_y - tile_offset.y as f32 * TILE_VISUAL_SIZE; - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - map.groups.user.pos.x, - map.groups.user.pos.y, - group_attr.as_ref(), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, - ); + map.game_camera() + .project(canvas_handle, &mut state, group_attr.as_ref()); state.canvas_br.x += center.x - pos_x; state.canvas_br.y += center.y - pos_y; state.canvas_tl.x += center.x - pos_x; @@ -2647,15 +2652,8 @@ impl TileBrush { }) = &brush.render { let mut state = State::new(); - RenderTools::map_canvas_of_group( - CanvasType::Handle(canvas_handle), - &mut state, - map.groups.user.pos.x, - map.groups.user.pos.y, - group_attr.as_ref(), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, - ); + map.game_camera() + .project(canvas_handle, &mut state, group_attr.as_ref()); state.canvas_br.x += center.x; state.canvas_br.y += center.y; state.canvas_tl.x += center.x; diff --git a/game/editor/src/tools/tile_layer/legacy_rules.rs b/game/editor/src/tools/tile_layer/legacy_rules.rs index 4f7b6cb1..273dcc5a 100644 --- a/game/editor/src/tools/tile_layer/legacy_rules.rs +++ b/game/editor/src/tools/tile_layer/legacy_rules.rs @@ -4,7 +4,7 @@ use anyhow::anyhow; use editor_interface::auto_mapper::{ AutoMapperInputModes, AutoMapperInterface, AutoMapperModes, AutoMapperOutputModes, }; -use game_base::mapdef_06::DdraceTileNum; +use legacy_map::mapdef_06::DdraceTileNum; use map::map::groups::layers::tiles::{Tile, TileFlags}; use scan_fmt::{scan_fmt, scan_fmt_some}; diff --git a/game/editor/src/tools/utils.rs b/game/editor/src/tools/utils.rs index cfe3181e..d09e23d0 100644 --- a/game/editor/src/tools/utils.rs +++ b/game/editor/src/tools/utils.rs @@ -1,15 +1,16 @@ -use client_render_base::map::render_tools::RenderTools; +use camera::CameraInterface; use egui::Color32; use graphics::handles::{ canvas::canvas::GraphicsCanvasHandle, - stream::stream::{GraphicsStreamHandle, LinesStreamHandle, QuadStreamHandle}, + stream::stream::GraphicsStreamHandle, stream_types::{StreamedLine, StreamedQuad}, + texture::texture::TextureType, }; use graphics_types::rendering::{ColorMaskMode, State, StencilMode}; -use hiarc::hi_closure; -use math::math::vector::{ubvec4, vec2}; +use map::map::groups::MapGroupAttr; +use math::math::vector::{ffixed, fvec2, ubvec4, vec2}; -use crate::map::EditorMap; +use crate::map::{EditorMap, EditorMapInterface}; pub fn render_rect_from_state( stream_handle: &GraphicsStreamHandle, @@ -17,29 +18,25 @@ pub fn render_rect_from_state( rect: egui::Rect, color: ubvec4, ) { - stream_handle.render_lines( - hi_closure!([rect: egui::Rect, color: ubvec4], |mut stream_handle: LinesStreamHandle<'_>| -> () { - let mut line = StreamedLine::new().with_color(color); - - line = line.from_pos( - [vec2::new(rect.min.x, rect.min.y), vec2::new(rect.max.x, rect.min.y)] - ); - stream_handle.add_vertices(line.into()); - line = line.from_pos( - [vec2::new(rect.min.x, rect.min.y), vec2::new(rect.min.x, rect.max.y)] - ); - stream_handle.add_vertices(line.into()); - line = line.from_pos( - [vec2::new(rect.max.x, rect.min.y), vec2::new(rect.max.x, rect.max.y)] - ); - stream_handle.add_vertices(line.into()); - line = line.from_pos( - [vec2::new(rect.min.x, rect.max.y), vec2::new(rect.max.x, rect.max.y)] - ); - stream_handle.add_vertices(line.into()); - }), - state, - ); + let line = StreamedLine::new().with_color(color); + + let line1 = line.from_pos([ + vec2::new(rect.min.x, rect.min.y), + vec2::new(rect.max.x, rect.min.y), + ]); + let line2 = line.from_pos([ + vec2::new(rect.min.x, rect.min.y), + vec2::new(rect.min.x, rect.max.y), + ]); + let line3 = line.from_pos([ + vec2::new(rect.max.x, rect.min.y), + vec2::new(rect.max.x, rect.max.y), + ]); + let line4 = line.from_pos([ + vec2::new(rect.min.x, rect.max.y), + vec2::new(rect.max.x, rect.max.y), + ]); + stream_handle.render_lines(&[line1, line2, line3, line4], state); } pub fn render_rect_state( @@ -49,18 +46,15 @@ pub fn render_rect_state( offset: &vec2, ) -> State { let mut state = State::new(); - let points: [f32; 4] = RenderTools::map_canvas_to_world( - map.groups.user.pos.x, - map.groups.user.pos.y, - parallax.x, - parallax.y, - offset.x, - offset.y, - canvas_handle.canvas_aspect(), - map.groups.user.zoom, - map.groups.user.parallax_aware_zoom, + map.game_camera().project( + canvas_handle, + &mut state, + Some(&MapGroupAttr { + offset: fvec2::new(ffixed::from_num(offset.x), ffixed::from_num(offset.y)), + parallax: fvec2::new(ffixed::from_num(parallax.x), ffixed::from_num(parallax.y)), + clipping: None, + }), ); - state.map_canvas(points[0], points[1], points[2], points[3]); state } @@ -156,24 +150,20 @@ pub fn render_filled_rect_from_state( ColorMaskMode::WriteAll }); + let pos = rect.min; + let size = rect.size(); stream_handle.render_quads( - hi_closure!([rect: egui::Rect, color: ubvec4], |mut stream_quads: QuadStreamHandle<'_>| -> () { - let pos = rect.min; - let size = rect.size(); - stream_quads.add_vertices( - StreamedQuad::default() - .from_pos_and_size(vec2::new(pos.x, pos.y), vec2::new(size.x, size.y)) - .tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ) - .color(color) - .into() - ); - }), + &[StreamedQuad::default() + .from_pos_and_size(vec2::new(pos.x, pos.y), vec2::new(size.x, size.y)) + .tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + ) + .color(color)], state, + TextureType::None, ); } diff --git a/game/editor/src/ui/animation_panel/panel.rs b/game/editor/src/ui/animation_panel/panel.rs index 0d5114e4..22cbc8ee 100644 --- a/game/editor/src/ui/animation_panel/panel.rs +++ b/game/editor/src/ui/animation_panel/panel.rs @@ -220,7 +220,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st ) where S: FnOnce(usize, &AnimBaseSkeleton) -> EditorAction, { - ui.label(format!("{}:", name)); + ui.label(format!("{name}:")); // selection of animation if ui.button("\u{f060}").clicked() { *index = index.map(|i| i.checked_sub(1)).flatten(); @@ -230,7 +230,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st fn combobox_name(ty: &str, index: usize, name: &str) -> String { name.is_empty() - .then_some(format!("{ty} #{}", index)) + .then_some(format!("{ty} #{index}")) .unwrap_or_else(|| name.to_owned()) } egui::ComboBox::new(format!("animations-select-anim{name}"), "") @@ -266,7 +266,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st client.execute( add(index), - Some(&format!("{name}-anim-insert-anim-at-{}", index)), + Some(&format!("{name}-anim-insert-anim-at-{index}")), ); } @@ -327,7 +327,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st }, |index, anims, groups| EditorActionGroup { actions: rem_color_anim(anims, groups, index), - identifier: Some(format!("color-anim-del-anim-at-{}", index)), + identifier: Some(format!("color-anim-del-anim-at-{index}")), }, |index, anim| { let mut anim: ColorAnimation = anim.clone().into(); @@ -372,7 +372,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st }, |index, anims, groups| EditorActionGroup { actions: rem_pos_anim(anims, groups, index), - identifier: Some(format!("pos-anim-del-anim-at-{}", index)), + identifier: Some(format!("pos-anim-del-anim-at-{index}")), }, |index, anim| { let mut anim: PosAnimation = anim.clone().into(); @@ -418,7 +418,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st }, |index, anims, groups| EditorActionGroup { actions: rem_sound_anim(anims, groups, index), - identifier: Some(format!("sound-anim-del-anim-at-{}", index)), + identifier: Some(format!("sound-anim-del-anim-at-{index}")), }, |index, anim| { let mut anim: SoundAnimation = anim.clone().into(); @@ -683,7 +683,7 @@ fn handle_point_insert(pipe: &mut UiRenderPipe) { anim: anim.clone(), }, }), - Some(&format!("pos-anim-repl-anim-{}", index)), + Some(&format!("pos-anim-repl-anim-{index}")), ); } if let Some(((index, anim, props), anim_point)) = @@ -697,7 +697,7 @@ fn handle_point_insert(pipe: &mut UiRenderPipe) { anim: anim.clone(), }, }), - Some(&format!("color-anim-repl-anim-{}", index)), + Some(&format!("color-anim-repl-anim-{index}")), ); } if let Some(((index, anim, props), anim_point)) = @@ -711,7 +711,7 @@ fn handle_point_insert(pipe: &mut UiRenderPipe) { anim: anim.clone(), }, }), - Some(&format!("sound-anim-repl-anim-{}", index)), + Some(&format!("sound-anim-repl-anim-{index}")), ); } } @@ -729,7 +729,7 @@ fn handle_points_changed(pipe: &mut UiRenderPipe) { if !props.selected_point_channels.is_empty() || !props.selected_points.is_empty() { client.execute( gen_action(*index, anim), - Some(&format!("{}-anim-repl-anim-{}", prefix, index)), + Some(&format!("{prefix}-anim-repl-anim-{index}")), ); } } @@ -798,7 +798,7 @@ fn handle_point_delete( anim.points.remove(point_index); client.execute( gen_action(*index, anim), - Some(&format!("{}-anim-repl-anim-{}", group_name, index)), + Some(&format!("{group_name}-anim-repl-anim-{index}")), ); fn new_point(p: usize, point_index: usize) -> Option { diff --git a/game/editor/src/ui/auto_mapper/auto_mapper.rs b/game/editor/src/ui/auto_mapper/auto_mapper.rs index 5ecf5064..055ecd02 100644 --- a/game/editor/src/ui/auto_mapper/auto_mapper.rs +++ b/game/editor/src/ui/auto_mapper/auto_mapper.rs @@ -369,7 +369,7 @@ fn render_op_list( if let Some((operator, next)) = operation { ui.add_space(3.0); ComboBox::new( - format!("tile-auto-mapper-creator-operator-ty-{}", counter), + format!("tile-auto-mapper-creator-operator-ty-{counter}"), "", ) .selected_text(match operator { diff --git a/game/editor/src/ui/group_and_layer/group_props.rs b/game/editor/src/ui/group_and_layer/group_props.rs index 4df17b48..e7fb9217 100644 --- a/game/editor/src/ui/group_and_layer/group_props.rs +++ b/game/editor/src/ui/group_and_layer/group_props.rs @@ -515,8 +515,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st } }; - if window_res.is_some() { - let window_res = window_res.as_ref().unwrap(); + if let Some(window_res) = &window_res { ui_state.add_blur_rect(window_res.response.rect, 0.0); } diff --git a/game/editor/src/ui/group_and_layer/layer_props.rs b/game/editor/src/ui/group_and_layer/layer_props.rs index 28e42596..9be87749 100644 --- a/game/editor/src/ui/group_and_layer/layer_props.rs +++ b/game/editor/src/ui/group_and_layer/layer_props.rs @@ -2,7 +2,7 @@ use std::{collections::BTreeMap, ops::RangeInclusive}; use base::hash::fmt_hash; use egui::{Button, Checkbox, Color32, ComboBox, DragValue, InnerResponse}; -use game_base::mapdef_06::DdraceTileNum; +use legacy_map::mapdef_06::DdraceTileNum; use map::{ map::groups::layers::{ design::MapLayerTile, @@ -591,7 +591,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st // color anim fn combobox_name(ty: &str, index: usize, name: &str) -> String { name.is_empty() - .then_some(format!("{ty} #{}", index)) + .then_some(format!("{ty} #{index}")) .unwrap_or_else(|| name.to_owned()) } ui.label("Color anim"); diff --git a/game/editor/src/ui/group_and_layer/quad_props.rs b/game/editor/src/ui/group_and_layer/quad_props.rs index 7c6dabe6..8b3cc056 100644 --- a/game/editor/src/ui/group_and_layer/quad_props.rs +++ b/game/editor/src/ui/group_and_layer/quad_props.rs @@ -257,7 +257,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st if matches!(point, QuadPointerDownPoint::Center) { fn combobox_name(ty: &str, index: usize, name: &str) -> String { name.is_empty() - .then_some(format!("{ty} #{}", index)) + .then_some(format!("{ty} #{index}")) .unwrap_or_else(|| name.to_owned()) } if can_change_pos_anim { diff --git a/game/editor/src/ui/group_and_layer/sound_props.rs b/game/editor/src/ui/group_and_layer/sound_props.rs index bf955a9c..cee92a5f 100644 --- a/game/editor/src/ui/group_and_layer/sound_props.rs +++ b/game/editor/src/ui/group_and_layer/sound_props.rs @@ -236,7 +236,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st if matches!(point, SoundPointerDownPoint::Center) { fn combobox_name(ty: &str, index: usize, name: &str) -> String { name.is_empty() - .then_some(format!("{ty} #{}", index)) + .then_some(format!("{ty} #{index}")) .unwrap_or_else(|| name.to_owned()) } if can_change_pos_anim { diff --git a/game/editor/src/ui/left_panel/groups_and_layers.rs b/game/editor/src/ui/left_panel/groups_and_layers.rs index ecbd6d5f..40a914c5 100644 --- a/game/editor/src/ui/left_panel/groups_and_layers.rs +++ b/game/editor/src/ui/left_panel/groups_and_layers.rs @@ -213,7 +213,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { let mut selected_layers = Vec::new(); let mut selected_groups = Vec::new(); for (g, group) in groups.iter_mut().enumerate() { - CollapsingState::load_with_default_open(ui.ctx(), format!("{}-{g}", id).into(), true) + CollapsingState::load_with_default_open(ui.ctx(), format!("{id}-{g}").into(), true) .show_header(ui, |ui| { ui.with_layout(Layout::right_to_left(egui::Align::Min), |ui| { let hidden = group.editor_attr_mut().hidden; diff --git a/game/editor/src/ui/main_frame.rs b/game/editor/src/ui/main_frame.rs index d561e5dc..1f8d54ce 100644 --- a/game/editor/src/ui/main_frame.rs +++ b/game/editor/src/ui/main_frame.rs @@ -1,4 +1,5 @@ use egui::{Align2, Color32, FontId, Modal, ModifierNames, Window}; +use map::utils::file_ext_or_twmap_tar; use ui_base::types::{UiRenderPipe, UiState}; use crate::network::{NetworkClientState, NetworkState}; @@ -95,14 +96,14 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m "Disconnected. You can still save the map, \ but not edit it anymore.", ); - ui.label(format!("Reason: {}", reason)); + ui.label(format!("Reason: {reason}")); }); } NetworkClientState::Err(reason) => { Window::new("Network") .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) .show(ui.ctx(), |ui| { - ui.label(format!("Error: {}", reason)); + ui.label(format!("Error: {reason}")); }); } } @@ -125,14 +126,11 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_state: &m Modal::new("hovered-file-drag-zones".into()).show(ui.ctx(), |ui| { ui.set_width(ui.ctx().screen_rect().width()); ui.set_height(ui.ctx().screen_rect().height()); - let ext = hovered_file - .extension() - .and_then(|e| e.to_str()) - .unwrap_or(""); + let ext = file_ext_or_twmap_tar(hovered_file).unwrap_or(""); let drop_areas = match ext { "map" => vec!["Drop the legacy map file here to open a new tab."], - "twmap" => vec!["Drop the map file here to open a new tab."], + "twmap.tar" => vec!["Drop the map file here to open a new tab."], "png" => vec![ "Drop the quad texture here to add it to the current map.", "Drop the tile texture here to add it to the current map.", diff --git a/game/editor/src/ui/mapper_cursors/main_frame.rs b/game/editor/src/ui/mapper_cursors/main_frame.rs index de836399..6d026312 100644 --- a/game/editor/src/ui/mapper_cursors/main_frame.rs +++ b/game/editor/src/ui/mapper_cursors/main_frame.rs @@ -1,9 +1,9 @@ -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::CameraInterface; use egui::Color32; use graphics::handles::canvas::canvas::GraphicsCanvasHandle; use graphics_types::rendering::State; -use crate::ui::user_data::EditorTabsRefMut; +use crate::{map::EditorMapInterface, ui::user_data::EditorTabsRefMut}; pub fn render( ui: &mut egui::Ui, @@ -17,19 +17,10 @@ pub fn render( .iter() .filter(|c| c.server_id != tab.client.server_id) { - let points = RenderTools::canvas_points_of_group_attr( - CanvasType::Handle(canvas_handle), - tab.map.groups.user.pos.x, - tab.map.groups.user.pos.y, - 100.0, - 100.0, - 0.0, - 0.0, - tab.map.groups.user.zoom, - tab.map.groups.user.parallax_aware_zoom, - ); let mut state = State::new(); - state.map_canvas(points[0], points[1], points[2], points[3]); + tab.map + .game_camera() + .project(canvas_handle, &mut state, None); let size = ui.ctx().screen_rect().size(); let (x0, y0, x1, y1) = state.get_canvas_mapping(); diff --git a/game/editor/src/ui/server_config_variables/panel.rs b/game/editor/src/ui/server_config_variables/panel.rs index 0bf38339..f8ac60b1 100644 --- a/game/editor/src/ui/server_config_variables/panel.rs +++ b/game/editor/src/ui/server_config_variables/panel.rs @@ -119,7 +119,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st ); if let Some(comment) = &args.comment { job.append( - &format!(" # {}", comment), + &format!(" # {comment}"), 0.0, TextFormat { color: Color32::GRAY, diff --git a/game/editor/src/ui/server_settings/panel.rs b/game/editor/src/ui/server_settings/panel.rs index abd0fc54..1bfb027d 100644 --- a/game/editor/src/ui/server_settings/panel.rs +++ b/game/editor/src/ui/server_settings/panel.rs @@ -71,7 +71,7 @@ pub fn render_server_commands(ui: &mut egui::Ui, tab: &mut EditorTab) { ); if let Some(comment) = &cmd.comment { job.append( - &format!(" # {}", comment), + &format!(" # {comment}"), 0.0, TextFormat { color: Color32::GRAY, diff --git a/game/editor/src/ui/tool_overlays/tile_brush.rs b/game/editor/src/ui/tool_overlays/tile_brush.rs index 59d5a0ac..4058f1a5 100644 --- a/game/editor/src/ui/tool_overlays/tile_brush.rs +++ b/game/editor/src/ui/tool_overlays/tile_brush.rs @@ -148,7 +148,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { let rect = ui.painter().text( pos, egui::Align2::LEFT_TOP, - format!("{}x", count), + format!("{count}x"), FontId::monospace(24.0), Color32::WHITE, ); @@ -182,7 +182,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe) { let rect = ui.painter().text( pos, egui::Align2::LEFT_TOP, - format!("{}x", count), + format!("{count}x"), FontId::monospace(24.0), Color32::WHITE, ); diff --git a/game/editor/src/ui/top_toolbar/tile_mirror.rs b/game/editor/src/ui/top_toolbar/tile_mirror.rs index 4716a90e..5dd008dc 100644 --- a/game/editor/src/ui/top_toolbar/tile_mirror.rs +++ b/game/editor/src/ui/top_toolbar/tile_mirror.rs @@ -1,6 +1,5 @@ use std::sync::Arc; -use game_base::mapdef_06::tile_can_rotate; use graphics::{ graphics_mt::GraphicsMultiThreaded, handles::{ @@ -9,6 +8,7 @@ use graphics::{ shader_storage::shader_storage::GraphicsShaderStorageHandle, }, }; +use legacy_map::mapdef_06::tile_can_rotate; use map::map::groups::layers::tiles::{ rotate_by_plus_90, MapTileLayerPhysicsTiles, MapTileLayerTiles, TileBase, TileFlags, }; @@ -847,10 +847,7 @@ fn generate_client_action( h: range.h, }, }), - Some(&format!( - "selection_physics_tile_layer_tools_{}", - layer_index - )), + Some(&format!("selection_physics_tile_layer_tools_{layer_index}")), ); } } @@ -882,8 +879,7 @@ fn generate_client_action( }, }), Some(&format!( - "selection_tile_layer_tools_{}_{}_{}", - is_background, group_index, layer_index + "selection_tile_layer_tools_{is_background}_{group_index}_{layer_index}" )), ); } diff --git a/game/editor/src/ui/top_toolbar/toolbar.rs b/game/editor/src/ui/top_toolbar/toolbar.rs index 3c0107a3..52663de3 100644 --- a/game/editor/src/ui/top_toolbar/toolbar.rs +++ b/game/editor/src/ui/top_toolbar/toolbar.rs @@ -688,7 +688,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st }], }, }), - Some(&format!("quad-add design {}", layer_index)), + Some(&format!("quad-add design {layer_index}")), ); } } @@ -788,7 +788,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st }, }, ), - Some(&format!("sound-add design {}", layer_index)), + Some(&format!("sound-add design {layer_index}")), ); } } diff --git a/game/editor/src/ui/top_toolbar/tune.rs b/game/editor/src/ui/top_toolbar/tune.rs index 02f09b44..dd12d483 100644 --- a/game/editor/src/ui/top_toolbar/tune.rs +++ b/game/editor/src/ui/top_toolbar/tune.rs @@ -4,7 +4,7 @@ use egui::{ scroll_area::ScrollBarVisibility, text::LayoutJob, Align, Color32, DragValue, FontId, Frame, Layout, ScrollArea, TextEdit, TextFormat, }; -use game_base::mapdef_06::DdraceTileNum; +use legacy_map::mapdef_06::DdraceTileNum; use map::{ map::{command_value::CommandValue, groups::layers::physics::MapLayerTilePhysicsTuneZone}, skeleton::groups::layers::physics::MapLayerTunePhysicsSkeleton, @@ -123,7 +123,7 @@ pub fn render_tune_overview( act.new_tunes = new_tunes; client.execute( EditorAction::ChangeTuneZone(act), - Some(&format!("tune_zone_change_zones-{}", zone_val)), + Some(&format!("tune_zone_change_zones-{zone_val}")), ); } } @@ -191,7 +191,7 @@ pub fn render_tune_overview( act.new_enter_msg = new_enter_msg; client.execute( EditorAction::ChangeTuneZone(act), - Some(&format!("tune_zone_change_zones-{}", tune_index)), + Some(&format!("tune_zone_change_zones-{tune_index}")), ); } cancel(overview_extra); @@ -236,7 +236,7 @@ pub fn render_tune_overview( act.new_leave_msg = new_leave_msg; client.execute( EditorAction::ChangeTuneZone(act), - Some(&format!("tune_zone_change_zones-{}", tune_index)), + Some(&format!("tune_zone_change_zones-{tune_index}")), ); } cancel(overview_extra); @@ -259,7 +259,7 @@ pub fn render_tune_overview( ); if let Some(comment) = &val.comment { job.append( - &format!(" # {}", comment), + &format!(" # {comment}"), 0.0, TextFormat { color: Color32::GRAY, @@ -275,8 +275,7 @@ pub fn render_tune_overview( client.execute( EditorAction::ChangeTuneZone(act), Some(&format!( - "tune_zone_change_zones-{}", - tune_index + "tune_zone_change_zones-{tune_index}" )), ); } @@ -335,8 +334,8 @@ pub fn render_tune_overview( client.execute( EditorAction::ChangeTuneZone(act), Some(&format!( - "tune_zone_change_zones-{}", - tune_index + "tune_zone_change_zones-\ + {tune_index}" )), ); } @@ -479,10 +478,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st new_leave_msg: leave_msg, old_leave_msg: tune.leave_extra.clone(), }), - Some(&format!( - "tune_zone_change_zones-{}", - active_tune - )), + Some(&format!("tune_zone_change_zones-{active_tune}")), ); } tune.name = tune_name; @@ -503,7 +499,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st } else { tune.name.clone() }; - ui.menu_button(format!("Tunes of {}", cur_tune_name,), |ui| { + ui.menu_button(format!("Tunes of {cur_tune_name}",), |ui| { context_menu_extra_open = true; ui.label("Enter message:"); @@ -540,7 +536,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st new_leave_msg: tune.leave_extra.clone(), old_leave_msg, }), - Some(&format!("tune_zone_change_zones-{}", active_tune)), + Some(&format!("tune_zone_change_zones-{active_tune}")), ); } @@ -560,7 +556,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st ); if let Some(comment) = &val.comment { job.append( - &format!(" # {}", comment), + &format!(" # {comment}"), 0.0, TextFormat { color: Color32::GRAY, @@ -584,10 +580,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st new_leave_msg: tune.leave_extra.clone(), old_leave_msg: tune.leave_extra.clone(), }), - Some(&format!( - "tune_zone_change_zones-{}", - active_tune - )), + Some(&format!("tune_zone_change_zones-{active_tune}")), ); } }); @@ -596,7 +589,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st let val = &mut layer.user.number_extra_text; ui.add_space(10.0); ui.separator(); - ui.label(format!("Add commands for tune zone {}.", cur_tune_name)); + ui.label(format!("Add commands for tune zone {cur_tune_name}.")); ui.horizontal(|ui| { ui.label("Tune command:"); ui.add(TextEdit::singleline(val).hint_text("gravtiy 0.25")); @@ -642,7 +635,7 @@ pub fn render(ui: &mut egui::Ui, pipe: &mut UiRenderPipe, ui_st new_leave_msg: leave_msg, old_leave_msg: tune.leave_extra.clone(), }), - Some(&format!("tune_zone_change_zones-{}", active_tune)), + Some(&format!("tune_zone_change_zones-{active_tune}")), ); } } diff --git a/game/editor/src/ui/user_data.rs b/game/editor/src/ui/user_data.rs index d4690d5b..456b5c7f 100644 --- a/game/editor/src/ui/user_data.rs +++ b/game/editor/src/ui/user_data.rs @@ -186,7 +186,7 @@ impl EditorMenuDialogMode { .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) .movable(false) .initial_directory(open_path) - .default_file_name("ctf1.twmap"), + .default_file_name("ctf1.twmap.tar"), )); file_dialog.pick_file(); @@ -203,7 +203,7 @@ impl EditorMenuDialogMode { .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) .movable(false) .initial_directory(open_path) - .default_file_name("ctf1.twmap"), + .default_file_name("ctf1.twmap.tar"), )); file_dialog.save_file(); @@ -220,7 +220,7 @@ impl EditorMenuDialogMode { .anchor(Align2::CENTER_CENTER, (0.0, 0.0)) .movable(false) .initial_directory(open_path) - .default_file_name("ctf1.twmap"), + .default_file_name("ctf1.twmap.tar"), )); file_dialog.pick_file(); diff --git a/game/editor/src/ui/utils.rs b/game/editor/src/ui/utils.rs index 6b74f00c..fe6ad576 100644 --- a/game/editor/src/ui/utils.rs +++ b/game/editor/src/ui/utils.rs @@ -5,7 +5,7 @@ use crate::map::{EditorGroup, EditorLayer, EditorPhysicsLayer, EditorResources}; pub fn group_name(group: &EditorGroup, index: usize) -> String { if group.name.is_empty() { - format!("Group #{}", index) + format!("Group #{index}") } else { format!("Group \"{}\"", group.name) } @@ -63,7 +63,7 @@ pub fn layer_name( } else { ( icon, - LayoutJob::simple(format!("#{}", index), Default::default(), text_color, 0.0), + LayoutJob::simple(format!("#{index}"), Default::default(), text_color, 0.0), ) } } @@ -80,5 +80,5 @@ pub fn layer_name_phy(layer: &EditorPhysicsLayer, index: usize) -> String { EditorPhysicsLayer::Switch(_) => "Switch", EditorPhysicsLayer::Tune(_) => "Tune", }; - format!("#{} {layer_name}", index) + format!("#{index} {layer_name}") } diff --git a/game/editor/src/utils.rs b/game/editor/src/utils.rs index 3f1e45f1..ac40b64f 100644 --- a/game/editor/src/utils.rs +++ b/game/editor/src/utils.rs @@ -1,7 +1,9 @@ -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::{Camera, CameraInterface}; use egui::{vec2, Rect}; use graphics::handles::canvas::canvas::GraphicsCanvasHandle; -use math::math::vector::vec2; +use graphics_types::rendering::State; +use map::map::groups::MapGroupAttr; +use math::math::vector::{ffixed, fvec2, vec2}; pub type UiCanvasSize = Rect; @@ -18,17 +20,23 @@ pub fn ui_pos_to_world_pos_and_world_height( parallax_y: f32, parallax_aware_zoom: bool, ) -> (vec2, f32) { - let points = RenderTools::canvas_points_of_group_attr( - CanvasType::Handle(canvas_handle), - center_x, - center_y, - parallax_x, - parallax_y, - offset_x, - offset_y, + let mut fake_state = State::new(); + Camera::new( + vec2::new(center_x, center_y), zoom, + None, parallax_aware_zoom, + ) + .project( + canvas_handle, + &mut fake_state, + Some(&MapGroupAttr { + offset: fvec2::new(ffixed::from_num(offset_x), ffixed::from_num(offset_y)), + parallax: fvec2::new(ffixed::from_num(parallax_x), ffixed::from_num(parallax_y)), + clipping: None, + }), ); + let (tl_x, tl_y, br_x, br_y) = fake_state.get_canvas_mapping(); let x = inp.x; let y = inp.y; @@ -39,10 +47,10 @@ pub fn ui_pos_to_world_pos_and_world_height( let x_ratio = x / size.x; let y_ratio = y / size.y; - let x = points[0] + x_ratio * (points[2] - points[0]); - let y = points[1] + y_ratio * (points[3] - points[1]); + let x = tl_x + x_ratio * (br_x - tl_x); + let y = tl_y + y_ratio * (br_y - tl_y); - (vec2::new(x, y), points[3] - points[1]) + (vec2::new(x, y), br_y - tl_y) } pub fn ui_pos_to_world_pos( diff --git a/game/egui-timeline/src/timeline.rs b/game/egui-timeline/src/timeline.rs index 7c7e8f86..ca19366e 100644 --- a/game/egui-timeline/src/timeline.rs +++ b/game/egui-timeline/src/timeline.rs @@ -304,7 +304,7 @@ impl Timeline { } else { Align2::CENTER_CENTER }, - format!("{}", x), + format!("{x}"), egui::FontId::proportional(font_size), Color32::GRAY, ); diff --git a/game/game-base/Cargo.toml b/game/game-base/Cargo.toml index 72bbfea4..0d06513f 100644 --- a/game/game-base/Cargo.toml +++ b/game/game-base/Cargo.toml @@ -6,21 +6,14 @@ edition = "2021" [dependencies] math = { path = "../../lib/math" } base = { path = "../../lib/base" } -image-utils = { path = "../../lib/image-utils" } -graphics-types = { path = "../../lib/graphics-types" } config = { path = "../../lib/config" } command-parser = { path = "../../lib/command-parser" } pool = { path = "../../lib/pool" } hiarc = { path = "../../lib/hiarc", features = ["enable_time"] } -map = { path = "../map" } game-interface = { path = "../game-interface" } game-config = { path = "../game-config" } -rayon = "1.10.0" -num-derive = "0.4.2" -num-traits = "0.2.19" -flate2 = "1.1.1" anyhow = { version = "1.0.98", features = ["backtrace"] } hashlink = { git = "https://github.com/Jupeyy/hashlink/", branch = "ddnet", features = ["serde", "serde_impl"] } serde = { version = "1.0.219", features = ["derive"] } @@ -28,10 +21,6 @@ indexmap = "2.9.0" time = { version = "0.3.41", features = ["serde"] } serde_with = "3.12.0" thiserror = "2.0.12" -itertools = "0.14.0" - -[package.metadata.cargo-machete] -ignored = ["num-traits"] [dev-dependencies] rustc-hash = "2.1.1" diff --git a/game/game-base/src/lib.rs b/game/game-base/src/lib.rs index bd170ae9..c95e4d81 100644 --- a/game/game-base/src/lib.rs +++ b/game/game-base/src/lib.rs @@ -2,11 +2,9 @@ pub mod assets_url; pub mod browser_favorite_player; pub mod config_helper; pub mod connecting_log; -pub mod datafile; pub mod game_types; pub mod indexmap_tests; pub mod local_server_info; -pub mod mapdef_06; pub mod network; pub mod player_input; pub mod server_browser; diff --git a/game/game-base/src/local_server_info.rs b/game/game-base/src/local_server_info.rs index 78eabcc5..cf829b6a 100644 --- a/game/game-base/src/local_server_info.rs +++ b/game/game-base/src/local_server_info.rs @@ -41,6 +41,14 @@ pub struct LocalServerConnectInfo { pub server_cert_hash: [u8; 32], } +#[derive(Debug)] +pub struct LocalServerStateReady { + pub connect_info: LocalServerConnectInfo, + pub browser_info: Option, + // must be last + pub thread: LocalServerThread, +} + #[derive(Debug, Default)] pub enum LocalServerState { #[default] @@ -50,12 +58,7 @@ pub enum LocalServerState { // must be last thread: LocalServerThread, }, - Ready { - connect_info: LocalServerConnectInfo, - // must be last - thread: LocalServerThread, - browser_info: Option, - }, + Ready(Box), } #[derive(Debug, Default)] diff --git a/game/game-base/src/server_browser.rs b/game/game-base/src/server_browser.rs index b59fdee5..a8c07f2c 100644 --- a/game/game-base/src/server_browser.rs +++ b/game/game-base/src/server_browser.rs @@ -331,7 +331,15 @@ impl ServerBrowserData { .info .name .to_lowercase() - .contains(&filter.search.to_lowercase())) + .contains(&filter.search.to_lowercase()) + || server.info.players.iter().any(|p| { + p.name + .to_lowercase() + .contains(&filter.search.to_lowercase()) + || p.clan + .to_lowercase() + .contains(&filter.search.to_lowercase()) + })) && (!filter.has_players || !server.info.players.is_empty()) && (!filter.filter_full_servers || server.info.players.len() < server.info.max_ingame_players as usize) diff --git a/game/game-config-fs/src/fs.rs b/game/game-config-fs/src/fs.rs index c6162f81..41562737 100644 --- a/game/game-config-fs/src/fs.rs +++ b/game/game-config-fs/src/fs.rs @@ -1,9 +1,9 @@ use std::path::Path; -use base_io::io::{Io, IoFileSys}; +use base_io::io::IoFileSys; use game_config::config::ConfigGame; -pub fn save(config: &ConfigGame, io: &Io) { +pub fn save(config: &ConfigGame, io: &IoFileSys) { let save_str = config.to_json_string(); if let Ok(save_str) = save_str { diff --git a/game/game-server/src/auto_map_votes.rs b/game/game-server/src/auto_map_votes.rs index dadff843..e990569b 100644 --- a/game/game-server/src/auto_map_votes.rs +++ b/game/game-server/src/auto_map_votes.rs @@ -13,7 +13,7 @@ impl AutoMapVotes { let map_files: HashSet = dir .into_iter() - .filter_map(|(p, _)| p.ends_with(".twmap").then::(|| p.into())) + .filter_map(|(p, _)| p.ends_with(".twmap.tar").then::(|| p.into())) .collect(); let map_files = { diff --git a/game/game-server/src/network_plugins/accounts_only.rs b/game/game-server/src/network_plugins/accounts_only.rs index 112709d7..504e7f98 100644 --- a/game/game-server/src/network_plugins/accounts_only.rs +++ b/game/game-server/src/network_plugins/accounts_only.rs @@ -35,13 +35,11 @@ impl AccountsOnly { #[async_trait] impl NetworkPluginConnection for AccountsOnly { - #[must_use] async fn on_incoming(&self, _remote_addr: &SocketAddr) -> bool { // This plugin prefers proper error messages instead // of ignoring connections true } - #[must_use] async fn on_connect( &self, _id: &NetworkConnectionId, diff --git a/game/game-server/src/network_plugins/cert_ban.rs b/game/game-server/src/network_plugins/cert_ban.rs index 4f00e5f6..d11bbb3a 100644 --- a/game/game-server/src/network_plugins/cert_ban.rs +++ b/game/game-server/src/network_plugins/cert_ban.rs @@ -120,13 +120,11 @@ impl CertBans { #[async_trait] impl NetworkPluginConnection for CertBans { - #[must_use] async fn on_incoming(&self, _remote_addr: &SocketAddr) -> bool { // This plugin prefers proper error messages instead // of ignoring connections true } - #[must_use] async fn on_connect( &self, id: &NetworkConnectionId, diff --git a/game/game-server/src/server.rs b/game/game-server/src/server.rs index ef14dde8..4cb8c28b 100644 --- a/game/game-server/src/server.rs +++ b/game/game-server/src/server.rs @@ -84,7 +84,10 @@ use crate::{ use game_base::{ config_helper::handle_config_variable_cmd, game_types::{is_next_tick, time_until_tick}, - local_server_info::{LocalServerConnectInfo, LocalServerInfo, LocalServerState, ServerDbgGame}, + local_server_info::{ + LocalServerConnectInfo, LocalServerInfo, LocalServerState, LocalServerStateReady, + ServerDbgGame, + }, network::{ messages::{ AddLocalPlayerResponseError, MsgClChatMsg, MsgClLoadVotes, MsgClReadyResponse, @@ -867,7 +870,7 @@ impl Server { thread, } = std::mem::take(&mut *state) { - *state = LocalServerState::Ready { + *state = LocalServerState::Ready(Box::new(LocalServerStateReady { connect_info: LocalServerConnectInfo { sock_addr: sock_addrs[0], dbg_games: Default::default(), @@ -877,7 +880,7 @@ impl Server { }, thread, browser_info: None, - }; + })); } } @@ -1096,13 +1099,10 @@ impl Server { iter.enumerate().for_each(|(index, (net_id, _))| { self.network.send_unordered_to( &ServerToClientMessage::QueueInfo( - format!( - "The server is full.\nYou are queued at position: #{}", - index - ) - .as_str() - .try_into() - .unwrap(), + format!("The server is full.\nYou are queued at position: #{index}") + .as_str() + .try_into() + .unwrap(), ), net_id, ); @@ -1897,7 +1897,7 @@ impl Server { .map(|id| id.to_string()) .collect::>() .join(", "); - res = format!("Banned the following id(s): {}", text); + res = format!("Banned the following id(s): {text}"); })?; anyhow::Ok(res) } @@ -1918,7 +1918,7 @@ impl Server { .map(|id| id.to_string()) .collect::>() .join(", "); - res = format!("Kicked the following id(s): {}", text); + res = format!("Kicked the following id(s): {text}"); }, )?; anyhow::Ok(res) @@ -2764,15 +2764,15 @@ impl Server { .flat_map(|s| s.1.world.projectiles.iter()) .collect(); - let players = format!("{:?}", player_infos); - let projectiles = format!("{:?}", projectiles); + let players = format!("{player_infos:?}"); + let projectiles = format!("{projectiles:?}"); let inputs = format!("{:?}", inputs.map(|inp| inp.collect::>())); - if let LocalServerState::Ready { - connect_info: LocalServerConnectInfo { dbg_games, .. }, - .. - } = &mut *shared_info.state.lock().unwrap() - { + if let LocalServerState::Ready(ready) = &mut *shared_info.state.lock().unwrap() { + let LocalServerStateReady { + connect_info: LocalServerConnectInfo { dbg_games, .. }, + .. + } = ready.as_mut(); let dbg_game = dbg_games.get(&cur_tick); if let Some(dbg_game) = dbg_game { let now = std::time::Instant::now(); @@ -2896,13 +2896,14 @@ impl Server { requires_account: self.accounts_only, }; - if let Some(LocalServerState::Ready { browser_info, .. }) = self + if let Some(LocalServerState::Ready(ready)) = self .shared_info .upgrade() .as_ref() .and_then(|info| info.state.lock().ok()) .as_deref_mut() { + let LocalServerStateReady { browser_info, .. } = ready.as_mut(); *browser_info = Some(register_info.clone()) } @@ -2944,28 +2945,25 @@ impl Server { port: u16| { Box::pin(async move { for master_server in master_servers { + let headers = vec![ + ( + "Address", + format!( + "ddrs-0.1+quic://connecting-address.invalid:{port}" + ) + .as_str(), + ) + .into(), + ("Secret", fmt_hash(&secret).as_str()).into(), + ("Challenge-Secret", fmt_hash(&challenge_secret).as_str()) + .into(), + ("Info-Serial", serial.to_string().as_str()).into(), + ("content-type", "application/json").into(), + ]; match http .custom_request( master_server.try_into().unwrap(), - vec![ - ( - "Address", - format!( - "ddrs-0.1+quic://connecting-address.invalid:{}", - port - ) - .as_str(), - ) - .into(), - ("Secret", fmt_hash(&secret).as_str()).into(), - ( - "Challenge-Secret", - fmt_hash(&challenge_secret).as_str(), - ) - .into(), - ("Info-Serial", serial.to_string().as_str()).into(), - ("content-type", "application/json").into(), - ], + headers, Some(register_info.as_bytes().to_vec()), ) .await diff --git a/game/game-server/src/server_game.rs b/game/game-server/src/server_game.rs index ad0d78ee..6b250e38 100644 --- a/game/game-server/src/server_game.rs +++ b/game/game-server/src/server_game.rs @@ -24,7 +24,10 @@ use game_database::traits::DbInterface; use game_state_wasm::game::state_wasm_manager::{ GameStateMod, GameStateWasmManager, STATE_MODS_PATH, }; -use map::map::{resources::MapResourceMetaData, Map}; +use map::{ + file::MapFileReader, + map::{resources::MapResourceMetaData, Map}, +}; use network::network::connection::NetworkConnectionId; use pool::{datatypes::PoolFxLinkedHashMap, pool::Pool}; @@ -84,10 +87,10 @@ impl ServerMap { let map_file_str = map_name.to_string(); let fs = io.fs.clone(); let map = io.rt.spawn(async move { - let map_path = format!("map/maps/{}.twmap", map_file_str); + let map_path = format!("map/maps/{map_file_str}.twmap.tar"); let map_file = fs.read_file(map_path.as_ref()).await?; - let (resources, _) = Map::read_resources_and_header(&map_file)?; + let resources = Map::read_resources_and_header(&MapFileReader::new(map_file.clone())?)?; let mut resource_files: HashMap> = Default::default(); let (names, files): (Vec<_>, Vec<_>) = { @@ -186,7 +189,7 @@ impl ServerMap { rest_path.into() }; let fs = io.fs.clone(); - let cache = Arc::new(Cache::<20250418>::new("legacy-to-new-map-server", io)); + let cache = Arc::new(Cache::<20250610>::new("legacy-to-new-map-server", io)); let map_name = map_name.to_string(); let tp = runtime_thread_pool.clone(); @@ -219,8 +222,7 @@ impl ServerMap { legacy map loading failed too: {err}" ) })?; - let mut map_bytes = Vec::new(); - map.map.write(&mut map_bytes, &tp)?; + let map_bytes = map.map.write(&tp)?; let mut resource_files: HashMap> = Default::default(); for (blake3_hash, resource) in map.resources.images.into_iter() { let path = format!( @@ -267,7 +269,7 @@ impl ServerMap { }) .get_storage()?; - let map = Map::read(&map_file, runtime_thread_pool)?; + let map = Map::read(&MapFileReader::new(map_file.clone())?, runtime_thread_pool)?; let load_resources = || { let mut resource_files: HashMap> = Default::default(); for path in map @@ -457,7 +459,7 @@ impl ServerGame { None, ), game_mod => { - let path = format!("{}/{}.wasm", STATE_MODS_PATH, game_mod); + let path = format!("{STATE_MODS_PATH}/{game_mod}.wasm"); let file_path = path.clone(); let (file, wasm_module) = { let fs = io.fs.clone(); @@ -498,7 +500,7 @@ impl ServerGame { let fs_change_watcher = game_mod_blake3_hash.is_some().then(|| { io.fs.watch_for_change( STATE_MODS_PATH.as_ref(), - Some(format!("{}.wasm", game_mod_name).as_ref()), + Some(format!("{game_mod_name}.wasm").as_ref()), ) }); @@ -607,7 +609,7 @@ impl ServerGame { ) -> anyhow::Result { HttpDownloadServer::new( vec![( - format!("map/maps/{}_{}.twmap", map_name, fmt_hash(&map_hash)), + format!("map/maps/{}_{}.twmap.tar", map_name, fmt_hash(&map_hash)), map_file.to_vec(), )] .into_iter() diff --git a/game/game-state-wasm/src/game/state_wasm_manager.rs b/game/game-state-wasm/src/game/state_wasm_manager.rs index 548be7a6..79774df6 100644 --- a/game/game-state-wasm/src/game/state_wasm_manager.rs +++ b/game/game-state-wasm/src/game/state_wasm_manager.rs @@ -61,7 +61,7 @@ pub enum GameStateMod { enum GameStateWrapper { Native(Box), //Ddnet(Ddnet), - Wasm(StateWasm), + Wasm(Box), } impl GameStateWrapper { @@ -69,7 +69,7 @@ impl GameStateWrapper { match self { Self::Native(state) => state.as_ref(), //GameStateWrapper::Ddnet(state) => state, - Self::Wasm(state) => state, + Self::Wasm(state) => state.as_ref(), } } @@ -77,7 +77,7 @@ impl GameStateWrapper { match self { Self::Native(state) => state.as_mut(), //GameStateWrapper::Ddnet(state) => state, - Self::Wasm(state) => state, + Self::Wasm(state) => state.as_mut(), } } } @@ -152,7 +152,7 @@ impl GameStateWasmManager { io.rt.clone(), db, )?; - (GameStateWrapper::Wasm(state), info) + (GameStateWrapper::Wasm(Box::new(state)), info) } }; Ok(Self { diff --git a/game/legacy-map/Cargo.toml b/game/legacy-map/Cargo.toml new file mode 100644 index 00000000..329383c6 --- /dev/null +++ b/game/legacy-map/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "legacy-map" +version = "0.1.0" +edition = "2021" + +[dependencies] +image-utils = { path = "../../lib/image-utils" } +graphics-types = { path = "../../lib/graphics-types" } +hiarc = { path = "../../lib/hiarc", features = ["derive"] } +math = { path = "../../lib/math" } +base = { path = "../../lib/base" } +config = { path = "../../lib/config" } + +map = { path = "../map" } + +rayon = "1.10.0" +num-derive = "0.4.2" +flate2 = "1.1.1" +itertools = "0.14.0" +num-traits = "0.2.19" +anyhow = { version = "1.0.98", features = ["backtrace"] } +hashlink = { git = "https://github.com/Jupeyy/hashlink/", branch = "ddnet", features = ["serde", "serde_impl"] } +serde = { version = "1.0.219", features = ["derive"] } +time = { version = "0.3.41", features = ["serde"] } + +[package.metadata.cargo-machete] +ignored = ["num-traits"] diff --git a/game/game-base/src/datafile.rs b/game/legacy-map/src/datafile.rs similarity index 95% rename from game/game-base/src/datafile.rs rename to game/legacy-map/src/datafile.rs index ccc53765..dc254a59 100644 --- a/game/game-base/src/datafile.rs +++ b/game/legacy-map/src/datafile.rs @@ -1,9 +1,10 @@ use std::{ borrow::Cow, - collections::HashMap, + collections::{HashMap, HashSet}, ffi::{CStr, CString}, io::{Read, Write}, mem::size_of, + ops::ControlFlow, time::Duration, }; @@ -273,6 +274,10 @@ pub struct CDatafileWrapper { // files to read, if the user of this object // wants to have support for images etc. pub read_files: LinkedHashMap, + // dup key to real key + pub duplicated_img_reads: HashMap, + // real key with a list of dup keys + pub duplicated_img_reads_list: HashMap>, } #[derive(Default)] @@ -365,6 +370,8 @@ impl CDatafileWrapper { tune_layer_index: usize::MAX, read_files: Default::default(), + duplicated_img_reads: Default::default(), + duplicated_img_reads_list: Default::default(), } } @@ -505,7 +512,7 @@ impl CDatafileWrapper { benchmark.bench("loading the map header, items and data"); // read items - thread_pool.install(|| { + let (_, _, c, _, _, _, g) = thread_pool.install(|| { join_all!( || { if !options.dont_load_map_item[MapItemTypes::Version as usize] { @@ -588,30 +595,57 @@ impl CDatafileWrapper { &mut num, ); self.images.resize_with(num as usize, MapImage::default); - self.images.par_iter_mut().enumerate().for_each(|(i, img)| { - let data = &items[start as usize + i].data[0..item_size]; - img.item_data = CMapItemImage::read_from_slice(data); + let r = self + .images + .par_iter_mut() + .enumerate() + .try_for_each(|(i, img)| { + let data = &items[start as usize + i].data[0..item_size]; + img.item_data = CMapItemImage::read_from_slice(data); - // read the image name - img.img_name = - Self::read_string(&data_file, img.item_data.image_name, data_start); - }); + // read the image name + img.img_name = match Self::read_string( + &data_file, + img.item_data.image_name, + data_start, + ) { + Ok(name) => name, + Err(lossy_name) => { + if img.item_data.external != 0 { + return ControlFlow::Break(anyhow!( + "External image contained invalid utf8 string" + )); + } + format!("{i}-{lossy_name}") + } + }; + ControlFlow::Continue(()) + }); + if let ControlFlow::Break(err) = r { + anyhow::bail!("{err}") + } self.images.iter().enumerate().for_each(|(index, img)| { if img.item_data.external != 0 { // add the external image to the read files - let r = self.read_files.insert( - format!("legacy/mapres/{}.png", img.img_name), - ReadFile::Image(index, Vec::new()), - ); - assert!( - r.is_none(), - "image with that name was already found {}", - img.img_name - ) + let key = format!("legacy/mapres/{}.png", img.img_name); + if let Some(ReadFile::Image(old_index, _)) = + self.read_files.get(&key) + { + self.duplicated_img_reads.insert(index, *old_index); + let list = self + .duplicated_img_reads_list + .entry(*old_index) + .or_default(); + list.insert(index); + } else { + self.read_files + .insert(key, ReadFile::Image(index, Vec::new())); + } } }); benchmark.bench_multi("loading the map images"); } + anyhow::Ok(()) }, || { if !options.dont_load_map_item[MapItemTypes::Envelope as usize] { @@ -690,20 +724,8 @@ impl CDatafileWrapper { let layer = CMapItemLayer::read_from_slice(data); if layer.item_layer == MapLayerTypes::Tiles as i32 { - let item_size_non_ddrace = - CMapItemLayerTilemap::size_of_without_ddrace(); - let item_size_non_ddrace_no_name = - CMapItemLayerTilemap::size_of_without_name(); - let item_size_full = size_of::(); let data_len = items[start as usize + i].data.len(); - let data = if data_len >= item_size_full { - &items[start as usize + i].data[0..item_size_full] - } else if data_len >= item_size_non_ddrace { - &items[start as usize + i].data[0..item_size_non_ddrace] - } else { - &items[start as usize + i].data - [0..item_size_non_ddrace_no_name] - }; + let data = &items[start as usize + i].data[0..data_len]; let tile_layer = CMapItemLayerTilemap::read_from_slice(data); let tile_layer_impl = MapTileLayerDetail::Tile(Vec::new()); @@ -764,7 +786,17 @@ impl CDatafileWrapper { let data = &items[start as usize + i].data[0..item_size]; let sound = CMapItemSound::read_from_slice(data); let sound_name = - Self::read_string(&data_file, sound.sound_name, data_start); + match Self::read_string(&data_file, sound.sound_name, data_start) { + Ok(name) => name, + Err(lossy_name) => { + if sound.external != 0 { + anyhow::bail!( + "External sound contained invalid utf8 string" + ); + } + format!("{i}-{lossy_name}") + } + }; self.sounds.push(MapSound { name: sound_name, def: sound, @@ -773,9 +805,12 @@ impl CDatafileWrapper { } benchmark.bench_multi("loading the map sounds"); } + anyhow::Ok(()) } - ); + ) }); + c?; + g?; if !options.dont_load_map_item[MapItemTypes::Envpoints as usize] { let has_bezier = self @@ -1260,12 +1295,16 @@ impl CDatafileWrapper { self.init_tilemap_skip(thread_pool); } - fn read_string(data_file: &CDatafile, index: i32, data_start: &[u8]) -> String { + // On fail gives a lossy string + fn read_string(data_file: &CDatafile, index: i32, data_start: &[u8]) -> Result { let data_name = Self::decompress_data(data_file, index as usize, data_start); let name_cstr = CStr::from_bytes_with_nul(data_name.as_slice()).unwrap_or_else(|_| { panic!("data name was not valid utf8 with null-termination {data_name:?}") }); - name_cstr.to_str().unwrap().to_string() + name_cstr + .to_str() + .map(|s| s.to_string()) + .map_err(|_| name_cstr.to_string_lossy().to_string()) } fn read_char_array( @@ -1382,15 +1421,29 @@ impl CDatafileWrapper { } fn read_str_from_ints(inp: &[i32]) -> String { + // many old maps have empty names (with zeroes) + if inp.iter().all(|&i| i == 0) { + return Default::default(); + } let mut res: [u8; 32] = Default::default(); ints_to_str(inp, &mut res); - CStr::from_bytes_until_nul(&res) + let mut res = CStr::from_bytes_until_nul(&res) .map_err(|err| anyhow!("reading {inp:?} - {res:?} => err: {err}")) .unwrap() .to_string_lossy() - .to_string() + .to_string(); + + if res.len() >= 32 { + res = res + .char_indices() + .filter(|(byte_offset, c)| *byte_offset + c.len_utf8() < 32) + .map(|(_, char)| char) + .collect(); + } + + res } /// images are external images @@ -1630,6 +1683,8 @@ impl CDatafileWrapper { let mut old_img_assign: HashMap = Default::default(); let mut old_img_array_assign: HashMap = Default::default(); + let mut images_high_ordered: Vec<(usize, MapImage, bool, bool)> = Default::default(); + let mut images_low_ordered: Vec<(usize, MapImage, bool, bool)> = Default::default(); let mut ext_image_count = 0; for (img_index, image) in self.images.into_iter().enumerate() { // was the image used in tile layer and/or quad layer? @@ -1638,12 +1693,24 @@ impl CDatafileWrapper { for layer in &self.layers { match layer { MapLayer::Tile(layer) => { - if layer.0.image == img_index as i32 { + if layer.0.image == img_index as i32 + || (layer.0.image > 0 + && self + .duplicated_img_reads_list + .get(&img_index) + .is_some_and(|list| list.contains(&(layer.0.image as usize)))) + { in_tile_layer = true; } } MapLayer::Quads(layer) => { - if layer.0.image == img_index as i32 { + if layer.0.image == img_index as i32 + || (layer.0.image > 0 + && self + .duplicated_img_reads_list + .get(&img_index) + .is_some_and(|list| list.contains(&(layer.0.image as usize)))) + { in_quad_layer = true; } } @@ -1651,6 +1718,27 @@ impl CDatafileWrapper { } } + if in_quad_layer { + images_high_ordered.push((img_index, image, in_quad_layer, in_tile_layer)); + } else if in_tile_layer { + images_low_ordered.push((img_index, image, in_quad_layer, in_tile_layer)); + } + } + for (img_index, image, in_quad_layer, in_tile_layer) in images_high_ordered + .into_iter() + .chain(images_low_ordered.into_iter()) + { + // skip if duplicated + if let Some(old_index) = self.duplicated_img_reads.get(&img_index) { + if let Some(old_img_array_assign_index) = old_img_array_assign.get(old_index) { + old_img_array_assign.insert(img_index, *old_img_array_assign_index); + } + if let Some(old_img_assign_index) = old_img_assign.get(old_index) { + old_img_assign.insert(img_index, *old_img_assign_index); + } + continue; + } + fn check_size_and_dilate<'a>( thread_pool: &rayon::ThreadPool, img: Cow<'a, [u8]>, @@ -1756,14 +1844,16 @@ impl CDatafileWrapper { }, hq_meta: None, }; - image_resources.insert( - res_ref.meta.blake3_hash, - LegacyMapToNewRes { - buf: png_data, - ty: "png".into(), - name: res_ref.name.to_string(), - }, - ); + if in_quad_layer || in_tile_layer { + image_resources.insert( + res_ref.meta.blake3_hash, + LegacyMapToNewRes { + buf: png_data, + ty: "png".into(), + name: res_ref.name.to_string(), + }, + ); + } if in_tile_layer { old_img_array_assign.insert(img_index, map.resources.image_arrays.len()); map.resources.image_arrays.push(res_ref.clone()); @@ -2538,13 +2628,14 @@ impl CDatafileWrapper { } } - let images_len = map.resources.images.len(); + let mut image_hash_to_index_mapping: HashMap<(String, Hash), usize> = Default::default(); + let mut image_array_index_mapping: HashMap = Default::default(); + let mut img_counter = 0; // resources { // images let item_index = res.data_file.info.item_offsets.len() as i32; - let image_count = map.resources.images.len() + map.resources.image_arrays.len(); for (index, image) in map.resources.images.into_iter().enumerate() { res.data_file .info @@ -2603,10 +2694,22 @@ impl CDatafileWrapper { }; data_item.write_to_vec(&mut data_items); data_items.append(&mut img_data); + + image_hash_to_index_mapping + .insert((image.name.to_string(), image.meta.blake3_hash), index); + img_counter += 1; } // images 2d array for (index, image) in map.resources.image_arrays.into_iter().enumerate() { + if let Some(map_index) = image_hash_to_index_mapping + .get(&(image.name.to_string(), image.meta.blake3_hash)) + { + image_array_index_mapping.insert(index, *map_index); + continue; + } + image_array_index_mapping.insert(index, img_counter); + res.data_file .info .item_offsets @@ -2664,12 +2767,14 @@ impl CDatafileWrapper { }; data_item.write_to_vec(&mut data_items); data_items.append(&mut img_data); + + img_counter += 1; } - if image_count > 0 { + if img_counter > 0 { res.data_file.info.item_types.push(CDatafileItemType { item_type: MapItemTypes::Image as i32, start: item_index, - num: image_count as i32, + num: img_counter as i32, }); } @@ -2850,7 +2955,7 @@ impl CDatafileWrapper { image: layer .attr .image_array - .map(|i| (i + images_len) as i32) + .map(|i| *image_array_index_mapping.get(&i).unwrap() as i32) .unwrap_or(-1), data: data_index as i32, name: Default::default(), @@ -3261,7 +3366,7 @@ impl CDatafileWrapper { tune_name, tune_val.value, if let Some(comment) = &tune_val.comment { - format!(" # {}", comment) + format!(" # {comment}") } else { String::default() } @@ -3275,7 +3380,7 @@ impl CDatafileWrapper { let mut msg = |postfix: &str, msg: &Option| { let Some(msg) = msg else { return }; let mut setting: [u8; 256] = vec![0; 256].try_into().unwrap(); - let cmd = format!("tune_zone_{postfix} {index} {}", msg); + let cmd = format!("tune_zone_{postfix} {index} {msg}"); let src = cmd.as_bytes(); setting[0..src.len().min(256)] .copy_from_slice(&src[0..src.len().min(256)]); @@ -3494,7 +3599,7 @@ impl CDatafileWrapper { "{}{}", cmd.value, if let Some(comment) = &cmd.comment { - format!(" # {}", comment) + format!(" # {comment}") } else { String::default() } @@ -3511,7 +3616,7 @@ impl CDatafileWrapper { var_name, value.value, if let Some(comment) = &value.comment { - format!(" # {}", comment) + format!(" # {comment}") } else { String::default() } diff --git a/game/legacy-map/src/lib.rs b/game/legacy-map/src/lib.rs new file mode 100644 index 00000000..10261021 --- /dev/null +++ b/game/legacy-map/src/lib.rs @@ -0,0 +1,2 @@ +pub mod datafile; +pub mod mapdef_06; diff --git a/game/game-base/src/mapdef_06.rs b/game/legacy-map/src/mapdef_06.rs similarity index 98% rename from game/game-base/src/mapdef_06.rs rename to game/legacy-map/src/mapdef_06.rs index 12121f40..064e6237 100644 --- a/game/game-base/src/mapdef_06.rs +++ b/game/legacy-map/src/mapdef_06.rs @@ -597,14 +597,6 @@ pub struct CMapItemLayerTilemap { } impl CMapItemLayerTilemap { - pub fn size_of_without_ddrace() -> usize { - std::mem::size_of::() - std::mem::size_of::() * 5 - } - - pub fn size_of_without_name() -> usize { - Self::size_of_without_ddrace() - std::mem::size_of::() * 3 - } - pub fn read_from_slice(data: &[u8]) -> Self { let (tile_layer, rest) = data.split_at(size_of::()); let tile_layer = CMapItemLayer::read_from_slice(tile_layer); @@ -679,6 +671,26 @@ impl CMapItemLayerTilemap { tune = read_i32_le(val); } + if version < 3 { + if flags & TilesLayerFlag::Tele as i32 != 0 { + tele = name[0]; + } + if flags & TilesLayerFlag::Speedup as i32 != 0 { + speedup = name[1]; + } + if flags & TilesLayerFlag::Front as i32 != 0 { + front = name[2]; + } + if flags & TilesLayerFlag::Switch as i32 != 0 { + switch = tele; + tele = -1; + } + if flags & TilesLayerFlag::Tune as i32 != 0 { + tune = speedup; + speedup = -1; + } + } + Self { layer: tile_layer, version, diff --git a/game/legacy_proxy/Cargo.toml b/game/legacy-proxy/Cargo.toml similarity index 81% rename from game/legacy_proxy/Cargo.toml rename to game/legacy-proxy/Cargo.toml index 57c30246..3ffb98aa 100644 --- a/game/legacy_proxy/Cargo.toml +++ b/game/legacy-proxy/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "legacy_proxy" +name = "legacy-proxy" version = "0.1.0" edition = "2021" @@ -16,13 +16,14 @@ game-network = { path = "../game-network" } game-server = { path = "../game-server" } game-interface = { path = "../game-interface" } game-base = { path = "../game-base" } +legacy-map = { path = "../legacy-map" } vanilla = { path = "../vanilla" } map = { path = "../map" } -libtw2-gamenet-ddnet = { git = "https://github.com/Jupeyy/libtw2.git", rev = "d81eff37fcefdffbd309015e445b9c7cfd167b95" } -libtw2-packer = { git = "https://github.com/Jupeyy/libtw2.git", rev = "d81eff37fcefdffbd309015e445b9c7cfd167b95" } -libtw2-snapshot = { git = "https://github.com/Jupeyy/libtw2.git", rev = "d81eff37fcefdffbd309015e445b9c7cfd167b95" } -libtw2-net = { git = "https://github.com/Jupeyy/libtw2.git", rev = "d81eff37fcefdffbd309015e445b9c7cfd167b95" } +libtw2-gamenet-ddnet = { git = "https://github.com/Jupeyy/libtw2.git", rev = "0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" } +libtw2-packer = { git = "https://github.com/Jupeyy/libtw2.git", rev = "0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" } +libtw2-snapshot = { git = "https://github.com/Jupeyy/libtw2.git", rev = "0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" } +libtw2-net = { git = "https://github.com/Jupeyy/libtw2.git", rev = "0e6a9e2f21f12bf34efd2da9f41043e1e3de4c57" } anyhow = { version = "1.0.98", features = ["backtrace"] } # stuck for compat with libtw2 @@ -37,3 +38,4 @@ warn = "0.2.2" rayon = "1.10.0" tokio = { version = "1.45.0", features = ["rt-multi-thread", "sync", "fs", "time", "macros"] } futures = "0.3.31" +hex = "0.4.3" diff --git a/game/legacy_proxy/src/client.rs b/game/legacy-proxy/src/client.rs similarity index 97% rename from game/legacy_proxy/src/client.rs rename to game/legacy-proxy/src/client.rs index ef22cbee..16a3eb59 100644 --- a/game/legacy_proxy/src/client.rs +++ b/game/legacy-proxy/src/client.rs @@ -21,12 +21,6 @@ use log::{debug, log, log_enabled, Level}; use crate::{socket::Socket, ServerInfo}; -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct QueuedChunk { - pub vital: bool, - pub data: Vec, -} - pub struct SocketClient { pub socket: Socket, pub net: Net, @@ -83,7 +77,7 @@ impl SocketClient { pub fn run_once(&mut self, mut on_event: impl FnMut(&mut Self, ChunkOrEvent<'_, SocketAddr>)) { self.net .tick(&mut self.socket) - .for_each(|e| panic!("{:?}", e)); + .for_each(|e| panic!("{e:?}")); while let Ok(res) = self.socket.try_recv() { self.run_recv(res, &mut on_event); @@ -150,7 +144,7 @@ impl Drop for SocketClient { fn hexdump(level: Level, data: &[u8]) { if log_enabled!(level) { - hexdump_iter(data).for_each(|s| log!(level, "{}", s)); + hexdump_iter(data).for_each(|s| log!(level, "{s}")); } } diff --git a/game/legacy_proxy/src/lib.rs b/game/legacy-proxy/src/lib.rs similarity index 96% rename from game/legacy_proxy/src/lib.rs rename to game/legacy-proxy/src/lib.rs index 80cc6874..ee3c1903 100644 --- a/game/legacy_proxy/src/lib.rs +++ b/game/legacy-proxy/src/lib.rs @@ -18,7 +18,6 @@ use base_io::io::Io; use client::{ClientData, ClientState, ProxyClient, SocketClient, WarnPkt}; use game_base::{ connecting_log::ConnectingLog, - datafile::ints_to_str, network::{ messages::{ AddLocalPlayerResponseError, GameModification, MsgClChatMsg, MsgClLoadVotes, @@ -77,6 +76,7 @@ use game_server::{ server_game::{ServerGame, ServerMap}, }; use hexdump::hexdump_iter; +use legacy_map::datafile::ints_to_str; use libtw2_gamenet_ddnet::{ enums::{self, Emote, Team, VERSION}, msg::{ @@ -95,7 +95,7 @@ use libtw2_gamenet_ddnet::{ use libtw2_net::net::PeerId; use libtw2_packer::{IntUnpacker, Unpacker}; use log::{debug, log_enabled, warn, Level}; -use map::map::Map; +use map::{file::MapFileReader, map::Map}; use math::{ colors::{legacy_color_to_rgba, rgba_to_legacy_color}, math::{ @@ -173,6 +173,15 @@ use vanilla::{ weapons::definitions::weapon_def::Weapon, }; +enum LegacyInputFlags { + // Playing = 1 << 0, + InMenu = 1 << 1, + Chatting = 1 << 2, + Scoreboard = 1 << 3, + Aim = 1 << 4, + // SpecCam = 1 << 5, +} + #[derive(Debug)] pub struct LegacyProxy { pub is_finished: Arc, @@ -198,7 +207,7 @@ const TICKS_PER_SECOND: u32 = 50; fn hexdump(level: Level, data: &[u8]) { if log_enabled!(level) { - hexdump_iter(data).for_each(|s| log::log!(level, "{}", s)); + hexdump_iter(data).for_each(|s| log::log!(level, "{s}")); } } @@ -286,7 +295,7 @@ struct ClientBase { server_info: ServerInfoTy, votes: ServerMapVotes, - vote_state: Option, + vote_state: Option<(VoteState, Duration)>, vote_list_updated: bool, loaded_map_votes: bool, loaded_misc_votes: bool, @@ -297,6 +306,12 @@ struct ClientBase { is_first_map_pkt: bool, + // ping calculations + last_ping: Option, + last_ping_uuid: u128, + last_pings: BTreeMap, + last_pong: Option, + // helpers input_deser: Pool>, player_snap_pool: Pool>, @@ -405,7 +420,7 @@ impl Client { let io = Io::new(|_| fs, Arc::new(HttpClient::new())); let mut server_info = None; { - log.log(format!("Getting server info from: {}", addr)); + log.log(format!("Getting server info from: {addr}")); // first get the server info let mut conless = SocketClient::new(&io, addr).unwrap(); let mut is_connect = false; @@ -435,29 +450,27 @@ impl Client { ) { Ok(m) => m, Err(err) => { - debug!("decode err during startup: {:?}", err); - // TODO: hacky way to listen for reconnect msg not impl in libtw2 - if !is_map_ready { - log.log("Reconnecting"); - conless - .net - .disconnect( - &mut conless.socket, - conless.server_pid, - b"reconnect", - ) - .unwrap(); - let (pid, res) = - conless.net.connect(&mut conless.socket, addr); - res.unwrap(); - conless.server_pid = pid; - } + debug!("decode err during startup: {err:?}"); return; } }; if matches!(msg, SystemOrGame::System(System::MapChange(_))) { is_map_ready = true; + } else if matches!(msg, SystemOrGame::System(System::Reconnect(_))) + { + log.log("Reconnecting"); + conless + .net + .disconnect( + &mut conless.socket, + conless.server_pid, + b"reconnect", + ) + .unwrap(); + let (pid, res) = conless.net.connect(&mut conless.socket, addr); + res.unwrap(); + conless.server_pid = pid; } } libtw2_net::net::ChunkOrEvent::Connless(msg) => { @@ -477,7 +490,7 @@ impl Client { } libtw2_net::net::ChunkOrEvent::Disconnect(_, items) => { let reason = String::from_utf8_lossy(items); - log.log(format!("Connection lost: {}", reason)); + log.log(format!("Connection lost: {reason}")); if reason.contains("password") { server_info = server_info.clone().or(Some(ServerInfoTy::Partial { @@ -624,6 +637,11 @@ impl Client { server_info, join_password: Default::default(), + + last_ping: None, + last_ping_uuid: Default::default(), + last_pings: Default::default(), + last_pong: None, }, con_id: None, @@ -831,7 +849,7 @@ impl Client { for (item, id) in items { match item { SnapObj::PlayerInput(inp) => { - debug!("[NOT IMPLEMENTED] player input: {:?}", inp); + debug!("[NOT IMPLEMENTED] player input: {inp:?}"); } SnapObj::Projectile(projectile) => { add_proj( @@ -955,7 +973,7 @@ impl Client { }); } SnapObj::CharacterCore(character_core) => { - debug!("[NOT IMPLEMENTED] character core: {:?}", character_core); + debug!("[NOT IMPLEMENTED] character core: {character_core:?}"); } SnapObj::Character(character) => { let stage_id = base @@ -987,12 +1005,13 @@ impl Client { let mut buffs: FxLinkedHashMap<_, _> = Default::default(); let active_weapon = match character.weapon { - libtw2_gamenet_ddnet::enums::Weapon::Hammer => WeaponType::Hammer, - libtw2_gamenet_ddnet::enums::Weapon::Pistol => WeaponType::Gun, - libtw2_gamenet_ddnet::enums::Weapon::Shotgun => WeaponType::Shotgun, - libtw2_gamenet_ddnet::enums::Weapon::Grenade => WeaponType::Grenade, - libtw2_gamenet_ddnet::enums::Weapon::Rifle => WeaponType::Laser, - libtw2_gamenet_ddnet::enums::Weapon::Ninja => { + enums::WEAPON_HAMMER => WeaponType::Hammer, + enums::WEAPON_PISTOL => WeaponType::Gun, + enums::WEAPON_SHOTGUN => WeaponType::Shotgun, + enums::WEAPON_GRENADE => WeaponType::Grenade, + enums::WEAPON_RIFLE => WeaponType::Laser, + // Weapon ninja + _ => { buffs.insert( CharacterBuff::Ninja, BuffProps { @@ -1044,7 +1063,7 @@ impl Client { character_core.hook_dy as f32 / 256.0, ), hook_tele_base: Default::default(), - hook_tick: character_core.hook_tick.0, + hook_tick: character_core.hook_tick, hook_state: match character_core.hook_state { 1 => HookState::RetractStart, 2 => HookState::RetractMid, @@ -1094,14 +1113,6 @@ impl Client { } let mut flags = CharacterInputFlags::empty(); - enum LegacyInputFlags { - // Playing = 1 << 0, - InMenu = 1 << 1, - Chatting = 1 << 2, - Scoreboard = 1 << 3, - Aim = 1 << 4, - // SpecCam = 1 << 5, - } if (character.player_flags & LegacyInputFlags::Aim as i32) != 0 { flags.insert(CharacterInputFlags::HOOK_COLLISION_LINE); } @@ -1191,11 +1202,11 @@ impl Client { &mut self, _ids: &[CharacterId], _for_each_func: &mut dyn FnMut( - &CharacterId, - &mut Core, - &mut CoreReusable, - &mut vanilla::entities::character::pos::character_pos::CharacterPos, - ) -> std::ops::ControlFlow<()>, + &CharacterId, + &mut Core, + &mut CoreReusable, + &mut vanilla::entities::character::pos::character_pos::CharacterPos, + ) -> std::ops::ControlFlow<()>, ) -> std::ops::ControlFlow<()> { std::ops::ControlFlow::Continue(()) } @@ -1650,7 +1661,7 @@ impl Client { } } SnapObj::MyOwnObject(my_own_object) => { - debug!("[NOT IMPLEMENTED] my own object: {:?}", my_own_object); + debug!("[NOT IMPLEMENTED] my own object: {my_own_object:?}"); } SnapObj::DdnetCharacter(_) => { panic!("This snap item is purposely removed earlier"); @@ -1659,13 +1670,10 @@ impl Client { panic!("This snap item is purposely removed earlier"); } SnapObj::GameInfoEx(game_info_ex) => { - debug!("[NOT IMPLEMENTED] game info ex: {:?}", game_info_ex); + debug!("[NOT IMPLEMENTED] game info ex: {game_info_ex:?}"); } SnapObj::DdraceProjectile(ddrace_projectile) => { - debug!( - "[NOT IMPLEMENTED] ddrace projectile: {:?}", - ddrace_projectile - ); + debug!("[NOT IMPLEMENTED] ddrace projectile: {ddrace_projectile:?}"); } SnapObj::DdnetLaser(laser) => { add_laser( @@ -1721,7 +1729,7 @@ impl Client { ); } SnapObj::Common(common) => { - debug!("[NOT IMPLEMENTED] common: {:?}", common); + debug!("[NOT IMPLEMENTED] common: {common:?}"); } SnapObj::Explosion(explosion) => { let events = base @@ -2113,16 +2121,28 @@ impl Client { ); } SnapObj::MyOwnEvent(my_own_event) => { - debug!("[NOT IMPLEMENTED] my own event: {:?}", my_own_event); + debug!("[NOT IMPLEMENTED] my own event: {my_own_event:?}"); } SnapObj::SpecChar(spec_char) => { - debug!("[NOT IMPLEMENTED] spec char: {:?}", spec_char); + debug!("[NOT IMPLEMENTED] spec char: {spec_char:?}"); } SnapObj::SwitchState(switch_state) => { - debug!("[NOT IMPLEMENTED] switch state: {:?}", switch_state); + debug!("[NOT IMPLEMENTED] switch state: {switch_state:?}"); } SnapObj::EntityEx(entity_ex) => { - debug!("[NOT IMPLEMENTED] entity ex: {:?}", entity_ex); + debug!("[NOT IMPLEMENTED] entity ex: {entity_ex:?}"); + } + SnapObj::DdnetSpectatorInfo(ddnet_spectator_info) => { + debug!("[NOT IMPLEMENTED] ddnet spectator info: {ddnet_spectator_info:?}"); + } + SnapObj::Birthday(birthday) => { + debug!("[NOT IMPLEMENTED] birthday: {birthday:?}"); + } + SnapObj::Finish(finish) => { + debug!("[NOT IMPLEMENTED] finish: {finish:?}"); + } + SnapObj::MapSoundWorld(map_sound_world) => { + debug!("[NOT IMPLEMENTED] map sound world: {map_sound_world:?}"); } } } @@ -2156,20 +2176,8 @@ impl Client { Err(err) => { let id = SystemOrGame::decode_id(&mut WarnPkt(pid, data), &mut Unpacker::new(data)).ok(); - warn!("decode error {:?} {:?}:", id, err); + warn!("decode error {id:?} {err:?}:"); hexdump(Level::Warn, data); - - // TODO: hacky way to listen for reconnect msg not impl in libtw2 - if collision.is_none() { - log.log("Proxy client will reconnect to the server. (reconnect packet)"); - socket - .net - .disconnect(&mut socket.socket, socket.server_pid, b"reconnect") - .unwrap(); - let (pid, res) = socket.net.connect(&mut socket.socket, *connect_addr); - res.unwrap(); - socket.server_pid = pid; - } return; } }; @@ -2219,6 +2227,21 @@ impl Client { base.capabilities.chat_timeout_codes = true; } } + (_, SystemOrGame::System(System::Reconnect(_))) => { + log.log("Proxy client will reconnect to the server. (reconnect packet)"); + socket + .net + .disconnect(&mut socket.socket, socket.server_pid, b"reconnect") + .unwrap(); + let (pid, res) = socket.net.connect(&mut socket.socket, *connect_addr); + res.unwrap(); + socket.server_pid = pid; + } + (_, SystemOrGame::System(System::PongEx(pong_ex))) => { + if let Some(time) = base.last_pings.remove(&pong_ex.id.as_u128()) { + base.last_pong = Some(sys.time_get().saturating_sub(time)); + } + } (_, SystemOrGame::System(System::MapDetails(info))) => { if let Some(name) = String::from_utf8(info.name.to_vec()) .ok() @@ -2347,10 +2370,7 @@ impl Client { let total_len = data.values().map(|d| d.len()).sum::(); if total_len < expected_size { log.log(format!("Received map chunk: {}", map_data.chunk)); - log.log(format!( - "{} of {} bytes downloaded", - total_len, expected_size - )); + log.log(format!("{total_len} of {expected_size} bytes downloaded")); let downloading_chunks = data.values().filter(|d| d.is_empty()).count(); for i in next_chunk..next_chunk + 50usize.saturating_sub(downloading_chunks) { @@ -2453,7 +2473,7 @@ impl Client { allowed_to_vote_count: 0, }; let state = (vote.timeout > 0).then_some(state); - base.vote_state = state.clone(); + base.vote_state = state.clone().map(|s| (s, sys.time_get())); server_network.send_in_order_to( &ServerToClientMessage::Vote(state), &con_id, @@ -2461,18 +2481,23 @@ impl Client { ); } (_, SystemOrGame::Game(Game::SvVoteStatus(vote))) => { - if let Some(mut state) = base.vote_state.clone() { + if let Some((mut state, start_time)) = base.vote_state.clone() { state.yes_votes = vote.yes.unsigned_abs() as u64; state.no_votes = vote.no.unsigned_abs() as u64; state.allowed_to_vote_count = vote.total.unsigned_abs() as u64; + let now = sys.time_get(); + state.remaining_time = state + .remaining_time + .saturating_sub(now.saturating_sub(start_time)); + server_network.send_in_order_to( &ServerToClientMessage::Vote(Some(state.clone())), &con_id, NetworkInOrderChannel::Global, ); - base.vote_state = Some(state); + base.vote_state = Some((state, now)); } } (_, SystemOrGame::System(System::ConReady(_))) => { @@ -2584,7 +2609,7 @@ impl Client { } } Err(e) => { - debug!("item decode error {:?}: {:?}", e, id); + debug!("item decode error {e:?}: {id:?}"); None } }) @@ -3081,7 +3106,16 @@ impl Client { .saturating_sub(inp.logic_overhead) .as_millis() ); - inp.logic_overhead = sys.time_get(); + // For now use ping pong for time calculations + if let Some(pong_time) = base.last_pong { + const PREDICTION_EXTRA_MARGIN: Duration = Duration::from_millis(5); + inp.logic_overhead = inp + .logic_overhead + .saturating_add(pong_time) + .saturating_add(PREDICTION_EXTRA_MARGIN); + } else { + inp.logic_overhead = sys.time_get(); + } *was_acked = true; } } @@ -3247,7 +3281,7 @@ impl Client { } if !processed { - debug!("unprocessed message {:?} {:?}", &player.state, msg); + debug!("unprocessed message {:?} {msg:?}", &player.state); } } @@ -3255,7 +3289,7 @@ impl Client { let msg = match Connless::decode(&mut WarnPkt(addr, data), &mut Unpacker::new(data)) { Ok(m) => m, Err(err) => { - warn!("decode error {:?}:", err); + warn!("decode error {err:?}:"); hexdump(Level::Warn, data); return None; } @@ -3281,7 +3315,7 @@ impl Client { _ => processed = false, } if !processed { - debug!("unprocessed message {:?}", msg); + debug!("unprocessed message {msg:?}"); } None } @@ -3385,6 +3419,23 @@ impl Client { } let wanted_weapon = 0; + let mut player_flags = 0; + if inp.state.flags.contains(CharacterInputFlags::CHATTING) { + player_flags |= LegacyInputFlags::Chatting as i32; + } + if inp + .state + .flags + .contains(CharacterInputFlags::HOOK_COLLISION_LINE) + { + player_flags |= LegacyInputFlags::Aim as i32; + } + if inp.state.flags.contains(CharacterInputFlags::SCOREBOARD) { + player_flags |= LegacyInputFlags::Scoreboard as i32; + } + if inp.state.flags.contains(CharacterInputFlags::MENU_UI) { + player_flags |= LegacyInputFlags::InMenu as i32; + } let mut input = snap_obj::PlayerInput { direction: *state.dir, target_x, @@ -3396,7 +3447,7 @@ impl Client { .map(|(v, _)| (v.get() * 2).saturating_sub(*state.fire as u64)) .unwrap_or_default() as i32, hook: *state.hook as i32, - player_flags: 0, + player_flags, wanted_weapon, next_weapon: prev_inp.next_weapon + next_weapon_diff, prev_weapon: prev_inp.prev_weapon + prev_weapon_diff, @@ -3415,7 +3466,7 @@ impl Client { Ok(()) } - fn player_info_to_legacy(player_info: &NetworkCharacterInfo) -> game::ClStartInfo { + fn player_info_to_legacy(player_info: &NetworkCharacterInfo) -> game::ClStartInfo<'_> { let skin: &ResourceKeyBase = player_info.skin.borrow(); let (use_custom_color, color_body, color_feet) = match player_info.skin_info { NetworkSkinInfo::Original => (false, 0, 0), @@ -3439,7 +3490,7 @@ impl Client { } } - fn run_once(&mut self) -> anyhow::Result<()> { + fn handle_client_events(&mut self) -> anyhow::Result<()> { if self .server_has_new_events .load(std::sync::atomic::Ordering::SeqCst) @@ -3478,10 +3529,8 @@ impl Client { return Ok(()); } NetworkEvent::ConnectingFailed(reason) => { - self.log.log(format!( - "Local client failed to connect to proxy: {}", - reason - )); + self.log + .log(format!("Local client failed to connect to proxy: {reason}")); self.is_finished .store(true, std::sync::atomic::Ordering::SeqCst); return Ok(()); @@ -3758,7 +3807,7 @@ impl Client { if switch { player.sendg(Game::ClSay(game::ClSay { team: false, - message: format!("/{}", pause).as_bytes(), + message: format!("/{pause}").as_bytes(), })); } player.sendg(Game::ClSetSpectatorMode( @@ -3770,20 +3819,12 @@ impl Client { } } ClientToServerPlayerMessage::StartVote(msg) => { - let mut get_player_name = |char_id: &CharacterId| { + let get_player_legacy_id = |char_id: &CharacterId| { self.base .char_new_id_to_legacy .get(char_id) .copied() - .and_then(|id| { - Self::player_info_mut( - id, - &self.base, - &mut self.last_snapshot, - ) - .map(|(_, i)| i.player_info.name.to_string()) - }) - .unwrap_or_default() + .unwrap_or(-1) }; let (type_, value, reason) = match msg { VoteIdentifierType::Map(vote) => ( @@ -3834,12 +3875,12 @@ impl Client { ), VoteIdentifierType::VoteKickPlayer(vote) => ( "kick".as_bytes(), - get_player_name(&vote.voted_player_id), + get_player_legacy_id(&vote.voted_player_id).to_string(), vote.reason.to_string(), ), VoteIdentifierType::VoteSpecPlayer(vote) => ( - "spec".as_bytes(), - get_player_name(&vote.voted_player_id), + "spectate".as_bytes(), + get_player_legacy_id(&vote.voted_player_id).to_string(), vote.reason.to_string(), ), VoteIdentifierType::Misc(vote) => ( @@ -3968,7 +4009,7 @@ impl Client { player.sendg(Game::ClSay(game::ClSay { team: false, - message: format!("/team {}", team).as_bytes(), + message: format!("/team {team}").as_bytes(), })); player.flush(); } @@ -4004,8 +4045,7 @@ impl Client { } ClientToServerPlayerMessage::RconExec { ident_text, args } => { debug!( - "[NOT IMPLEMENTED] rcon exec: {:?} {:?}", - ident_text, args + "[NOT IMPLEMENTED] rcon exec: {ident_text:?} {args:?}" ); } } @@ -4256,7 +4296,10 @@ impl Client { self.server_has_new_events .store(false, std::sync::atomic::Ordering::SeqCst); } + Ok(()) + } + fn handle_server_events_and_sleep(&mut self) -> anyhow::Result<()> { if let Some(con_id) = self.con_id { let mut event_handler = |socket: &mut SocketClient, ev: libtw2_net::net::ChunkOrEvent<'_, SocketAddr>, @@ -4314,7 +4357,7 @@ impl Client { Disconnect(_, reason) => { let reason = String::from_utf8_lossy(reason).to_string(); self.log - .log(format!("Proxy client got disconnected: {}", reason)); + .log(format!("Proxy client got disconnected: {reason}")); socket.skip_disconnect_on_drop = true; if is_main_connection { self.server_network.kick(&con_id, KickType::Kick(reason)); @@ -4396,7 +4439,7 @@ impl Client { } self.log.log(format!( - "Client proxy is converting map: {}.\ + "Client proxy is converting map: {}. \ This might take a moment.", map_name.as_str() )); @@ -4410,9 +4453,12 @@ impl Client { ) .unwrap(); - let (phy_group, _) = Map::read_physics_group_and_config(&map).unwrap(); + let (phy_group, _) = Map::read_physics_group_and_config( + &MapFileReader::new(map.clone()).unwrap(), + ) + .unwrap(); - let new_collision = Collision::new(&phy_group, true).unwrap(); + let new_collision = Collision::new(phy_group, true).unwrap(); self.log.log("Client proxy prepares map collision"); self.collisions = Some(new_collision); @@ -4639,6 +4685,42 @@ impl Client { Ok(()) } + + fn run_once(&mut self) -> anyhow::Result<()> { + self.handle_client_events()?; + + self.handle_server_events_and_sleep()?; + + // do 10 pings per second to determine accurate ping + let time_now = self.sys.time_get(); + if self.base.last_ping.is_none_or(|last_ping| { + time_now.saturating_sub(last_ping) > Duration::from_millis(1000 / 10) + }) { + self.base.last_ping = Some(time_now); + + if let Some(player) = self.players.values_mut().next() { + if matches!(player.data.state, ClientState::Ingame) { + let pkt = system::PingEx { + id: hex::encode(self.base.last_ping_uuid.to_ne_bytes()) + .parse() + .unwrap(), + }; + player.socket.sends(System::PingEx(pkt)); + player.socket.flush(); + + self.base + .last_pings + .insert(self.base.last_ping_uuid, time_now); + while self.base.last_pings.len() > 50 { + self.base.last_pings.pop_first(); + } + self.base.last_ping_uuid += 1; + } + } + } + + Ok(()) + } } pub fn proxy_run( diff --git a/game/legacy_proxy/src/projectile.rs b/game/legacy-proxy/src/projectile.rs similarity index 100% rename from game/legacy_proxy/src/projectile.rs rename to game/legacy-proxy/src/projectile.rs diff --git a/game/legacy_proxy/src/socket.rs b/game/legacy-proxy/src/socket.rs similarity index 93% rename from game/legacy_proxy/src/socket.rs rename to game/legacy-proxy/src/socket.rs index 6c0b74f5..ae816858 100644 --- a/game/legacy_proxy/src/socket.rs +++ b/game/legacy-proxy/src/socket.rs @@ -21,17 +21,6 @@ impl fmt::Display for NoAddressFamiliesSupported { } } -#[derive(Debug)] -pub struct AddressFamilyNotSupported(()); - -impl error::Error for AddressFamilyNotSupported {} - -impl fmt::Display for AddressFamilyNotSupported { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("destination address family (IPv4 or IPv6) not supported on this system") - } -} - type ThreadedReceiver = Arc, SocketAddr)>>>; struct AsyncSocket { @@ -84,9 +73,7 @@ impl Socket { .get_storage()?; if v4.is_none() && v6.is_none() { - return Err( - io::Error::new(io::ErrorKind::Other, NoAddressFamiliesSupported(())).into(), - ); + return Err(io::Error::other(NoAddressFamiliesSupported(())).into()); } let spawn_socket = |s: Arc| { // recv diff --git a/game/map-convert-lib/Cargo.toml b/game/map-convert-lib/Cargo.toml index 456a4b0f..22b3d6a1 100644 --- a/game/map-convert-lib/Cargo.toml +++ b/game/map-convert-lib/Cargo.toml @@ -8,7 +8,8 @@ edition = "2021" [dependencies] base = { path = "../../lib/base" } base-io = { path = "../../lib/base-io" } -game-base = { path = "../../game/game-base" } + +legacy-map = { path = "../../game/legacy-map" } map = { path = "../../game/map" } rayon = "1.10.0" anyhow = { version = "1.0.98", features = ["backtrace"] } diff --git a/game/map-convert-lib/src/legacy_to_new.rs b/game/map-convert-lib/src/legacy_to_new.rs index 205f49c8..277c2aac 100644 --- a/game/map-convert-lib/src/legacy_to_new.rs +++ b/game/map-convert-lib/src/legacy_to_new.rs @@ -14,7 +14,7 @@ use base::{ hash::{generate_hash_for, Hash}, }; use base_io::io::IoFileSys; -use game_base::datafile::{ +use legacy_map::datafile::{ CDatafileWrapper, LegacyMapToNewOutput, LegacyMapToNewRes, MapFileImageReadOptions, MapFileLayersReadOptions, MapFileOpenOptions, MapFileSoundReadOptions, }; diff --git a/game/map-convert-lib/src/lib.rs b/game/map-convert-lib/src/lib.rs index e3127949..b2c08e80 100644 --- a/game/map-convert-lib/src/lib.rs +++ b/game/map-convert-lib/src/lib.rs @@ -3,6 +3,7 @@ pub mod new_to_legacy; #[cfg(test)] mod test { + use std::path::Path; use std::sync::Arc; use base_fs::filesys::FileSystem; @@ -11,38 +12,17 @@ mod test { use crate::legacy_to_new::{legacy_to_new, legacy_to_new_from_buf}; use crate::new_to_legacy::new_to_legacy_from_buf_async; - fn convert_back_and_forth_for_map(map_name: &str) { - let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../"); - std::env::set_current_dir(workspace_root).unwrap(); - let io = IoFileSys::new(|rt| { - Arc::new( - FileSystem::new(rt, "ddnet-test", "ddnet-test", "ddnet-test", "ddnet-test") - .unwrap(), - ) - }); - - let thread_pool = Arc::new( - rayon::ThreadPoolBuilder::new() - .num_threads(1) - .build() - .unwrap(), - ); - - let new_map = legacy_to_new( - format!("legacy/maps/{}.map", map_name).as_ref(), - &io, - &thread_pool, - false, - ) - .unwrap(); + fn convert_back_and_forth_for_map(io: &IoFileSys, tp: &Arc, path: &Path) { + let map_name = path.file_stem().unwrap().to_str().unwrap(); + println!("converting map: {map_name}"); + let new_map = legacy_to_new(path, io, tp, false).unwrap(); let mut map = new_map.map.clone(); - let tp = thread_pool.clone(); + let tp_task = tp.clone(); let old_map = io .rt .spawn(async move { - let mut file = Vec::new(); - new_map.map.write(&mut file, &tp)?; + let file = new_map.map.write(&tp_task)?; new_to_legacy_from_buf_async( &file, |_| { @@ -93,18 +73,22 @@ mod test { )) }) }, - &tp, + &tp_task, ) .await }) .get_storage() .unwrap(); - let new_map2 = - legacy_to_new_from_buf(old_map.map, map_name, &io, &thread_pool, false).unwrap(); + let new_map2 = legacy_to_new_from_buf(old_map.map, map_name, io, tp, false).unwrap(); let mut map2 = new_map2.map; - fn assert_json_eq(name: &str, a: &A, b: &B) { + fn assert_json_eq( + map_name: &str, + name: &str, + a: &A, + b: &B, + ) { let map1_json = serde_json::to_string_pretty(a).unwrap(); let map2_json = serde_json::to_string_pretty(b).unwrap(); let found_diff = map1_json @@ -118,19 +102,19 @@ mod test { let s1_end = s1_start + (map1_json.len() - s1_start).min(range_len * 2); let s2_start = diff_index.max(range_len) - range_len; - let s2_end = s1_start + (map1_json.len() - s1_start).min(range_len * 2); + let s2_end = s2_start + (map2_json.len() - s2_start).min(range_len * 2); let diff = difference::Changeset::new( - &map1_json[s1_start..s1_end], - &map2_json[s2_start..s2_end], + &String::from_utf8_lossy(&map1_json.as_bytes()[s1_start..s1_end]), + &String::from_utf8_lossy(&map2_json.as_bytes()[s2_start..s2_end]), "\n", ); panic!( - "difference found for {name} @{diff_index}: \n{}\n in {} vs. {}", + "difference found for {map_name} {name} @{diff_index}: \n{}\n in\n{} vs.\n{}", diff, - &map1_json[s1_start..s1_end], - &map2_json[s2_start..s2_end], + &String::from_utf8_lossy(&map1_json.as_bytes()[s1_start..s1_end]), + &String::from_utf8_lossy(&map2_json.as_bytes()[s2_start..s2_end]), ); } assert!( @@ -144,17 +128,76 @@ mod test { map2.resources.sounds.clear(); // animation - assert_json_eq("animations", &map.animations, &map2.animations); - assert_json_eq("resources", &map.resources, &map2.resources); - assert_json_eq("bg groups", &map.groups.background, &map2.groups.background); - assert_json_eq("physics groups", &map.groups.physics, &map2.groups.physics); - assert_json_eq("fg groups", &map.groups.foreground, &map2.groups.foreground); + assert_json_eq(map_name, "animations", &map.animations, &map2.animations); + assert_json_eq(map_name, "resources", &map.resources, &map2.resources); + assert_json_eq( + map_name, + "bg groups", + &map.groups.background, + &map2.groups.background, + ); + assert_json_eq( + map_name, + "physics groups", + &map.groups.physics, + &map2.groups.physics, + ); + assert_json_eq( + map_name, + "fg groups", + &map.groups.foreground, + &map2.groups.foreground, + ); } #[test] fn convert_back_and_forth() { - convert_back_and_forth_for_map("Sunny Side Up"); - convert_back_and_forth_for_map("ctf1"); - //convert_back_and_forth_for_map("arctic"); + let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../"); + std::env::set_current_dir(workspace_root).unwrap(); + let io = IoFileSys::new(|rt| { + Arc::new( + FileSystem::new(rt, "ddnet-test", "ddnet-test", "ddnet-test", "ddnet-test") + .unwrap(), + ) + }); + let thread_pool = Arc::new( + rayon::ThreadPoolBuilder::new() + .num_threads(1) + .build() + .unwrap(), + ); + let in_ty = |ty: &str| { + let fs = io.fs.clone(); + let ty_t = ty.to_string(); + let entries = io + .rt + .spawn(async move { + Ok(fs + .entries_in_dir(format!("types/{ty_t}/maps").as_ref()) + .await + .unwrap()) + }) + .get_storage() + .unwrap(); + + for (path, _) in entries { + if path.ends_with(".map") { + let path: &std::path::Path = path.as_ref(); + convert_back_and_forth_for_map(&io, &thread_pool, path); + } + } + }; + in_ty("novice"); + in_ty("moderate"); + in_ty("brutal"); + in_ty("ddmax.easy"); + in_ty("ddmax.next"); + in_ty("ddmax.nut"); + in_ty("ddmax.pro"); + in_ty("dummy"); + in_ty("fun"); + in_ty("insane"); + in_ty("race"); + in_ty("solo"); } } diff --git a/game/map-convert-lib/src/new_to_legacy.rs b/game/map-convert-lib/src/new_to_legacy.rs index d9075e3b..6176ff51 100644 --- a/game/map-convert-lib/src/new_to_legacy.rs +++ b/game/map-convert-lib/src/new_to_legacy.rs @@ -1,8 +1,8 @@ use anyhow::anyhow; use base::{benchmark::Benchmark, hash::fmt_hash}; use base_io::io::IoFileSys; -use game_base::datafile::CDatafileWrapper; -use map::map::Map; +use legacy_map::datafile::CDatafileWrapper; +use map::{file::MapFileReader, map::Map}; use std::{future::Future, io::Cursor, path::Path, pin::Pin, sync::Arc}; use vorbis_rs::VorbisDecoder; @@ -21,7 +21,7 @@ pub async fn new_to_legacy_from_buf_async( >, thread_pool: &Arc, ) -> anyhow::Result { - let map = Map::read(file, thread_pool) + let map = Map::read(&MapFileReader::new(file.to_vec())?, thread_pool) .map_err(|err| anyhow!("loading map from file failed: {err}"))?; let (images, image_arrays, mut sounds) = load_resources(&map).await?; diff --git a/game/map/Cargo.toml b/game/map/Cargo.toml index e9aa5da0..9d580525 100644 --- a/game/map/Cargo.toml +++ b/game/map/Cargo.toml @@ -4,9 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] +assets-base = { path = "../assets-base" } + math = { path = "../../lib/math" } base = { path = "../../lib/base" } hiarc = { path = "../../lib/hiarc", features = ["enable_time", "enable_hashlink", "enable_rustc_hash"] } +image-utils = { path = "../../lib/image-utils" } rayon = "1.10.0" bincode = { features = ["serde"], version = "2.0.1" } diff --git a/game/map/src/file.rs b/game/map/src/file.rs new file mode 100644 index 00000000..c96d73ca --- /dev/null +++ b/game/map/src/file.rs @@ -0,0 +1,23 @@ +use std::{collections::HashMap, path::PathBuf}; + +use assets_base::tar::{tar_entry_to_file, tar_file_entries, tar_reader, TarEntries}; + +/// The map file reader wraps around file in memory. +pub struct MapFileReader { + pub(crate) entries: TarEntries, +} + +impl MapFileReader { + pub fn new(file: Vec) -> anyhow::Result { + Ok(Self { + entries: tar_file_entries(&mut tar_reader(file))?, + }) + } + + pub fn read_all(&self) -> anyhow::Result>> { + self.entries + .iter() + .map(|(path, entry)| anyhow::Ok((path.clone(), tar_entry_to_file(entry)?.to_vec()))) + .collect::>() + } +} diff --git a/game/map/src/header.rs b/game/map/src/header.rs new file mode 100644 index 00000000..50304e8a --- /dev/null +++ b/game/map/src/header.rs @@ -0,0 +1,15 @@ +use hiarc::Hiarc; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Hiarc, Clone, Serialize, Deserialize)] +pub struct Header { + /// The type of map + pub ty: String, + /// Map version code + pub version: u64, +} + +impl Header { + pub const VERSION: u64 = 2025061000; + pub const FILE_TY: &'static str = "twmap"; +} diff --git a/game/map/src/lib.rs b/game/map/src/lib.rs index d79b8658..c1018fdc 100644 --- a/game/map/src/lib.rs +++ b/game/map/src/lib.rs @@ -1,6 +1,8 @@ #![deny(warnings)] #![deny(clippy::all)] +pub mod file; +pub mod header; pub mod map; pub mod skeleton; pub mod types; @@ -18,7 +20,10 @@ mod test { use base_io::io::IoFileSys; use flate2::Compression; - use crate::map::{groups::MapGroup, Map}; + use crate::{ + file::MapFileReader, + map::{groups::MapGroup, Map}, + }; fn compression_tests_for_map(map_name: &str) { let workspace_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../"); @@ -42,10 +47,10 @@ mod test { let map_name = map_name.to_string(); let map_legacy = io.rt.spawn(async move { let map = fs - .read_file(format!("map/maps/{}.twmap", map_name).as_ref()) + .read_file(format!("map/maps/{}.twmap.tar", map_name).as_ref()) .await?; - Map::read(&map, &tp) + Map::read(&MapFileReader::new(map)?, &tp) }); let map = map_legacy.get_storage().unwrap(); diff --git a/game/map/src/map.rs b/game/map/src/map.rs index be78caa9..efd68af9 100644 --- a/game/map/src/map.rs +++ b/game/map/src/map.rs @@ -5,18 +5,28 @@ pub mod groups; pub mod metadata; pub mod resources; +use std::path::{Path, PathBuf}; + use anyhow::anyhow; +use assets_base::{ + tar::{new_tar, tar_add_file, tar_entry_to_file}, + verify::{json::verify_json, ogg_vorbis::verify_ogg_vorbis, txt::verify_txt}, +}; use base::{ hash::{generate_hash_for, name_and_hash, Hash}, join_all, }; use groups::layers::design::MapLayer; use hiarc::Hiarc; +use image_utils::png::is_png_image_valid; +pub use image_utils::png::PngValidatorOptions; use serde::{Deserialize, Serialize}; use crate::{ + file::MapFileReader, + header::Header, map::groups::{MapGroup, MapGroupPhysics}, - utils::compressed_size, + utils::{deserialize_twmap_bincode, serialize_twmap_bincode, verify_twmap_bincode}, }; use self::{ @@ -37,14 +47,11 @@ use self::{ /// position or similar stuff of elements in the map layers. /// /// Serialization & Deserialization of all items in the collection happens indepentially (mostly to allow parallel processing). -/// To make it easy use [`Map::read`] & [`Map::write`], which automatically de-/serializes and compresses the map +/// To make it easy use [`Map::read`] & [`Map::write`], which automatically de-/serializes and compresses the map components. /// /// ### De-/serialization notes -/// A common file header is always add in the form of "twmap[u64]" where u64 is a 64-bit sized version number. -/// If performance matters `resources` should be deserialized and processed async to loading the rest of this map file, since resources -/// are always external files and deserializing this map file can be expensive too. -/// `groups` always de-/serializes the physics group indepentially to design groups, -/// this allows the server to process it without design groups. +/// A map file file must contain a [`Header`], whos type is of "twmap". +/// If the whole map is not needed, it's also possible to only load parts of the map (e.g. only the physics group). #[derive(Debug, Hiarc, Clone)] pub struct Map { pub resources: Resources, @@ -56,9 +63,6 @@ pub struct Map { } impl Map { - pub const VERSION: u64 = 2024040200; - pub const FILE_TY: &'static str = "twmap"; - pub(crate) fn validate_resource_and_anim_indices( resources: &Resources, animations: &Animations, @@ -146,146 +150,159 @@ impl Map { Ok(()) } - /// Deserializes the resources and returns the amount of bytes read - pub fn deserialize_resources(uncompressed_file: &[u8]) -> anyhow::Result<(Resources, usize)> { - Ok(bincode::serde::decode_from_slice::( - uncompressed_file, - bincode::config::standard(), - )?) + /// Deserializes the header + pub fn deserialize_header(uncompressed_file: &[u8]) -> anyhow::Result
{ + let file = String::from_utf8(uncompressed_file.into())?; + let mut lines = file.lines(); + Ok(Header { + ty: lines + .next() + .ok_or_else(|| anyhow!("type in header is missing"))? + .into(), + version: lines + .next() + .ok_or_else(|| anyhow!("version in header is missing"))? + .parse()?, + }) } - /// Decompresses the resources. Returns the amount of bytes read - pub fn decompress_resources(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Deserializes the resources + pub fn deserialize_resources(uncompressed_file: &[u8]) -> anyhow::Result { + Ok(serde_json::from_slice::(uncompressed_file)?) + } + + /// Decompresses the resources. + pub fn decompress_resources(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Deserializes the animations and returns the amount of bytes read - pub fn deserialize_animations(uncompressed_file: &[u8]) -> anyhow::Result<(Animations, usize)> { - Ok(bincode::serde::decode_from_slice::( - uncompressed_file, - bincode::config::standard(), - )?) + /// Deserializes the animations + pub fn deserialize_animations(uncompressed_file: &[u8]) -> anyhow::Result { + deserialize_twmap_bincode::(uncompressed_file) } - /// Decompresses the animations. Returns the amount of bytes read - pub fn decompress_animations(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Decompresses the animations. + pub fn decompress_animations(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Deserializes the config and returns the amount of bytes read - pub fn deserialize_config(uncompressed_file: &[u8]) -> anyhow::Result<(Config, usize)> { - Ok(bincode::serde::decode_from_slice::( - uncompressed_file, - bincode::config::standard(), - )?) + /// Deserializes the config + pub fn deserialize_config(uncompressed_file: &[u8]) -> anyhow::Result { + Ok(serde_json::from_slice::(uncompressed_file)?) } - /// Decompresses the config. Returns the amount of bytes read - pub fn decompress_config(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Decompresses the config. + pub fn decompress_config(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Deserializes the meta data and returns the amount of bytes read - pub fn deserialize_meta(uncompressed_file: &[u8]) -> anyhow::Result<(Metadata, usize)> { - Ok(bincode::serde::decode_from_slice::( - uncompressed_file, - bincode::config::standard(), - )?) + /// Deserializes the meta data + pub fn deserialize_meta(uncompressed_file: &[u8]) -> anyhow::Result { + Ok(serde_json::from_slice::(uncompressed_file)?) } - /// Decompresses the meta data. Returns the amount of bytes read - pub fn decompress_meta(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Decompresses the meta data. + pub fn decompress_meta(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Read the map resources. Returns the number of bytes read. - pub fn read_resources(file: &[u8]) -> anyhow::Result<(Resources, usize)> { - let (resources_file, read_bytes_res) = Self::decompress_resources(file)?; - let (resources, _) = Self::deserialize_resources(&resources_file)?; - Ok((resources, read_bytes_res)) + /// Read the map resources. + pub fn read_resources(reader: &MapFileReader) -> anyhow::Result { + let file = tar_entry_to_file( + reader + .entries + .get(Path::new("resource_index.json.zst")) + .ok_or_else(|| anyhow!("resource index was not found in map file"))?, + )?; + let resources_file = Self::decompress_resources(file)?; + let resources = Self::deserialize_resources(&resources_file)?; + Ok(resources) } - /// All maps that the client knows MUST start with "twmap", even if the version changes etc. - pub fn validate_twmap_header(file: &[u8]) -> bool { - let twmap_str_len = Self::FILE_TY.len(); - file.len() >= twmap_str_len - && String::from_utf8_lossy(&file[..Self::FILE_TY.len()]) == Self::FILE_TY + /// All maps that the client knows MUST be of type "twmap", even if the version changes etc. + pub fn validate_twmap_header_type(header: &Header) -> bool { + header.ty == Header::FILE_TY } - /// Read the map resources (and the file header). Returns the number of bytes read. - pub fn read_resources_and_header(file: &[u8]) -> anyhow::Result<(Resources, usize)> { - let header_len = Self::FILE_TY.len() + std::mem::size_of::(); - anyhow::ensure!( - file.len() >= header_len && Self::validate_twmap_header(file), - "file smaller than the size of the header." - ); + /// All maps that the client knows MUST be of type "twmap", even if the version changes etc. + pub fn read_twmap_header(reader: &MapFileReader) -> anyhow::Result
{ + let file = tar_entry_to_file( + reader + .entries + .get(Path::new("header.txt")) + .ok_or_else(|| anyhow!("header was not found in map file"))?, + )?; + let header = Self::deserialize_header(file)?; + Ok(header) + } + + /// Read the map resources (and validate the file header). + pub fn read_resources_and_header(reader: &MapFileReader) -> anyhow::Result { + let header = Self::read_twmap_header(reader)?; anyhow::ensure!( - u64::from_le_bytes( - file[Self::FILE_TY.len()..Self::FILE_TY.len() + std::mem::size_of::()] - .try_into()? - ) == Self::VERSION, - "file version mismatch." + Self::validate_twmap_header_type(&header), + "header validation failed." ); - let file = &file[header_len..]; - - let (resources_file, read_bytes_res) = Self::decompress_resources(file)?; - let (resources, _) = Self::deserialize_resources(&resources_file)?; - Ok((resources, read_bytes_res + header_len)) - } + anyhow::ensure!(header.version == Header::VERSION, "file version mismatch."); - /// Read the map animations. Returns the number of bytes read. - pub fn read_animations(file: &[u8]) -> anyhow::Result<(Animations, usize)> { - let (animations_file, read_bytes_res) = Self::decompress_resources(file)?; - let (animations, _) = Self::deserialize_animations(&animations_file)?; - Ok((animations, read_bytes_res)) + let resources = Self::read_resources(reader)?; + Ok(resources) } - /// Skip the map animations. Returns the number of bytes read. - pub fn skip_animations(file: &[u8]) -> anyhow::Result { - let (size, bytes_read) = super::utils::compressed_size(file)?; - Ok(size as usize + bytes_read) + /// Read the map animations. + pub fn read_animations(reader: &MapFileReader) -> anyhow::Result { + let file = tar_entry_to_file( + reader + .entries + .get(Path::new("animations.twmap_bincode.zst")) + .ok_or_else(|| anyhow!("animations were not found in map file"))?, + )?; + let animations_file = Self::decompress_animations(file)?; + let animations = Self::deserialize_animations(&animations_file)?; + Ok(animations) } - /// Read the map config. Returns the number of bytes read. - pub fn read_config(file: &[u8]) -> anyhow::Result<(Config, usize)> { - let (config_file, read_bytes_res) = Self::decompress_resources(file)?; - let (config, _) = Self::deserialize_config(&config_file)?; - Ok((config, read_bytes_res)) + /// Read the map config. + pub fn read_config(reader: &MapFileReader) -> anyhow::Result { + let file = tar_entry_to_file( + reader + .entries + .get(Path::new("config.json.zst")) + .ok_or_else(|| anyhow!("config was not found in map file"))?, + )?; + let config_file = Self::decompress_config(file)?; + let config = Self::deserialize_config(&config_file)?; + Ok(config) } - /// Read the map meta data. Returns the number of bytes read. - pub fn read_meta(file: &[u8]) -> anyhow::Result<(Metadata, usize)> { - let (meta_file, read_bytes_res) = Self::decompress_resources(file)?; - let (meta_data, _) = Self::deserialize_meta(&meta_file)?; - Ok((meta_data, read_bytes_res)) + /// Read the map meta data. + pub fn read_meta(reader: &MapFileReader) -> anyhow::Result { + let file = tar_entry_to_file( + reader + .entries + .get(Path::new("meta.json.zst")) + .ok_or_else(|| anyhow!("meta was not found in map file"))?, + )?; + let meta_file = Self::decompress_resources(file)?; + let meta_data = Self::deserialize_meta(&meta_file)?; + Ok(meta_data) } /// Read a map file - pub fn read(file: &[u8], tp: &rayon::ThreadPool) -> anyhow::Result { - let header_len = Self::FILE_TY.len() + std::mem::size_of::(); - anyhow::ensure!( - file.len() >= header_len && Self::validate_twmap_header(file), - "file smaller than the size of the header." - ); + pub fn read(reader: &MapFileReader, tp: &rayon::ThreadPool) -> anyhow::Result { + let header = Self::read_twmap_header(reader)?; anyhow::ensure!( - u64::from_le_bytes( - file[Self::FILE_TY.len()..Self::FILE_TY.len() + std::mem::size_of::()] - .try_into()? - ) == Self::VERSION, - "file version mismatch." + Self::validate_twmap_header_type(&header), + "header validation failed." ); - let file = &file[header_len..]; + anyhow::ensure!(header.version == Header::VERSION, "file version mismatch."); - let (resources, read_bytes_res) = Self::read_resources(file)?; + let resources = Self::read_resources(reader)?; - let (groups, read_bytes_groups) = MapGroups::read(&file[read_bytes_res..], tp)?; - let (animations, read_bytes_animations) = - Self::read_animations(&file[read_bytes_res + read_bytes_groups..])?; - let (config, read_bytes_config) = - Self::read_config(&file[read_bytes_res + read_bytes_groups + read_bytes_animations..])?; - let (meta, _) = Self::read_meta( - &file[read_bytes_res + read_bytes_groups + read_bytes_animations + read_bytes_config..], - )?; + let groups = MapGroups::read(reader, tp)?; + let animations = Self::read_animations(reader)?; + let config = Self::read_config(reader)?; + let meta = Self::read_meta(reader)?; Self::validate_resource_and_anim_indices(&resources, &animations, &groups)?; @@ -301,34 +318,19 @@ impl Map { /// Read only the physics group and the config (skips all other stuff). /// /// This is usually nice to use on the server. - pub fn read_physics_group_and_config(file: &[u8]) -> anyhow::Result<(MapGroupPhysics, Config)> { - let header_len = Self::FILE_TY.len() + std::mem::size_of::(); + pub fn read_physics_group_and_config( + reader: &MapFileReader, + ) -> anyhow::Result<(MapGroupPhysics, Config)> { + let header = Self::read_twmap_header(reader)?; anyhow::ensure!( - file.len() >= header_len && Self::validate_twmap_header(file), - "file smaller than the size of the header." + Self::validate_twmap_header_type(&header), + "header validation failed." ); - anyhow::ensure!( - u64::from_le_bytes( - file[Self::FILE_TY.len()..Self::FILE_TY.len() + std::mem::size_of::()] - .try_into()? - ) == Self::VERSION, - "file version mismatch." - ); - let file = &file[header_len..]; - - // size of resources + the size information itself - let (resource_size, read_size) = compressed_size(file)?; + anyhow::ensure!(header.version == Header::VERSION, "file version mismatch."); - let file = &file[resource_size as usize + read_size..]; - let (groups, bytes_read) = MapGroups::read_physics_group(file)?; + let groups = MapGroups::read_physics_group(reader)?; - let file = &file[bytes_read..]; - let bytes_read = MapGroups::skip_design_group(file)?; - - let file = &file[bytes_read..]; - let read_bytes_animations = Self::skip_animations(file)?; - let file = &file[read_bytes_animations..]; - let (config, _) = Self::read_config(file)?; + let config = Self::read_config(reader)?; Ok((groups, config)) } @@ -337,18 +339,14 @@ impl Map { /// See [`Map::read_resources_and_header`] pub fn read_with_resources( resources: Resources, - file_without_res: &[u8], + reader: &MapFileReader, tp: &rayon::ThreadPool, ) -> anyhow::Result { - let (groups, read_bytes_groups) = MapGroups::read(file_without_res, tp)?; - - let (animations, read_bytes_animations) = - Self::read_animations(&file_without_res[read_bytes_groups..])?; - let (config, read_bytes_config) = - Self::read_config(&file_without_res[read_bytes_groups + read_bytes_animations..])?; - let (meta, _) = Self::read_meta( - &file_without_res[read_bytes_groups + read_bytes_animations + read_bytes_config..], - )?; + let groups = MapGroups::read(reader, tp)?; + + let animations = Self::read_animations(reader)?; + let config = Self::read_config(reader)?; + let meta = Self::read_meta(reader)?; Self::validate_resource_and_anim_indices(&resources, &animations, &groups)?; @@ -361,23 +359,26 @@ impl Map { }) } - /// Serializes the resources and returns the amount of bytes written + /// Serializes the header + pub fn serialize_header(res: &Header, writer: &mut W) -> anyhow::Result<()> { + let ty = format!("{}\n", res.ty).into_bytes(); + let version = format!("{}\n", res.version).into_bytes(); + writer.write_all(&ty)?; + writer.write_all(&version)?; + Ok(()) + } + + /// Serializes the resources pub fn serialize_resources( res: &Resources, writer: &mut W, - ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - res, - writer, - bincode::config::standard(), - )?) + ) -> anyhow::Result<()> { + serde_json::to_writer_pretty(writer, res)?; + Ok(()) } - pub fn compress_resources( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + pub fn compress_resources(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } /// Serializes the animations and returns the amount of bytes written @@ -385,109 +386,172 @@ impl Map { anims: &Animations, writer: &mut W, ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - anims, - writer, - bincode::config::standard(), - )?) + serialize_twmap_bincode(anims, writer) } - pub fn compress_animations( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + pub fn compress_animations(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } - /// Serializes the config and returns the amount of bytes written + /// Serializes the config pub fn serialize_config( config: &Config, writer: &mut W, - ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - config, - writer, - bincode::config::standard(), - )?) + ) -> anyhow::Result<()> { + serde_json::to_writer_pretty(writer, config)?; + Ok(()) } - pub fn compress_config( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + pub fn compress_config(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } - /// Serializes the meta and returns the amount of bytes written + /// Serializes the meta pub fn serialize_meta( meta_data: &Metadata, writer: &mut W, - ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - meta_data, - writer, - bincode::config::standard(), - )?) + ) -> anyhow::Result<()> { + serde_json::to_writer_pretty(writer, meta_data)?; + Ok(()) } - pub fn compress_meta( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + pub fn compress_meta(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } /// Write a map file to a writer - pub fn write( - &self, - writer: &mut W, - tp: &rayon::ThreadPool, - ) -> anyhow::Result<()> { - let (resources, groups, animations, config, meta) = tp.install(|| { + pub fn write(&self, tp: &rayon::ThreadPool) -> anyhow::Result> { + let (header, resources, groups, animations, config, meta) = tp.install(|| { join_all!( || { - let mut resources: Vec = Vec::new(); let mut serializer_helper: Vec = Default::default(); - Self::serialize_resources(&self.resources, &mut serializer_helper)?; - Self::compress_resources(&serializer_helper, &mut resources)?; - anyhow::Ok(resources) + Self::serialize_header( + &Header { + ty: Header::FILE_TY.to_string(), + version: Header::VERSION, + }, + &mut serializer_helper, + )?; + anyhow::Ok(serializer_helper) }, || { - let mut groups: Vec = Vec::new(); - MapGroups::write(&self.groups, &mut groups, tp)?; - anyhow::Ok(groups) + let mut serializer_helper: Vec = Default::default(); + Self::serialize_resources(&self.resources, &mut serializer_helper)?; + Self::compress_resources(&serializer_helper) }, + || { MapGroups::write(&self.groups, tp) }, || { - let mut animations: Vec = Vec::new(); let mut serializer_helper: Vec = Default::default(); Self::serialize_animations(&self.animations, &mut serializer_helper)?; - Self::compress_animations(&serializer_helper, &mut animations)?; - anyhow::Ok(animations) + Self::compress_animations(&serializer_helper) }, || { - let mut config: Vec = Vec::new(); let mut serializer_helper: Vec = Default::default(); Self::serialize_config(&self.config, &mut serializer_helper)?; - Self::compress_config(&serializer_helper, &mut config)?; - anyhow::Ok(config) + Self::compress_config(&serializer_helper) }, || { - let mut meta_data: Vec = Vec::new(); let mut serializer_helper: Vec = Default::default(); Self::serialize_meta(&self.meta, &mut serializer_helper)?; - Self::compress_meta(&serializer_helper, &mut meta_data)?; - anyhow::Ok(meta_data) + Self::compress_meta(&serializer_helper) } ) }); - writer.write_all(Self::FILE_TY.as_bytes())?; - writer.write_all(&Self::VERSION.to_le_bytes())?; - writer.write_all(&resources?)?; - writer.write_all(&groups?)?; - writer.write_all(&animations?)?; - writer.write_all(&config?)?; - writer.write_all(&meta?)?; + let mut builder = new_tar(); + + tar_add_file(&mut builder, "header.txt", &header?); + tar_add_file(&mut builder, "resource_index.json.zst", &resources?); + + let (physics, bg, fg) = groups?; + tar_add_file(&mut builder, "groups/physics.twmap_bincode.zst", &physics); + tar_add_file(&mut builder, "groups/background.twmap_bincode.zst", &bg); + tar_add_file(&mut builder, "groups/foreground.twmap_bincode.zst", &fg); + + tar_add_file(&mut builder, "animations.twmap_bincode.zst", &animations?); + tar_add_file(&mut builder, "config.json.zst", &config?); + tar_add_file(&mut builder, "meta.json.zst", &meta?); + + Ok(builder.into_inner()?) + } + + /// Validate a downloaded map's entries. + /// + /// This includes: + /// - image files + /// - text files + /// - sound files + /// - the map header (without a version check) + /// - twmap_bincode files (the map internal bincode file format), __BUT__ + /// it does not check it's file contents (it does not deserialize it). + /// - zstd file (the content must be one of the above types) + pub fn validate_downloaded_map_file( + reader: &MapFileReader, + png_options: PngValidatorOptions, + ) -> anyhow::Result<()> { + let header = Self::read_twmap_header(reader)?; + anyhow::ensure!( + Self::validate_twmap_header_type(&header), + "header validation failed." + ); + + let files = reader.read_all()?; + + for (path, file) in files { + fn verify_file( + path: PathBuf, + file: Vec, + png_options: PngValidatorOptions, + ) -> anyhow::Result<()> { + let file_ext = path + .extension() + .ok_or_else(|| { + anyhow!( + "no file extension found during \ + downloaded map validation." + ) + })? + .to_str() + .ok_or_else(|| { + anyhow!( + "file extension check during downloaded \ + map contained invalid characters" + ) + })?; + let file_name = path + .file_stem() + .ok_or_else(|| { + anyhow!( + "no file stem found during \ + downloaded map validation." + ) + })? + .to_str() + .ok_or_else(|| { + anyhow!( + "file steam check during downloaded \ + map contained invalid characters" + ) + })?; + match file_ext { + "ogg" => verify_ogg_vorbis(&file)?, + "png" => is_png_image_valid(&file, png_options)?, + "txt" => verify_txt(&file, file_name)?, + "json" => verify_json(&file)?, + "twmap_bincode" => verify_twmap_bincode(&file)?, + "zst" => { + let file = crate::utils::decompress(&file)?; + verify_file(file_name.into(), file, png_options)?; + } + _ => anyhow::bail!( + "file extension: {} is unknown and cannot be validated.", + file_ext + ), + } + Ok(()) + } + verify_file(path, file, png_options)?; + } Ok(()) } diff --git a/game/map/src/map/animations.rs b/game/map/src/map/animations.rs index 3edd8fe5..089027db 100644 --- a/game/map/src/map/animations.rs +++ b/game/map/src/map/animations.rs @@ -152,7 +152,7 @@ where if d > V::from(0) { // only one 'real' solution let s = sqrt(d); - return cbrt(s - q) - cbrt(s + q) - sub; + cbrt(s - q) - cbrt(s + q) - sub } else if d == V::from(0) { // one single, one double solution or triple solution let s = cbrt(-q); @@ -275,7 +275,7 @@ where ); // value = y(t) - res[c] = F::from_fixed(Self::bezier(&p0.y, &p1.y, &p2.y, &p3.y, a)); + res[c] = F::saturating_from_fixed(Self::bezier(&p0.y, &p1.y, &p2.y, &p3.y, a)); } return res; } @@ -285,7 +285,7 @@ where for c in 0..CHANNELS { let v0: ffixed = point1.value[c].to_fixed(); let v1: ffixed = point2_value[c].to_fixed(); - res[c] = F::from_fixed(v0 + (v1 - v0) * a); + res[c] = F::saturating_from_fixed(v0 + (v1 - v0) * a); } res } diff --git a/game/map/src/map/groups.rs b/game/map/src/map/groups.rs index 6a3e33cc..1d9b29e9 100644 --- a/game/map/src/map/groups.rs +++ b/game/map/src/map/groups.rs @@ -1,12 +1,19 @@ pub mod layers; +use std::path::Path; + use anyhow::anyhow; +use assets_base::tar::tar_entry_to_file; use base::join_all; use hiarc::Hiarc; use math::math::vector::{fvec2, ufvec2}; use serde::{Deserialize, Serialize}; -use crate::types::NonZeroU16MinusOne; +use crate::{ + file::MapFileReader, + types::NonZeroU16MinusOne, + utils::{deserialize_twmap_bincode, serialize_twmap_bincode}, +}; use self::layers::{design::MapLayer, physics::MapLayerPhysics, tiles::TileBase}; @@ -115,8 +122,7 @@ impl<'de> Deserialize<'de> for MapGroupPhysics { } } { return Err(serde::de::Error::custom(format!( - "could not validate physics layer: {}", - err + "could not validate physics layer: {err}" ))); } } @@ -153,8 +159,7 @@ impl<'de> Deserialize<'de> for MapGroupPhysics { Ok(()) } { return Err(serde::de::Error::custom(format!( - "could not validate physics layer: {}", - err + "could not validate physics layer: {err}" ))); } @@ -191,14 +196,9 @@ pub struct MapGroups { } impl MapGroups { - /// Deserializes the physics group and returns the amount of bytes read - pub fn deserialize_physics_group( - uncompressed_file: &[u8], - ) -> anyhow::Result<(MapGroupPhysics, usize)> { - Ok(bincode::serde::decode_from_slice::( - uncompressed_file, - bincode::config::standard(), - )?) + /// Deserializes the physics group + pub fn deserialize_physics_group(uncompressed_file: &[u8]) -> anyhow::Result { + deserialize_twmap_bincode::(uncompressed_file) } /// Serializes the physics group and returns the amount of bytes written @@ -206,50 +206,34 @@ impl MapGroups { grp: &MapGroupPhysics, writer: &mut W, ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - grp, - writer, - bincode::config::standard(), - )?) + serialize_twmap_bincode(grp, writer) } - /// Decompresses the physics group, returns the amount of bytes read - pub fn decompress_physics_group(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Decompresses the physics group + pub fn decompress_physics_group(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Compresses the physics group, returns the amount of bytes written - pub fn compress_physics_group( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + /// Compresses the physics group + pub fn compress_physics_group(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } - fn deserialize_design_groups( - uncompressed_file: &[u8], - ) -> anyhow::Result<(Vec, usize)> { - Ok(bincode::serde::decode_from_slice::, _>( - uncompressed_file, - bincode::config::standard(), - )?) + fn deserialize_design_groups(uncompressed_file: &[u8]) -> anyhow::Result> { + deserialize_twmap_bincode::>(uncompressed_file) } fn serialize_design_groups( grps: &Vec, writer: &mut W, ) -> anyhow::Result { - Ok(bincode::serde::encode_into_std_write( - grps, - writer, - bincode::config::standard(), - )?) + serialize_twmap_bincode(grps, writer) } - /// Deserializes the foreground groups and returns the amount of bytes read + /// Deserializes the foreground groups pub(crate) fn deserialize_foreground_groups( uncompressed_file: &[u8], - ) -> anyhow::Result<(Vec, usize)> { + ) -> anyhow::Result> { Self::deserialize_design_groups(uncompressed_file) } @@ -261,10 +245,10 @@ impl MapGroups { Self::serialize_design_groups(grps, writer) } - /// Deserializes the background groups and returns the amount of bytes read + /// Deserializes the background groups pub(crate) fn deserialize_background_groups( uncompressed_file: &[u8], - ) -> anyhow::Result<(Vec, usize)> { + ) -> anyhow::Result> { Self::deserialize_design_groups(uncompressed_file) } @@ -276,91 +260,97 @@ impl MapGroups { Self::serialize_design_groups(grps, writer) } - /// Decompresses the background & foreground groups, returns the amount of bytes read - pub fn decompress_design_groups(file: &[u8]) -> anyhow::Result<(Vec, usize)> { + /// Decompresses the background & foreground groups + pub fn decompress_design_group(file: &[u8]) -> anyhow::Result> { crate::utils::decompress(file) } - /// Compresses the background & foreground groups, returns the amount of bytes read - pub fn compress_design_groups( - uncompressed_file: &[u8], - writer: &mut W, - ) -> anyhow::Result<()> { - crate::utils::compress(uncompressed_file, writer) + /// Compresses the background & foreground groups + pub fn compress_design_group(uncompressed_file: &[u8]) -> anyhow::Result> { + crate::utils::compress(uncompressed_file) } - /// Read the map's game group. returns the amount of bytes read. - pub(crate) fn read(file: &[u8], tp: &rayon::ThreadPool) -> anyhow::Result<(Self, usize)> { - let (physics_group_file, bytes_read) = Self::decompress_physics_group(file)?; - let (physics_group, design_groups) = tp.install(|| { + /// Read the map's game group. + pub(crate) fn read(reader: &MapFileReader, tp: &rayon::ThreadPool) -> anyhow::Result { + let physics_file = tar_entry_to_file( + reader + .entries + .get(Path::new("groups/physics.twmap_bincode.zst")) + .ok_or_else(|| anyhow!("physics group was not found in map file"))?, + )?; + let bg_file = tar_entry_to_file( + reader + .entries + .get(Path::new("groups/background.twmap_bincode.zst")) + .ok_or_else(|| anyhow!("background groups was not found in map file"))?, + )?; + let fg_file = tar_entry_to_file( + reader + .entries + .get(Path::new("groups/foreground.twmap_bincode.zst")) + .ok_or_else(|| anyhow!("foreground groups was not found in map file"))?, + )?; + let (physics_group, background_groups, foreground_groups) = tp.install(|| { join_all!( || { - let (physics_group, _) = Self::deserialize_physics_group(&physics_group_file)?; + let physics_group_file = Self::decompress_physics_group(physics_file)?; + let physics_group = Self::deserialize_physics_group(&physics_group_file)?; anyhow::Ok(physics_group) }, || { - let (design_groups_file, bytes_read_group) = - Self::decompress_design_groups(&file[bytes_read..])?; - - let (background_groups, bytes_read) = - Self::deserialize_background_groups(&design_groups_file)?; - let (foreground_groups, _) = - Self::deserialize_foreground_groups(&design_groups_file[bytes_read..])?; - anyhow::Ok((bytes_read_group, background_groups, foreground_groups)) + let bg_group_file = Self::decompress_design_group(bg_file)?; + + let background_groups = Self::deserialize_background_groups(&bg_group_file)?; + anyhow::Ok(background_groups) + }, + || { + let fg_group_file = Self::decompress_design_group(fg_file)?; + + let foreground_groups = Self::deserialize_foreground_groups(&fg_group_file)?; + anyhow::Ok(foreground_groups) } ) }); - let (bytes_read_group, background_groups, foreground_groups) = design_groups?; - - Ok(( - Self { - physics: physics_group?, - background: background_groups, - foreground: foreground_groups, - }, - bytes_read + bytes_read_group, - )) - } - - /// Returns the physics group and the amount of bytes read - pub fn read_physics_group(file: &[u8]) -> anyhow::Result<(MapGroupPhysics, usize)> { - let (physics_group_file, bytes_read) = Self::decompress_physics_group(file)?; - let (physics_group, _) = Self::deserialize_physics_group(&physics_group_file)?; - anyhow::Ok((physics_group, bytes_read)) + Ok(Self { + physics: physics_group?, + background: background_groups?, + foreground: foreground_groups?, + }) } - /// Skip the design groups and returns the amount of bytes skipped - pub fn skip_design_group(file: &[u8]) -> anyhow::Result { - let (design_group_size, bytes_read) = crate::utils::compressed_size(file)?; - anyhow::Ok(design_group_size as usize + bytes_read) + /// Returns the physics group + pub fn read_physics_group(reader: &MapFileReader) -> anyhow::Result { + let physics_file = tar_entry_to_file( + reader + .entries + .get(Path::new("groups/physics.twmap_bincode.zst")) + .ok_or_else(|| anyhow!("physics group was not found in map file"))?, + )?; + let physics_group_file = Self::decompress_physics_group(physics_file)?; + + let physics_group = Self::deserialize_physics_group(&physics_group_file)?; + anyhow::Ok(physics_group) } /// Write a map file to a writer - pub fn write( - &self, - writer: &mut W, - tp: &rayon::ThreadPool, - ) -> anyhow::Result<()> { + pub fn write(&self, tp: &rayon::ThreadPool) -> anyhow::Result<(Vec, Vec, Vec)> { let (physics, bg_fg) = tp.install(|| { tp.join( || { - let mut physics: Vec = Default::default(); let mut serialized_physics: Vec = Default::default(); Self::serialize_physics_group(&self.physics, &mut serialized_physics)?; - Self::compress_physics_group(&serialized_physics, &mut physics)?; - anyhow::Ok(physics) + Self::compress_physics_group(&serialized_physics) }, || { - let mut bg_fg: Vec = Default::default(); - let (serialized_bg, serialized_fg) = tp.join( + let (bg, fg) = tp.join( || { let mut serialized_bg: Vec = Default::default(); Self::serialize_background_groups( &self.background, &mut serialized_bg, )?; - anyhow::Ok(serialized_bg) + Self::compress_design_group(&serialized_bg) }, || { let mut serialized_fg: Vec = Default::default(); @@ -368,20 +358,15 @@ impl MapGroups { &self.foreground, &mut serialized_fg, )?; - anyhow::Ok(serialized_fg) + Self::compress_design_group(&serialized_fg) }, ); - Self::compress_design_groups( - &[serialized_bg?, serialized_fg?].concat(), - &mut bg_fg, - )?; - anyhow::Ok(bg_fg) + anyhow::Ok((bg?, fg?)) }, ) }); - writer.write_all(&physics?)?; - writer.write_all(&bg_fg?)?; - Ok(()) + let (bg, fg) = bg_fg?; + Ok((physics?, bg, fg)) } } diff --git a/game/map/src/map/groups/layers/physics.rs b/game/map/src/map/groups/layers/physics.rs index 440f70f2..d7068b84 100644 --- a/game/map/src/map/groups/layers/physics.rs +++ b/game/map/src/map/groups/layers/physics.rs @@ -70,7 +70,7 @@ pub enum MapLayerPhysicsRef<'a> { } impl MapLayerPhysics { - pub fn as_ref(&self) -> MapLayerPhysicsRef { + pub fn as_ref(&self) -> MapLayerPhysicsRef<'_> { match self { MapLayerPhysics::Arbitrary(layer) => MapLayerPhysicsRef::Arbitrary(layer), MapLayerPhysics::Game(layer) => MapLayerPhysicsRef::Game(layer), @@ -84,7 +84,7 @@ impl MapLayerPhysics { } impl MapLayerPhysicsRef<'_> { - pub fn tiles_ref(&self) -> MapTileLayerPhysicsTilesRef { + pub fn tiles_ref(&self) -> MapTileLayerPhysicsTilesRef<'_> { match self { Self::Arbitrary(_) => panic!("not a tile layer"), Self::Game(layer) => MapTileLayerPhysicsTilesRef::Game(&layer.tiles), diff --git a/game/map/src/map/groups/layers/tiles.rs b/game/map/src/map/groups/layers/tiles.rs index 9a572ca3..9047ab6a 100644 --- a/game/map/src/map/groups/layers/tiles.rs +++ b/game/map/src/map/groups/layers/tiles.rs @@ -194,7 +194,7 @@ impl MapTileLayerPhysicsTiles { } } - pub fn as_ref(&self) -> MapTileLayerPhysicsTilesRef { + pub fn as_ref(&self) -> MapTileLayerPhysicsTilesRef<'_> { match self { MapTileLayerPhysicsTiles::Arbitrary(tiles) => { MapTileLayerPhysicsTilesRef::Arbitrary(tiles) diff --git a/game/map/src/skeleton/groups/layers/physics.rs b/game/map/src/skeleton/groups/layers/physics.rs index 9dbcc0a9..2110d3de 100644 --- a/game/map/src/skeleton/groups/layers/physics.rs +++ b/game/map/src/skeleton/groups/layers/physics.rs @@ -116,7 +116,7 @@ impl MapLayerPhysicsSkeleton { MapLayerPhysicsSkeleton::Tune(layer) => &mut layer.user, } } - pub fn layer_ref(&self) -> MapLayerPhysicsRef { + pub fn layer_ref(&self) -> MapLayerPhysicsRef<'_> { match self { MapLayerPhysicsSkeleton::Arbitrary(layer) => MapLayerPhysicsRef::Arbitrary(&layer.buf), MapLayerPhysicsSkeleton::Game(layer) => MapLayerPhysicsRef::Game(&layer.layer), diff --git a/game/map/src/utils.rs b/game/map/src/utils.rs index 872ac33e..7e7ff75c 100644 --- a/game/map/src/utils.rs +++ b/game/map/src/utils.rs @@ -1,45 +1,117 @@ -use std::io::{Read, Write}; - -/// returns the size of the compressed chunk and the size that was -/// read from `file` -pub fn compressed_size(file: &[u8]) -> anyhow::Result<(u32, usize)> { - let size_mem_size = std::mem::size_of::(); - anyhow::ensure!(file.len() >= size_mem_size); - let file_size = u32::from_le_bytes([file[0], file[1], file[2], file[3]]); - Ok((file_size, size_mem_size)) -} +use std::{ + io::{Read, Write}, + path::Path, +}; + +use serde::{de::DeserializeOwned, Serialize}; /// Decompresses a compressed file into an uncompressed file. Returns the bytes read /// ### Prefer this method over using compression algorithms yourself, because it has side effects -pub fn decompress(file: &[u8]) -> anyhow::Result<(Vec, usize)> { - let (file_size, read_size) = compressed_size(file)?; +pub fn decompress(file: &[u8]) -> anyhow::Result> { let mut uncompressed_file: Vec = Default::default(); #[cfg(not(feature = "rust_zstd"))] { - let mut dec = zstd::Decoder::new(&file[read_size..read_size + file_size as usize])?; + let mut dec = zstd::Decoder::new(file)?; dec.read_to_end(&mut uncompressed_file)?; dec.finish(); } #[cfg(feature = "rust_zstd")] { - let mut decoder = ruzstd::decoding::StreamingDecoder::new( - &file[read_size..read_size + file_size as usize], - )?; + let mut decoder = ruzstd::decoding::StreamingDecoder::new(file)?; decoder.read_to_end(&mut uncompressed_file)?; } - Ok((uncompressed_file, read_size + file_size as usize)) + Ok(uncompressed_file) } /// Compresses an uncompressed file into a compressed file. /// ### Prefer this method over using compression algorithms yourself. It has side effects -pub fn compress(uncompressed_file: &[u8], writer: &mut W) -> anyhow::Result<()> { +pub fn compress(uncompressed_file: &[u8]) -> anyhow::Result> { let mut write_data: Vec = Default::default(); // Compression level 15 seems to be a good trait performance vs map size // Tested with the test benchmark in this crate on some maps. let mut enc = zstd::Encoder::new(&mut write_data, 15)?; enc.write_all(uncompressed_file)?; enc.finish()?; - writer.write_all(&(write_data.len() as u32).to_le_bytes())?; - writer.write_all(&write_data)?; + Ok(write_data) +} + +const TWMAP_BINCODE: &str = "twmap_bincode"; + +/// Deserializes the given type from the internal twmap bincode format. +pub fn deserialize_twmap_bincode( + uncompressed_file: &[u8], +) -> anyhow::Result { + let (ty, read_size) = bincode::serde::decode_from_slice::( + uncompressed_file, + bincode::config::standard(), + )?; + let uncompressed_file = &uncompressed_file[read_size..]; + let (expected_size, read_size) = bincode::serde::decode_from_slice::( + uncompressed_file, + bincode::config::standard(), + )?; + let uncompressed_file = &uncompressed_file[read_size..]; + anyhow::ensure!( + ty == TWMAP_BINCODE, + "given file is not of type {TWMAP_BINCODE}" + ); + let (res, read_size) = + bincode::serde::decode_from_slice::(uncompressed_file, bincode::config::standard())?; + anyhow::ensure!( + read_size as u64 == expected_size, + "deserialization size is wrong, expected {expected_size} got {read_size}" + ); + Ok(res) +} + +/// Serializes the given type to the internal twmap bincode format. +/// Returns the amount of bytes written. +pub fn serialize_twmap_bincode( + value: &T, + writer: &mut W, +) -> anyhow::Result { + let written_bytes_str = + bincode::serde::encode_into_std_write(TWMAP_BINCODE, writer, bincode::config::standard())?; + let mut tmp_writer: Vec = Default::default(); + let written_bytes = + bincode::serde::encode_into_std_write(value, &mut tmp_writer, bincode::config::standard())?; + let written_bytes_file_size = bincode::serde::encode_into_std_write( + tmp_writer.len() as u64, + writer, + bincode::config::standard(), + )?; + writer.write_all(&tmp_writer)?; + Ok(written_bytes_str + written_bytes_file_size + written_bytes) +} + +/// Serializes the given type to the internal twmap bincode format. +/// Returns the amount of bytes written. +pub fn verify_twmap_bincode(file: &[u8]) -> anyhow::Result<()> { + let (ty, read_size) = + bincode::serde::decode_from_slice::(file, bincode::config::standard())?; + let file = &file[read_size..]; + let (expected_size, read_size) = + bincode::serde::decode_from_slice::(file, bincode::config::standard())?; + let file = &file[read_size..]; + anyhow::ensure!( + ty == TWMAP_BINCODE, + "given file is not of type {TWMAP_BINCODE}" + ); + anyhow::ensure!( + file.len() as u64 == expected_size, + "deserialization size is wrong, expected {expected_size} got {read_size}" + ); Ok(()) } + +/// Get's the file extension of the given path or if the file name ends with +/// `.twmap.tar` it returns `twmap.tar`. +pub fn file_ext_or_twmap_tar(path: &Path) -> Option<&str> { + let ext = path.extension().and_then(|e| e.to_str())?; + let stem = path.file_stem().and_then(|e| e.to_str())?; + Some(if stem.ends_with("twmap") && ext == "tar" { + "twmap.tar" + } else { + ext + }) +} diff --git a/game/master-server-types/src/addr.rs b/game/master-server-types/src/addr.rs index 60df0376..d58e9a7a 100644 --- a/game/master-server-types/src/addr.rs +++ b/game/master-server-types/src/addr.rs @@ -149,7 +149,7 @@ impl serde::Serialize for Addr { S: serde::Serializer, { let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); - write!(&mut buf, "{}", self).unwrap(); + write!(&mut buf, "{self}").unwrap(); serializer.serialize_str(&buf) } } @@ -212,8 +212,8 @@ impl fmt::Display for ParseRegisterAddrError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::ParseRegisterAddrError::*; match *self { - Url(e) => write!(f, "URL parse error: {}", e), - Protocol(e) => write!(f, "protocol parse error: {}", e), + Url(e) => write!(f, "URL parse error: {e}"), + Protocol(e) => write!(f, "protocol parse error: {e}"), HostNotConnectingAddressInvalid => write!( f, "register address must have domain connecting-address.invalid" @@ -243,7 +243,7 @@ impl serde::Serialize for RegisterAddr { S: serde::Serializer, { let mut buf: ArrayString<[u8; 128]> = ArrayString::new(); - write!(&mut buf, "{}", self).unwrap(); + write!(&mut buf, "{self}").unwrap(); serializer.serialize_str(&buf) } } diff --git a/game/master-server-types/src/locations.rs b/game/master-server-types/src/locations.rs index 094e523d..166c3173 100644 --- a/game/master-server-types/src/locations.rs +++ b/game/master-server-types/src/locations.rs @@ -28,11 +28,11 @@ impl Locations { } pub fn read(filename: &Path) -> Result { let mut reader = csv::Reader::from_path(filename) - .map_err(|e| LocationsError(format!("error opening {:?}: {}", filename, e)))?; + .map_err(|e| LocationsError(format!("error opening {filename:?}: {e}")))?; let locations: Result, _> = reader.deserialize().collect(); Ok(Locations { locations: locations - .map_err(|e| LocationsError(format!("error deserializing: {}", e)))?, + .map_err(|e| LocationsError(format!("error deserializing: {e}")))?, }) } pub fn lookup(&self, addr: IpAddr) -> Option { diff --git a/game/render-game-wasm/src/render/render_wasm_manager.rs b/game/render-game-wasm/src/render/render_wasm_manager.rs index 950a8a71..1dd64acc 100644 --- a/game/render-game-wasm/src/render/render_wasm_manager.rs +++ b/game/render-game-wasm/src/render/render_wasm_manager.rs @@ -27,15 +27,15 @@ pub enum RenderGameMod { } pub enum RenderGameWrapper { - Native(RenderGame), - Wasm(RenderWasm), + Native(Box), + Wasm(Box), } impl AsRef for RenderGameWrapper { fn as_ref(&self) -> &(dyn RenderGameInterface + 'static) { match self { - Self::Native(state) => state, - Self::Wasm(state) => state, + Self::Native(state) => state.as_ref(), + Self::Wasm(state) => state.as_ref(), } } } @@ -43,8 +43,8 @@ impl AsRef for RenderGameWrapper { impl AsMut for RenderGameWrapper { fn as_mut(&mut self) -> &mut (dyn RenderGameInterface + 'static) { match self { - Self::Native(state) => state, - Self::Wasm(state) => state, + Self::Native(state) => state.as_mut(), + Self::Wasm(state) => state.as_mut(), } } } @@ -105,12 +105,12 @@ impl RenderGameWasmManager { props, ) .map_err(|err| anyhow!(err))?; - RenderGameWrapper::Native(state) + RenderGameWrapper::Native(Box::new(state)) } RenderGameMod::Wasm { file } => { let state = RenderWasm::new(sound, graphics, backend, io, &file, map_file, config, props)?; - RenderGameWrapper::Wasm(state) + RenderGameWrapper::Wasm(Box::new(state)) } }; Ok(Self { diff --git a/game/vanilla/Cargo.toml b/game/vanilla/Cargo.toml index c35a31dc..45843764 100644 --- a/game/vanilla/Cargo.toml +++ b/game/vanilla/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +legacy-map = { path = "../legacy-map" } game-base = { path = "../game-base" } game-interface = { path = "../game-interface" } map = { path = "../map" } diff --git a/game/vanilla/src/collision.rs b/game/vanilla/src/collision.rs index 6b7cc6fb..1b0ea8ae 100644 --- a/game/vanilla/src/collision.rs +++ b/game/vanilla/src/collision.rs @@ -2,8 +2,8 @@ pub mod collision { use anyhow::anyhow; use bitflags::bitflags; use config::{traits::ConfigInterface, ConfigInterface}; - use game_base::mapdef_06::DdraceTileNum; use hiarc::Hiarc; + use legacy_map::mapdef_06::DdraceTileNum; use map::map::groups::{ layers::{ physics::MapLayerPhysics, @@ -179,7 +179,7 @@ pub mod collision { // TODO: use u8 or an enum for tile indices, instead of i32 impl Collision { pub fn new( - physics_group: &MapGroupPhysics, + physics_group: MapGroupPhysics, load_all_layers: bool, ) -> anyhow::Result> { let width = physics_group.attr.width.get() as u32; @@ -189,23 +189,26 @@ pub mod collision { let mut front_layer = None; let mut tune_layer = None; let mut tele_layer = None; - physics_group.layers.iter().for_each(|layer| match layer { - MapLayerPhysics::Arbitrary(_) => {} - MapLayerPhysics::Game(layer) => { - game_layer = Some(layer); - } - MapLayerPhysics::Front(layer) => { - front_layer = load_all_layers.then_some(layer); - } - MapLayerPhysics::Tele(layer) => { - tele_layer = load_all_layers.then_some(layer); - } - MapLayerPhysics::Speedup(_) => {} - MapLayerPhysics::Switch(_) => {} - MapLayerPhysics::Tune(layer) => { - tune_layer = load_all_layers.then_some(layer); - } - }); + physics_group + .layers + .into_iter() + .for_each(|layer| match layer { + MapLayerPhysics::Arbitrary(_) => {} + MapLayerPhysics::Game(layer) => { + game_layer = Some(layer); + } + MapLayerPhysics::Front(layer) => { + front_layer = load_all_layers.then_some(layer); + } + MapLayerPhysics::Tele(layer) => { + tele_layer = load_all_layers.then_some(layer); + } + MapLayerPhysics::Speedup(_) => {} + MapLayerPhysics::Switch(_) => {} + MapLayerPhysics::Tune(layer) => { + tune_layer = load_all_layers.then_some(layer); + } + }); let game_layer = game_layer.ok_or_else(|| anyhow!("no game layer found"))?; diff --git a/game/vanilla/src/entities/character.rs b/game/vanilla/src/entities/character.rs index 98c4b08e..00a08032 100644 --- a/game/vanilla/src/entities/character.rs +++ b/game/vanilla/src/entities/character.rs @@ -15,7 +15,6 @@ pub mod character { use base::linked_hash_map_view::{ FxLinkedHashMap, FxLinkedHashSet, LinkedHashMapView, LinkedHashMapViewMut, }; - use game_base::mapdef_06::DdraceTileNum; use game_interface::{ events::{ GameBuffNinjaEventSound, GameBuffSoundEvent, GameCharacterEffectEvent, @@ -39,6 +38,7 @@ pub mod character { }, }; use hiarc::{hiarc_safer_rc_refcell, Hiarc}; + use legacy_map::mapdef_06::DdraceTileNum; use map::map::groups::layers::tiles::Tile; use pool::{datatypes::PoolFxLinkedHashMap, mt_pool::Pool as MtPool}; use rustc_hash::FxHashSet; diff --git a/game/vanilla/src/entities/character/core.rs b/game/vanilla/src/entities/character/core.rs index f8fb4d70..2a4f69d0 100644 --- a/game/vanilla/src/entities/character/core.rs +++ b/game/vanilla/src/entities/character/core.rs @@ -2,7 +2,6 @@ pub mod character_core { use std::ops::{AddAssign, ControlFlow}; use crate::reusable::{CloneWithCopyableElements, ReusableCore}; - use game_base::mapdef_06::DdraceTileNum; use game_interface::{ events::{ GameCharacterEffectEvent, GameCharacterEventEffect, GameCharacterEventSound, @@ -15,6 +14,7 @@ pub mod character_core { }, }; use hiarc::Hiarc; + use legacy_map::mapdef_06::DdraceTileNum; use num::FromPrimitive; use crate::{ @@ -417,7 +417,7 @@ pub mod character_core { let ids = pos .field .by_distancef(hook_pos, hook_len + (physical_size() + 2.0)); - pipe.get_other_character_id_and_cores_iter_by_ids_mut( + let _ = pipe.get_other_character_id_and_cores_iter_by_ids_mut( &ids, &mut |char_id, char_core, _, char_pos| { if !(is_super || char_core.is_super) && (char_core.solo || solo) { @@ -577,7 +577,7 @@ pub mod character_core { let tunings = collision.get_tune_at(pos.pos()); const PHY_RANGE_COLLISION: i32 = (physical_size() * 1.25) as i32; let mut ids = pos.in_range(PHY_RANGE_COLLISION); - pipe.get_other_character_id_and_cores_iter_by_ids_mut( + let _ = pipe.get_other_character_id_and_cores_iter_by_ids_mut( &ids, &mut |_, char_core, _, char_pos| { if !(self.is_super || char_core.is_super) && (self.solo || char_core.solo) { @@ -617,7 +617,7 @@ pub mod character_core { // reuse the previous ids here ids.clear(); ids.push(hooked_player); - pipe.get_other_character_id_and_cores_iter_by_ids_mut( + let _ = pipe.get_other_character_id_and_cores_iter_by_ids_mut( &ids, &mut |char_id, char_core, _, char_pos| { if !(self.is_super || char_core.is_super) && (self.solo || char_core.solo) { diff --git a/game/vanilla/src/entities/character/pos.rs b/game/vanilla/src/entities/character/pos.rs index 35719378..d0a2e158 100644 --- a/game/vanilla/src/entities/character/pos.rs +++ b/game/vanilla/src/entities/character/pos.rs @@ -56,7 +56,7 @@ pub mod character_pos { } #[inline] - fn entry(&mut self, index: u32) -> WorldMapEntry { + fn entry(&mut self, index: u32) -> WorldMapEntry<'_> { match self { WorldMap::Hashed { map, world_pool } => WorldMapEntry::Hashed { entry: map.entry(index), @@ -188,12 +188,12 @@ pub mod character_pos { } #[inline] - fn get_at_index_mut(world: &mut WorldMap, index: u32) -> WorldMapEntry { + fn get_at_index_mut(world: &mut WorldMap, index: u32) -> WorldMapEntry<'_> { world.entry(index) } #[inline] - fn get_at_mut(&mut self, pos: &usvec2) -> WorldMapEntry { + fn get_at_mut(&mut self, pos: &usvec2) -> WorldMapEntry<'_> { let index = self.index_at(pos); Self::get_at_index_mut(&mut self.world, index) } diff --git a/game/vanilla/src/game_objects.rs b/game/vanilla/src/game_objects.rs index 353fbfce..08e09568 100644 --- a/game/vanilla/src/game_objects.rs +++ b/game/vanilla/src/game_objects.rs @@ -1,7 +1,7 @@ pub mod game_objects { - use game_base::mapdef_06::EntityTiles; use game_interface::types::{emoticons::EnumCount, weapons::WeaponType}; use hiarc::Hiarc; + use legacy_map::mapdef_06::EntityTiles; use map::map::groups::layers::tiles::TileBase; use math::math::vector::ivec2; diff --git a/game/vanilla/src/lib.rs b/game/vanilla/src/lib.rs index c894556d..949b46b6 100644 --- a/game/vanilla/src/lib.rs +++ b/game/vanilla/src/lib.rs @@ -51,7 +51,7 @@ mod test { use crate::{config::config::ConfigVanilla, state::state::GameState}; fn get_game() -> GameState { - let file = include_bytes!("../../../data/map/maps/ctf1.twmap"); + let file = include_bytes!("../../../data/map/maps/ctf1.twmap.tar"); let rt = create_runtime(); let io_rt = IoRuntime::new(rt); diff --git a/game/vanilla/src/simulation_pipe.rs b/game/vanilla/src/simulation_pipe.rs index 6e5785d5..61928622 100644 --- a/game/vanilla/src/simulation_pipe.rs +++ b/game/vanilla/src/simulation_pipe.rs @@ -485,8 +485,11 @@ pub mod simulation_pipe { impl SimulationPipeCharacters<'_> { pub fn get_characters_except_owner( &mut self, - ) -> CharactersViewMut bool + '_, impl Fn(&Character) -> bool + '_> - { + ) -> CharactersViewMut< + '_, + impl Fn(&CharacterId) -> bool + '_, + impl Fn(&Character) -> bool + '_, + > { CharactersViewMut::new( self.characters, |id| *id != self.owner_character, @@ -495,13 +498,17 @@ pub mod simulation_pipe { } pub fn get_characters( &mut self, - ) -> CharactersViewMut bool, impl Fn(&Character) -> bool> { + ) -> CharactersViewMut<'_, impl Fn(&CharacterId) -> bool, impl Fn(&Character) -> bool> + { CharactersViewMut::new(self.characters, |_| true, |c| !c.phased.is_phased()) } pub fn get_owner_character_view( &mut self, - ) -> CharactersViewMut bool + '_, impl Fn(&Character) -> bool + '_> - { + ) -> CharactersViewMut< + '_, + impl Fn(&CharacterId) -> bool + '_, + impl Fn(&Character) -> bool + '_, + > { CharactersViewMut::new( self.characters, |id| *id == self.owner_character, @@ -542,15 +549,18 @@ pub mod simulation_pipe { impl SimulationPipeOwnerlessCharacters<'_> { pub fn characters( &self, - ) -> CharactersView bool + '_, impl Fn(&Character) -> bool + '_> + ) -> CharactersView<'_, impl Fn(&CharacterId) -> bool + '_, impl Fn(&Character) -> bool + '_> { CharactersView::new(self.characters, |_| true, |v| !v.phased.is_phased()) } pub fn characters_mut( &mut self, - ) -> CharactersViewMut bool + '_, impl Fn(&Character) -> bool + '_> - { + ) -> CharactersViewMut< + '_, + impl Fn(&CharacterId) -> bool + '_, + impl Fn(&Character) -> bool + '_, + > { CharactersViewMut::new(self.characters, |_| true, |v| !v.phased.is_phased()) } } diff --git a/game/vanilla/src/state.rs b/game/vanilla/src/state.rs index 3d98b0af..56b1ddb6 100644 --- a/game/vanilla/src/state.rs +++ b/game/vanilla/src/state.rs @@ -56,6 +56,7 @@ pub mod state { use game_interface::types::weapons::WeaponType; use game_interface::vote_commands::{VoteCommand, VoteCommandResult}; use hiarc::hi_closure; + use map::file::MapFileReader; use map::map::config::ConfigVariables; use map::map::Map; use math::math::lerp; @@ -64,7 +65,6 @@ pub mod state { use pool::mt_datatypes::{PoolCow as MtPoolCow, PoolFxLinkedHashMap as MtPoolFxLinkedHashMap}; use pool::pool::Pool; - use game_base::mapdef_06::EntityTiles; use game_interface::interface::{ GameStateCreate, GameStateCreateOptions, GameStateInterface, GameStateServerOptions, GameStateStaticInfo, MAX_MAP_NAME_LEN, MAX_PHYSICS_GAME_TYPE_NAME_LEN, @@ -85,6 +85,7 @@ pub mod state { ScoreboardScoreType, ScoreboardStageInfo, }; use game_interface::types::snapshot::{SnapshotClientInfo, SnapshotLocalPlayers}; + use legacy_map::mapdef_06::EntityTiles; use pool::rc::PoolRc; use rustc_hash::FxHashMap; @@ -471,7 +472,7 @@ pub mod state { if let Err(err) = save::setup(db.clone()).await { log::warn!( target: "sql", - "failed to setup databases: {}", err + "failed to setup databases: {err}" ); return Err(err); } @@ -479,16 +480,18 @@ pub mod state { let acc_info = AccountInfo::new(db.clone(), options.account_db).await; if let Err(err) = &acc_info { log::warn!( - target: "sql", - "failed to prepare account info sql: {}", err); + target: "sql", + "failed to prepare account info sql: {err}" + ); } let account_created = match AccountCreated::new(db, options.account_db).await { Ok(account_created) => Some(account_created), Err(err) => { log::warn!( - target: "sql", - "failed to prepare account_created sql: {}", err); + target: "sql", + "failed to prepare account_created sql: {err}" + ); None } }; @@ -502,14 +505,15 @@ pub mod state { } }); - let (physics_group, map_config) = Map::read_physics_group_and_config(&map)?; + let (physics_group, map_config) = + Map::read_physics_group_and_config(&MapFileReader::new(map)?)?; let w = physics_group.attr.width.get() as u32; let h = physics_group.attr.height.get() as u32; - let tiles = physics_group.get_game_layer_tiles(); + let tiles = physics_group.get_game_layer_tiles().clone(); - let mut collision = Collision::new(&physics_group, true)?; + let mut collision = Collision::new(physics_group, true)?; // Always handle config variables before commands. Self::handle_map_config_variables(&mut config, map_config.config_variables); @@ -541,7 +545,7 @@ pub mod state { } } - let game_objects = GameObjectDefinitions::new(tiles, w, h); + let game_objects = GameObjectDefinitions::new(&tiles, w, h); let mut spawns: Vec = Default::default(); let mut spawns_red: Vec = Default::default(); @@ -776,7 +780,7 @@ pub mod state { }; // TODO: remove this log (move it somewhere) - log::info!(target: "world", "added a character into side {:?}", side); + log::info!(target: "world", "added a character into side {side:?}"); let pos = stage.world.get_spawn_pos(side); diff --git a/lib/api-wasm-macros/src/lib.rs b/lib/api-wasm-macros/src/lib.rs index d3ba9a60..c998617e 100644 --- a/lib/api-wasm-macros/src/lib.rs +++ b/lib/api-wasm-macros/src/lib.rs @@ -29,7 +29,7 @@ pub fn wasm_func_auto_call(attr: TokenStream, tokens: TokenStream) -> TokenStrea if let Type::Reference(_) = typed_arg.ty.as_ref() { arg_expr += ""; } else { - arg_expr = format!("&{}", arg_expr); + arg_expr = format!("&{arg_expr}"); } let func_call = syn::parse::( TokenStream::from_str( diff --git a/lib/api-wasm-macros/src/mod_prepare.rs b/lib/api-wasm-macros/src/mod_prepare.rs index 022d2c80..a538c598 100644 --- a/lib/api-wasm-macros/src/mod_prepare.rs +++ b/lib/api-wasm-macros/src/mod_prepare.rs @@ -16,7 +16,7 @@ pub fn wasm_mod_prepare( // first find the trait GameStateInterface // this makes it ez to know all functions by name let mut func_names: Vec = Default::default(); - mod_impl + let _ = mod_impl .content .as_ref() .unwrap() @@ -57,7 +57,7 @@ pub fn wasm_mod_prepare( }); // now find the struct name - mod_impl + let _ = mod_impl .content .as_mut() .unwrap() @@ -81,7 +81,7 @@ pub fn wasm_mod_prepare( }); // find the constructor impl and rewrite it - mod_impl + let _ = mod_impl .content .as_mut() .unwrap() @@ -106,7 +106,7 @@ pub fn wasm_mod_prepare( impl_impl.attrs.clear(); // find the new func - impl_impl.items.iter_mut().try_for_each(|func| { + let _ = impl_impl.items.iter_mut().try_for_each(|func| { if let ImplItem::Fn(func) = func { if func.sig.ident == "new" { let mut res_token = func diff --git a/lib/base-fs/src/filesys.rs b/lib/base-fs/src/filesys.rs index ddb47e13..f6698178 100644 --- a/lib/base-fs/src/filesys.rs +++ b/lib/base-fs/src/filesys.rs @@ -272,9 +272,9 @@ impl ScopedDirFileSystemInterface for mem_fs::FileSystem {} #[derive(Debug, Hiarc)] pub struct ScopedDirFileSystem { #[hiarc_skip_unsafe] - fs: Box, - host_path: PathBuf, - mount_path: PathBuf, + pub fs: Box, + pub host_path: PathBuf, + pub mount_path: PathBuf, } impl ScopedDirFileSystem { @@ -311,12 +311,11 @@ pub struct FileSystem { impl FileSystem { #[cfg(not(feature = "bundled_data_dir"))] - fn add_data_dir(scoped_file_systems: &mut Vec) -> anyhow::Result { - scoped_file_systems.push(ScopedDirFileSystem::new("data/")?); - Ok(scoped_file_systems.len() - 1) + fn get_data_dir_fs() -> anyhow::Result { + ScopedDirFileSystem::new("data/") } #[cfg(feature = "bundled_data_dir")] - fn add_data_dir(scoped_file_systems: &mut Vec) -> anyhow::Result { + fn get_data_dir_fs() -> anyhow::Result { use virtual_fs::AsyncWriteExt; const DATA_DIR: include_dir::Dir = include_dir::include_dir!("$CARGO_MANIFEST_DIR/../../data"); @@ -356,20 +355,20 @@ impl FileSystem { add_dirs(fs.as_ref(), &DATA_DIR)?; - scoped_file_systems.push(ScopedDirFileSystem { + Ok(ScopedDirFileSystem { fs, host_path: "data/".into(), mount_path: "/".into(), - }); - Ok(scoped_file_systems.len() - 1) + }) } - pub fn new( + pub fn new_with_data_dir( rt: &tokio::runtime::Runtime, qualifier: &str, organization: &str, application: &str, secure_appl: &str, + data_dir: ScopedDirFileSystem, ) -> anyhow::Result { let config_dir: PathBuf = if let Some(proj_dirs) = ProjectDirs::from(qualifier, organization, application) { @@ -406,7 +405,10 @@ impl FileSystem { log::info!(target: "fs", "Found config dir in {config_dir:?}"); scoped_file_systems.push(ScopedDirFileSystem::new(config_dir)?); let config_dir_index = scoped_file_systems.len() - 1; - let data_dir_index = Self::add_data_dir(&mut scoped_file_systems)?; + + scoped_file_systems.push(data_dir); + let data_dir_index = scoped_file_systems.len() - 1; + if let Ok(exec_path) = std::env::current_dir() { scoped_file_systems.push(ScopedDirFileSystem::new(exec_path)?); } @@ -428,6 +430,27 @@ impl FileSystem { }) } + pub fn new( + rt: &tokio::runtime::Runtime, + qualifier: &str, + organization: &str, + application: &str, + secure_appl: &str, + ) -> anyhow::Result { + let g = rt.enter(); + let data_dir = Self::get_data_dir_fs()?; + drop(g); + + Self::new_with_data_dir( + rt, + qualifier, + organization, + application, + secure_appl, + data_dir, + ) + } + fn get_scoped_fs(&self, fs_path: FileSystemPath) -> &ScopedDirFileSystem { let index: usize; match fs_path { diff --git a/lib/base-http/src/http_server.rs b/lib/base-http/src/http_server.rs index bd7b6fbe..aa1de243 100644 --- a/lib/base-http/src/http_server.rs +++ b/lib/base-http/src/http_server.rs @@ -53,7 +53,7 @@ impl HttpDownloadServer { } }; app = app.route( - &format!("/{}", path), + &format!("/{path}"), axum::routing::get(|| async move { served_file }), ); } @@ -73,13 +73,13 @@ impl HttpDownloadServer { let tcp_socket = TcpSocket::new_v4()?; tcp_socket.set_reuseaddr(true)?; - tcp_socket.bind(format!("0.0.0.0:{}", ipv4_port).parse()?)?; + tcp_socket.bind(format!("0.0.0.0:{ipv4_port}").parse()?)?; let (join_v4, port_v4) = start_http_server(tcp_socket, served_files.clone(), served_dirs_disk.clone())?; let tcp_socket = TcpSocket::new_v6()?; tcp_socket.set_reuseaddr(true)?; - tcp_socket.bind(format!("[::0]:{}", ipv6_port).parse()?)?; + tcp_socket.bind(format!("[::0]:{ipv6_port}").parse()?)?; let (join_v6, port_v6) = start_http_server(tcp_socket, served_files.clone(), served_dirs_disk.clone())?; Ok(Self { diff --git a/lib/base/src/duration_ext.rs b/lib/base/src/duration_ext.rs index 42b37b87..a3223b46 100644 --- a/lib/base/src/duration_ext.rs +++ b/lib/base/src/duration_ext.rs @@ -15,12 +15,12 @@ impl DurationToRaceStr for Duration { format!( "{}{}{:0>2}:{:0>2}.{:0>2}", if days > 0 { - format!("{}d ", days) + format!("{days}d ") } else { String::default() }, if hours > 0 || days > 0 { - format!("{:0>2}:", hours) + format!("{hours:0>2}:") } else { String::default() }, diff --git a/lib/cache/src/lib.rs b/lib/cache/src/lib.rs index 63ff98d3..109a047e 100644 --- a/lib/cache/src/lib.rs +++ b/lib/cache/src/lib.rs @@ -62,7 +62,7 @@ impl Cache<{ VERSION }> { fn cache_file_path(cache: &CacheImpl, hash: &Hash) -> (PathBuf, PathBuf) { let dir_name = Path::new("cache/") .join(Path::new(&cache.cache_name)) - .join(Path::new(&format!("v{}", VERSION))); + .join(Path::new(&format!("v{VERSION}"))); let hash_path = dir_name.join(Path::new(&format!("f_{}.cached", fmt_hash(hash)))); (dir_name, hash_path) } @@ -180,7 +180,7 @@ impl Cache<{ VERSION }> { fn cache_named_file_path(cache: &CacheImpl, name_path: &Path) -> PathBuf { let dir_name = Path::new("cache/named") .join(Path::new(&cache.cache_name)) - .join(Path::new(&format!("v{}", VERSION))); + .join(Path::new(&format!("v{VERSION}"))); dir_name.join(name_path) } diff --git a/lib/command-parser/src/escape.rs b/lib/command-parser/src/escape.rs index 7f90b4bc..2550b60b 100644 --- a/lib/command-parser/src/escape.rs +++ b/lib/command-parser/src/escape.rs @@ -3,7 +3,7 @@ use std::borrow::Cow; use thiserror::Error; /// Escape `"`, `\` or ` ` characters -pub fn escape(s: &str) -> Cow { +pub fn escape(s: &str) -> Cow<'_, str> { let mut quote = false; let mut needs_per_char_quote = false; @@ -23,7 +23,7 @@ pub fn escape(s: &str) -> Cow { return Cow::from(s); } if !needs_per_char_quote { - return format!("\"{}\"", s).into(); + return format!("\"{s}\"").into(); } let mut output = String::with_capacity(s.len()); @@ -72,7 +72,7 @@ pub fn unescape(s: &str) -> Result { match chars.next() { None => { return Err(UnescapeError::InvalidEscape { - escape: format!("{}", c), + escape: format!("{c}"), index, string: String::from(s), }); @@ -84,7 +84,7 @@ pub fn unescape(s: &str) -> Result { ' ' => ' ', _ => { return Err(UnescapeError::InvalidEscape { - escape: format!("{}{}", c, c2), + escape: format!("{c}{c2}"), index, string: String::from(s), }); diff --git a/lib/command-parser/src/parser.rs b/lib/command-parser/src/parser.rs index fd6eb9d4..5ef38341 100644 --- a/lib/command-parser/src/parser.rs +++ b/lib/command-parser/src/parser.rs @@ -172,7 +172,7 @@ impl CommandType { }; cmd } - pub fn as_ref(&self) -> CommandTypeRef { + pub fn as_ref(&self) -> CommandTypeRef<'_> { match self { CommandType::Full(cmd) => CommandTypeRef::Full(cmd), CommandType::Partial(cmd) => CommandTypeRef::Partial(cmd), diff --git a/lib/config-fs/src/lib.rs b/lib/config-fs/src/lib.rs index 41bc6e57..72bfced9 100644 --- a/lib/config-fs/src/lib.rs +++ b/lib/config-fs/src/lib.rs @@ -1,7 +1,7 @@ -use base_io::io::{Io, IoFileSys}; +use base_io::io::IoFileSys; use config::config::ConfigEngine; -pub fn save(config: &ConfigEngine, io: &Io) { +pub fn save(config: &ConfigEngine, io: &IoFileSys) { let save_str = config.to_json_string(); if let Ok(save_str) = save_str { diff --git a/lib/config-macro/src/lib.rs b/lib/config-macro/src/lib.rs index d2ba27f6..44255b88 100644 --- a/lib/config-macro/src/lib.rs +++ b/lib/config-macro/src/lib.rs @@ -146,9 +146,7 @@ pub fn config(tokens: TokenStream) -> TokenStream { .replace("\\'", "'") .replace("\\n", "\n"); doc_comment = match &doc_comment { - Some(doc_comment) => { - Some(format!("{}\n{}", doc_comment, inner)) - } + Some(doc_comment) => Some(format!("{doc_comment}\n{inner}")), None => Some(inner), }; } diff --git a/lib/config/src/parsing.rs b/lib/config/src/parsing.rs index 8bf48134..dd89ff8a 100644 --- a/lib/config/src/parsing.rs +++ b/lib/config/src/parsing.rs @@ -43,7 +43,7 @@ fn parse_conf_value_usage(val: &ConfigValue) -> String { format!("int [{min}..{max}]") } ConfigValue::Float { min, max } => { - format!("float [{:.4},{:.4}]", min, max) + format!("float [{min:.4},{max:.4}]") } ConfigValue::String { min_length, diff --git a/lib/config/src/traits.rs b/lib/config/src/traits.rs index eb1284a3..988cc9e8 100644 --- a/lib/config/src/traits.rs +++ b/lib/config/src/traits.rs @@ -156,8 +156,7 @@ impl ConfigInterface for String { } { return Err(ConfigFromStrErr::PathErr( ConfigFromStrPathErr::ValidationError(format!( - "The min/max length of the string was reached ({}/{})", - min, max + "The min/max length of the string was reached ({min}/{max})" )), )); } @@ -180,8 +179,7 @@ fn validate_numerical( if min > val || max < val { Err(ConfigFromStrErr::PathErr( ConfigFromStrPathErr::ValidationError(format!( - "Numerical value out of allowed range: {} not in [{}, {}]", - val, min, max + "Numerical value out of allowed range: {val} not in [{min}, {max}]" )), )) } else { @@ -474,7 +472,7 @@ impl ConfigInterface for bool { .map(|v| v == 1) .map_err(|err: ParseIntError| { ConfigFromStrErr::PathErr(ConfigFromStrPathErr::ParsingErr( - format!("{}. {}", err, err_bool), + format!("{err}. {err_bool}"), )) }) })?; diff --git a/lib/graphics-backend-traits/src/plugin.rs b/lib/graphics-backend-traits/src/plugin.rs index 8c748682..b1e67839 100644 --- a/lib/graphics-backend-traits/src/plugin.rs +++ b/lib/graphics-backend-traits/src/plugin.rs @@ -38,7 +38,7 @@ pub enum SamplerAddressMode { /// clamp uv ClampToEdge, /// clamp uv, mirror repeat r - Texture2DArray, + Texture2dArray, } /// the resource descriptors are pre-defined sets of descriptors that diff --git a/lib/graphics-backend/Cargo.toml b/lib/graphics-backend/Cargo.toml index a97838ad..d836079f 100644 --- a/lib/graphics-backend/Cargo.toml +++ b/lib/graphics-backend/Cargo.toml @@ -52,6 +52,7 @@ either = "1.15.0" replace_with = "0.1.7" crossbeam = "0.8.4" futures = "0.3.31" +strum = { version = "0.27.1", features = ["derive"] } [target.'cfg(target_os = "macos")'.dependencies] ash-molten = { version = "0.20.0" } diff --git a/lib/graphics-backend/src/backend_thread.rs b/lib/graphics-backend/src/backend_thread.rs index 15890e82..483588ce 100644 --- a/lib/graphics-backend/src/backend_thread.rs +++ b/lib/graphics-backend/src/backend_thread.rs @@ -46,7 +46,7 @@ use crate::{ #[derive(Debug)] enum GraphicsBackendLoadingType { - Vulkan(VulkanBackendLoading), + Vulkan(Box), Null(NullBackend), } @@ -395,7 +395,7 @@ impl BackendThread { &options, custom_pipes, )?; - GraphicsBackendLoadingType::Vulkan(backend) + GraphicsBackendLoadingType::Vulkan(Box::new(backend)) } }; @@ -438,7 +438,7 @@ impl BackendThread { return Err(anyhow!("main thread init data was not of type vulkan")); }; GraphicsBackendType::Vulkan(VulkanBackend::new( - loading, + *loading, data, &runtime_threadpool, main_thread_init, diff --git a/lib/graphics-backend/src/backends/vulkan/barriers.rs b/lib/graphics-backend/src/backends/vulkan/barriers.rs index b821856a..f4d03577 100644 --- a/lib/graphics-backend/src/backends/vulkan/barriers.rs +++ b/lib/graphics-backend/src/backends/vulkan/barriers.rs @@ -183,10 +183,7 @@ pub fn image_barrier( needs_dependency = true; } else { - panic!( - "unsupported layout transition! old: {:?} -> new: {:?}", - old_layout, new_layout - ); + panic!("unsupported layout transition! old: {old_layout:?} -> new: {new_layout:?}"); } unsafe { diff --git a/lib/graphics-backend/src/backends/vulkan/frame_collection.rs b/lib/graphics-backend/src/backends/vulkan/frame_collection.rs index 57d71402..82708821 100644 --- a/lib/graphics-backend/src/backends/vulkan/frame_collection.rs +++ b/lib/graphics-backend/src/backends/vulkan/frame_collection.rs @@ -208,8 +208,7 @@ impl<'a> FrameCollector<'a> { .load(std::sync::atomic::Ordering::SeqCst); assert!( img_layout == ImageLayout::Undefined || img_layout == ImageLayout::ColorAttachment, - "{:?}", - img_layout + "{img_layout:?}" ); image_barrier( current_frame_resources, diff --git a/lib/graphics-backend/src/backends/vulkan/memory_block.rs b/lib/graphics-backend/src/backends/vulkan/memory_block.rs index bf1a35df..66b59e3d 100644 --- a/lib/graphics-backend/src/backends/vulkan/memory_block.rs +++ b/lib/graphics-backend/src/backends/vulkan/memory_block.rs @@ -15,7 +15,10 @@ fn verbose_allocated_memory(size: vk::DeviceSize, mem_usage: MemoryBlockType) { MemoryBlockType::Stream => "stream", MemoryBlockType::Staging => "staging buffer", }; - log::info!(target: "vulkan", "allocated chunk of memory with size: {} ({})", size, usage_str); + log::info!( + target: "vulkan", + "allocated chunk of memory with size: {size} ({usage_str})" + ); } fn verbose_deallocated_memory(size: vk::DeviceSize, mem_usage: MemoryBlockType) { @@ -25,7 +28,10 @@ fn verbose_deallocated_memory(size: vk::DeviceSize, mem_usage: MemoryBlockType) MemoryBlockType::Stream => "stream", MemoryBlockType::Staging => "staging buffer", }; - log::info!(target: "vulkan", "deallocated chunk of memory with size: {} ({})", size, usage_str); + log::info!( + target: "vulkan", + "deallocated chunk of memory with size: {size} ({usage_str})" + ); } #[derive(Debug, Clone, Hiarc)] diff --git a/lib/graphics-backend/src/backends/vulkan/phy_device.rs b/lib/graphics-backend/src/backends/vulkan/phy_device.rs index 008847b7..0c3377a6 100644 --- a/lib/graphics-backend/src/backends/vulkan/phy_device.rs +++ b/lib/graphics-backend/src/backends/vulkan/phy_device.rs @@ -241,7 +241,7 @@ impl PhyDevice { Self::get_driver_verson(device_prop.driver_version, device_prop.vendor_id) ); - info!("{}, {}", version_name, vendor_name); + info!("{version_name}, {vendor_name}"); // get important device limits limits.non_coherent_mem_alignment = device_prop.limits.non_coherent_atom_size; diff --git a/lib/graphics-backend/src/backends/vulkan/pipeline_manager.rs b/lib/graphics-backend/src/backends/vulkan/pipeline_manager.rs index 470e8c6a..3ef2e33a 100644 --- a/lib/graphics-backend/src/backends/vulkan/pipeline_manager.rs +++ b/lib/graphics-backend/src/backends/vulkan/pipeline_manager.rs @@ -12,7 +12,7 @@ use super::{ pipeline_cache::PipelineCacheInner, render_group::{ColorWriteMaskType, StencilOpType}, vulkan_device::Device, - vulkan_types::{EVulkanBackendBlendModes, EVulkanBackendClipModes, ShaderModule}, + vulkan_types::{CanvasClipModes, ShaderModule, SupportedBlendModes}, }; #[derive(Debug, Hiarc, Clone)] @@ -26,8 +26,8 @@ pub struct PipelineCreationAttributes { pub set_layouts: Vec, #[hiarc_skip_unsafe] pub push_constants: Vec, - pub blend_mode: EVulkanBackendBlendModes, - pub dynamic_mode: EVulkanBackendClipModes, + pub blend_mode: SupportedBlendModes, + pub dynamic_mode: CanvasClipModes, pub is_line_prim: bool, pub stencil_mode: StencilOpType, @@ -131,7 +131,7 @@ impl<'a> PipelineManager<'a> { rasterizer: &mut vk::PipelineRasterizationStateCreateInfo, multisampling: &mut vk::PipelineMultisampleStateCreateInfo, color_blend_attachments: &'b mut [vk::PipelineColorBlendAttachmentState], - blend_mode: EVulkanBackendBlendModes, + blend_mode: SupportedBlendModes, color_mask: ColorWriteMaskType, ) -> anyhow::Result<( vk::PipelineViewportStateCreateInfo<'b>, @@ -183,34 +183,34 @@ impl<'a> PipelineManager<'a> { ColorWriteMaskType::None => vk::ColorComponentFlags::empty(), }; - color_blend_attachment.blend_enable = if blend_mode == EVulkanBackendBlendModes::None { + color_blend_attachment.blend_enable = if blend_mode == SupportedBlendModes::None { vk::FALSE } else { vk::TRUE }; let src_blend_factor_color = match blend_mode { - EVulkanBackendBlendModes::Additive => vk::BlendFactor::ONE, - EVulkanBackendBlendModes::Alpha => vk::BlendFactor::SRC_ALPHA, - EVulkanBackendBlendModes::None => vk::BlendFactor::SRC_COLOR, + SupportedBlendModes::Additive => vk::BlendFactor::ONE, + SupportedBlendModes::Alpha => vk::BlendFactor::SRC_ALPHA, + SupportedBlendModes::None => vk::BlendFactor::SRC_COLOR, }; let dst_blend_factor_color = match blend_mode { - EVulkanBackendBlendModes::Additive => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, - EVulkanBackendBlendModes::Alpha => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, - EVulkanBackendBlendModes::None => vk::BlendFactor::SRC_COLOR, + SupportedBlendModes::Additive => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, + SupportedBlendModes::Alpha => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, + SupportedBlendModes::None => vk::BlendFactor::SRC_COLOR, }; let src_blend_factor_alpha = match blend_mode { - EVulkanBackendBlendModes::Additive => vk::BlendFactor::ONE, - EVulkanBackendBlendModes::Alpha => vk::BlendFactor::SRC_ALPHA, - EVulkanBackendBlendModes::None => vk::BlendFactor::SRC_COLOR, + SupportedBlendModes::Additive => vk::BlendFactor::ONE, + SupportedBlendModes::Alpha => vk::BlendFactor::SRC_ALPHA, + SupportedBlendModes::None => vk::BlendFactor::SRC_COLOR, }; let dst_blend_factor_alpha = match blend_mode { - EVulkanBackendBlendModes::Additive => vk::BlendFactor::ZERO, - EVulkanBackendBlendModes::Alpha => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, - EVulkanBackendBlendModes::None => vk::BlendFactor::SRC_COLOR, + SupportedBlendModes::Additive => vk::BlendFactor::ZERO, + SupportedBlendModes::Alpha => vk::BlendFactor::ONE_MINUS_SRC_ALPHA, + SupportedBlendModes::None => vk::BlendFactor::SRC_COLOR, }; color_blend_attachment.src_color_blend_factor = src_blend_factor_color; @@ -483,7 +483,7 @@ impl<'a> PipelineManager<'a> { pipeline_info.subpass = 0; pipeline_info.base_pipeline_handle = vk::Pipeline::null(); - if attr.dynamic_mode == EVulkanBackendClipModes::DynamicScissorAndViewport { + if attr.dynamic_mode == CanvasClipModes::DynamicScissorAndViewport { pipeline_info = pipeline_info.dynamic_state(&create_stack.dynamic_state_create); } diff --git a/lib/graphics-backend/src/backends/vulkan/render_cmds.rs b/lib/graphics-backend/src/backends/vulkan/render_cmds.rs index 1e7c98bd..a90e29ad 100644 --- a/lib/graphics-backend/src/backends/vulkan/render_cmds.rs +++ b/lib/graphics-backend/src/backends/vulkan/render_cmds.rs @@ -22,7 +22,7 @@ use super::{ render_manager::RenderManager, render_pass::CanvasSetup, vulkan::VulkanCustomPipes, - vulkan_types::{EVulkanBackendAddressModes, RenderPassType}, + vulkan_types::{RenderPassType, SupportedAddressModes}, vulkan_uniform::{ SUniformPrimExGVertColor, SUniformSpriteMultiGVertColor, UniformGBlur, UniformGPos, UniformPrimExGPos, UniformPrimExGPosRotationless, UniformPrimExGVertColorAlign, @@ -32,9 +32,9 @@ use super::{ pub fn get_address_mode_index(state: &State) -> usize { if state.wrap_mode == WrapType::Repeat { - EVulkanBackendAddressModes::Repeat as usize + SupportedAddressModes::Repeat as usize } else { - EVulkanBackendAddressModes::ClampEdges as usize + SupportedAddressModes::ClampEdges as usize } } diff --git a/lib/graphics-backend/src/backends/vulkan/render_fill_manager.rs b/lib/graphics-backend/src/backends/vulkan/render_fill_manager.rs index fdf20623..d2ee0849 100644 --- a/lib/graphics-backend/src/backends/vulkan/render_fill_manager.rs +++ b/lib/graphics-backend/src/backends/vulkan/render_fill_manager.rs @@ -10,9 +10,7 @@ use super::{ render_cmds::get_address_mode_index, render_setup::RenderSetupNativeType, vulkan::VulkanBackend, - vulkan_types::{ - ESupportedSamplerTypes, EVulkanBackendClipModes, RenderPassSubType, RenderPassType, - }, + vulkan_types::{CanvasClipModes, RenderPassSubType, RenderPassType, SupportedSamplerTypes}, }; #[derive(Debug, Hiarc, Default)] @@ -88,9 +86,9 @@ impl<'a> RenderCommandExecuteManager<'a> { fn get_dynamic_mode_index_from_state(&self, state: &State) -> usize { if state.clip.is_some() || self.backend.has_dynamic_viewport { - EVulkanBackendClipModes::DynamicScissorAndViewport as usize + CanvasClipModes::DynamicScissorAndViewport as usize } else { - EVulkanBackendClipModes::None as usize + CanvasClipModes::None as usize } } } @@ -192,7 +190,7 @@ impl BackendRenderExecuteInterface for RenderCommandExecuteManager<'_> { .set(&mut self.backend.current_frame_resources), ); self.exec_buffer.sampler_descriptors[index as usize] = Some( - self.backend.props.device.samplers[ESupportedSamplerTypes::Texture2DArray as usize] + self.backend.props.device.samplers[SupportedSamplerTypes::Texture2dArray as usize] .1 .set(&mut self.backend.current_frame_resources), ); @@ -230,7 +228,7 @@ impl BackendRenderExecuteInterface for RenderCommandExecuteManager<'_> { fn exec_buffer_fill_dynamic_states(&mut self, state: &State) { let dynamic_state_index: usize = self.get_dynamic_mode_index_from_state(state); - if dynamic_state_index == EVulkanBackendClipModes::DynamicScissorAndViewport as usize { + if dynamic_state_index == CanvasClipModes::DynamicScissorAndViewport as usize { let mut viewport = vk::Viewport::default(); if self.backend.has_dynamic_viewport { viewport.x = self.backend.dynamic_viewport_offset.x as f32; diff --git a/lib/graphics-backend/src/backends/vulkan/render_group.rs b/lib/graphics-backend/src/backends/vulkan/render_group.rs index cd497372..17b1e9d9 100644 --- a/lib/graphics-backend/src/backends/vulkan/render_group.rs +++ b/lib/graphics-backend/src/backends/vulkan/render_group.rs @@ -6,6 +6,7 @@ use base::linked_hash_map_view::FxLinkedHashMap; use graphics_backend_traits::frame_fetcher_plugin::OffscreenCanvasId; use hiarc::Hiarc; use num_derive::FromPrimitive; +use strum::EnumCount; use crate::{backend::CustomPipelines, window::BackendSwapchain}; @@ -24,7 +25,9 @@ use super::{ }; #[repr(u32)] -#[derive(FromPrimitive, Hiarc, Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive( + FromPrimitive, Hiarc, Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, EnumCount, +)] pub enum StencilOpType { #[default] None, @@ -32,9 +35,10 @@ pub enum StencilOpType { OnlyWhenPassed, OnlyWhenNotPassed, } -pub const STENCIL_OP_TYPE_COUNT: usize = 4; -#[derive(FromPrimitive, Hiarc, Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[derive( + FromPrimitive, Hiarc, Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, EnumCount, +)] #[repr(u32)] pub enum ColorWriteMaskType { #[default] @@ -43,7 +47,6 @@ pub enum ColorWriteMaskType { AlphaOnly, None, } -pub const COLOR_MASK_TYPE_COUNT: usize = 4; #[derive(Debug, Hiarc)] enum CanvasModeInternal { diff --git a/lib/graphics-backend/src/backends/vulkan/render_manager.rs b/lib/graphics-backend/src/backends/vulkan/render_manager.rs index bbb69f4c..1f0bf59c 100644 --- a/lib/graphics-backend/src/backends/vulkan/render_manager.rs +++ b/lib/graphics-backend/src/backends/vulkan/render_manager.rs @@ -19,8 +19,8 @@ use super::{ render_group::{ColorWriteMaskType, StencilOpType}, render_pass::CanvasSetup, vulkan_types::{ - EVulkanBackendBlendModes, EVulkanBackendClipModes, PipelineContainer, - PipelineContainerItem, RenderPassType, + CanvasClipModes, PipelineContainer, PipelineContainerItem, RenderPassType, + SupportedBlendModes, }, }; @@ -64,17 +64,17 @@ impl<'a> RenderManager<'a> { fn get_blend_mode_index(state: &State) -> usize { match state.blend_mode { - BlendType::None => EVulkanBackendBlendModes::None as usize, - BlendType::Alpha => EVulkanBackendBlendModes::Alpha as usize, - BlendType::Additive => EVulkanBackendBlendModes::Additive as usize, + BlendType::None => SupportedBlendModes::None as usize, + BlendType::Alpha => SupportedBlendModes::Alpha as usize, + BlendType::Additive => SupportedBlendModes::Additive as usize, } } fn get_dynamic_mode_index_from_exec_buffer(exec_buffer: &RenderCommandExecuteBuffer) -> usize { if exec_buffer.has_dynamic_state { - EVulkanBackendClipModes::DynamicScissorAndViewport as usize + CanvasClipModes::DynamicScissorAndViewport as usize } else { - EVulkanBackendClipModes::None as usize + CanvasClipModes::None as usize } } @@ -151,7 +151,9 @@ impl<'a> RenderManager<'a> { &creation_data.pipeline_cache, ); let pipelines = pipeline_manager - .create_graphics_pipeline_ex(&[creation_props.attr.clone()]) + .create_graphics_pipeline_ex(std::slice::from_ref( + &creation_props.attr, + )) .unwrap(); pipeline_and_layout.store(Arc::new(Some(pipelines))); } @@ -279,7 +281,7 @@ impl<'a> RenderManager<'a> { let dynamic_state_index: usize = Self::get_dynamic_mode_index_from_exec_buffer(self.exec_buffer); - if dynamic_state_index == EVulkanBackendClipModes::DynamicScissorAndViewport as usize { + if dynamic_state_index == CanvasClipModes::DynamicScissorAndViewport as usize { unsafe { self.device.device.cmd_set_viewport( self.command_buffer.command_buffer, diff --git a/lib/graphics-backend/src/backends/vulkan/sub_render_pass.rs b/lib/graphics-backend/src/backends/vulkan/sub_render_pass.rs index bf041e13..30c1be94 100644 --- a/lib/graphics-backend/src/backends/vulkan/sub_render_pass.rs +++ b/lib/graphics-backend/src/backends/vulkan/sub_render_pass.rs @@ -9,8 +9,9 @@ use graphics_backend_traits::plugin::{ }; use hiarc::Hiarc; use num_traits::FromPrimitive; +use strum::EnumCount; -use crate::backend::CustomPipelines; +use crate::{backend::CustomPipelines, backends::vulkan::vulkan_types::SupportedBlendModes}; use super::{ compiler::compiler::ShaderCompiler, @@ -18,15 +19,12 @@ use super::{ pipeline_cache::PipelineCacheInner, pipeline_manager::{PipelineCreationAttributes, PipelineManager}, pipelines::Pipelines, - render_group::{ - ColorWriteMaskType, StencilOpType, COLOR_MASK_TYPE_COUNT, STENCIL_OP_TYPE_COUNT, - }, + render_group::{ColorWriteMaskType, StencilOpType}, vulkan_device::DescriptorLayouts, vulkan_types::{ - ESupportedSamplerTypes, EVulkanBackendBlendModes, EVulkanBackendClipModes, - PipelineContainer, PipelineContainerCreateMode, PipelineContainerItem, + CanvasClipModes, PipelineContainer, PipelineContainerCreateMode, PipelineContainerItem, PipelineCreationAttributesEx, PipelineCreationOneByOne, PipelineCreationProps, - BLEND_MODE_COUNT, SAMPLER_TYPES_COUNT, + SupportedSamplerTypes, }, vulkan_uniform::{ SUniformPrimExGVertColor, SUniformSpriteMultiGVertColor, UniformGBlur, UniformGPos, @@ -84,7 +82,7 @@ impl SubRenderPass { layouts: &DescriptorLayouts, is_textured: bool, as_line_geometry: bool, - address_mode: ESupportedSamplerTypes, + address_mode: SupportedSamplerTypes, ) -> ( Vec, Vec, @@ -138,7 +136,7 @@ impl SubRenderPass { fn standard_3d_pipeline_layout( layouts: &DescriptorLayouts, is_textured: bool, - address_mode: ESupportedSamplerTypes, + address_mode: SupportedSamplerTypes, ) -> ( Vec, Vec, @@ -209,7 +207,7 @@ impl SubRenderPass { N: Fn(bool) -> Option<(String, String)>, L: Fn( bool, - ESupportedSamplerTypes, + SupportedSamplerTypes, ) -> ( Vec, Vec, @@ -218,27 +216,27 @@ impl SubRenderPass { bool, ), { - let cap_size = SAMPLER_TYPES_COUNT + let cap_size = SupportedSamplerTypes::COUNT * 2 - * COLOR_MASK_TYPE_COUNT - * STENCIL_OP_TYPE_COUNT - * BLEND_MODE_COUNT - * EVulkanBackendClipModes::Count as usize; + * ColorWriteMaskType::COUNT + * StencilOpType::COUNT + * SupportedBlendModes::COUNT + * CanvasClipModes::COUNT; let mut attrs: Vec = Vec::with_capacity(cap_size); let mut attrs_ex: Vec = Vec::with_capacity(cap_size); - for l in 0..SAMPLER_TYPES_COUNT { + for l in 0..SupportedSamplerTypes::COUNT { for t in 0..2 { let is_textured = t == 0; let (attribute_descriptors, set_layouts, push_constants, stride, is_line_geometry) = create_layout( is_textured, - ESupportedSamplerTypes::from_u32(l as u32).unwrap(), + SupportedSamplerTypes::from_u32(l as u32).unwrap(), ); if let Some((vert_name, frag_name)) = shader_names(is_textured) { - for c in 0..COLOR_MASK_TYPE_COUNT { - for s in 0..STENCIL_OP_TYPE_COUNT { - for i in 0..BLEND_MODE_COUNT { - for j in 0..EVulkanBackendClipModes::Count as usize { + for c in 0..ColorWriteMaskType::COUNT { + for s in 0..StencilOpType::COUNT { + for i in 0..SupportedBlendModes::COUNT { + for j in 0..CanvasClipModes::COUNT { let attr = PipelineCreationAttributes { vert_name: vert_name.clone(), frag_name: frag_name.clone(), @@ -246,10 +244,9 @@ impl SubRenderPass { input_attributes: attribute_descriptors.clone(), set_layouts: set_layouts.clone(), push_constants: push_constants.clone(), - blend_mode: EVulkanBackendBlendModes::from_u32(i as u32) - .unwrap(), - dynamic_mode: EVulkanBackendClipModes::from_u32(j as u32) + blend_mode: SupportedBlendModes::from_u32(i as u32) .unwrap(), + dynamic_mode: CanvasClipModes::from_u32(j as u32).unwrap(), is_line_prim: is_line_geometry, stencil_mode: StencilOpType::from_u32(s as u32).unwrap(), color_mask: ColorWriteMaskType::from_u32(c as u32).unwrap(), @@ -320,7 +317,7 @@ impl SubRenderPass { layouts: &DescriptorLayouts, is_textured: bool, rotationless: bool, - address_mode: ESupportedSamplerTypes, + address_mode: SupportedSamplerTypes, ) -> ( Vec, Vec, @@ -391,7 +388,7 @@ impl SubRenderPass { fn sprite_multi_pipeline_layout( layouts: &DescriptorLayouts, - address_mode: ESupportedSamplerTypes, + address_mode: SupportedSamplerTypes, ) -> ( Vec, Vec, @@ -480,7 +477,7 @@ impl SubRenderPass { fn backend_layout_to_vk_layout( layouts: &DescriptorLayouts, mut layout: BackendPipelineLayout, - address_mode: ESupportedSamplerTypes, + address_mode: SupportedSamplerTypes, ) -> ( Vec, Vec, diff --git a/lib/graphics-backend/src/backends/vulkan/swapchain.rs b/lib/graphics-backend/src/backends/vulkan/swapchain.rs index 8e1ca8a1..60ec9377 100644 --- a/lib/graphics-backend/src/backends/vulkan/swapchain.rs +++ b/lib/graphics-backend/src/backends/vulkan/swapchain.rs @@ -220,7 +220,7 @@ impl Swapchain { // Try rgb formats first on macos #[cfg(target_os = "macos")] if let Some(format) = surf_formats.remove(&vk::Format::R16G16B16A16_SFLOAT) { - log::debug!("Using surface format: {:?}", format); + log::debug!("Using surface format: {format:?}"); return Ok(format); } @@ -228,7 +228,7 @@ impl Swapchain { .remove(&vk::Format::R8G8B8A8_UNORM) .or_else(|| surf_formats.remove(&vk::Format::B8G8R8A8_UNORM)) { - log::debug!("Using surface format: {:?}", format); + log::debug!("Using surface format: {format:?}"); return Ok(format); } diff --git a/lib/graphics-backend/src/backends/vulkan/vulkan_device.rs b/lib/graphics-backend/src/backends/vulkan/vulkan_device.rs index 213818bf..c363863f 100644 --- a/lib/graphics-backend/src/backends/vulkan/vulkan_device.rs +++ b/lib/graphics-backend/src/backends/vulkan/vulkan_device.rs @@ -9,6 +9,7 @@ use ash::vk; use config::config::AtomicGfxDebugModes; use hiarc::Hiarc; use libc::c_void; +use strum::EnumCount; use super::{ barriers::{image_barrier, memory_barrier}, @@ -34,8 +35,8 @@ use super::{ vulkan_limits::Limits, vulkan_mem::{BufferAllocationError, ImageAllocationError, Memory}, vulkan_types::{ - BufferObject, BufferObjectMem, DescriptorPoolType, DeviceDescriptorPools, - ESupportedSamplerTypes, MemoryBlockType, ShaderStorage, TextureObject, SAMPLER_TYPES_COUNT, + BufferObject, BufferObjectMem, DescriptorPoolType, DeviceDescriptorPools, MemoryBlockType, + ShaderStorage, SupportedSamplerTypes, TextureObject, }, Options, }; @@ -67,7 +68,7 @@ pub struct DescriptorLayouts { pub vertex_shader_storage_descriptor_set_layout: Arc, - pub samplers_layouts: Arc<[Arc; SAMPLER_TYPES_COUNT]>, + pub samplers_layouts: Arc<[Arc; SupportedSamplerTypes::COUNT]>, } #[derive(Debug, Hiarc)] @@ -82,7 +83,7 @@ pub struct Device { #[hiarc_skip_unsafe] pub non_flushed_memory_ranges: Vec>, - pub samplers: Arc<[(Arc, Arc); SAMPLER_TYPES_COUNT]>, + pub samplers: Arc<[(Arc, Arc); SupportedSamplerTypes::COUNT]>, pub textures: HashMap, pub buffer_objects: HashMap, @@ -205,15 +206,15 @@ impl Device { options.gl.global_texture_lod_bias, )?; - let samplers: [Arc; SAMPLER_TYPES_COUNT] = + let samplers: [Arc; SupportedSamplerTypes::COUNT] = [repeat.0, clamp_to_edge.0, texture_2d_array.0]; - let sampler_layouts: [Arc; SAMPLER_TYPES_COUNT] = + let sampler_layouts: [Arc; SupportedSamplerTypes::COUNT] = [repeat.1, clamp_to_edge.1, texture_2d_array.1]; let sampler_descr_pool = DeviceDescriptorPools::new( &device, - SAMPLER_TYPES_COUNT as vk::DeviceSize, + SupportedSamplerTypes::COUNT as vk::DeviceSize, DescriptorPoolType::Sampler, )?; let (repeat_set, clamp_to_edge_set, texture_2d_set) = @@ -945,10 +946,10 @@ impl Device { pub fn create_new_sampler_descriptor_set( device: &Arc, - layouts: &[Arc; SAMPLER_TYPES_COUNT], + layouts: &[Arc; SupportedSamplerTypes::COUNT], sampler_descr_pool: &Arc>, - samplers: &[Arc; SAMPLER_TYPES_COUNT], - address_mode: ESupportedSamplerTypes, + samplers: &[Arc; SupportedSamplerTypes::COUNT], + address_mode: SupportedSamplerTypes, ) -> anyhow::Result, ImageAllocationError> { let res = VulkanAllocator::get_descriptor_pool_for_alloc( device, @@ -968,9 +969,9 @@ impl Device { pub fn create_new_sampler_descriptor_sets( device: &Arc, - layouts: &[Arc; SAMPLER_TYPES_COUNT], + layouts: &[Arc; SupportedSamplerTypes::COUNT], sampler_descr_pool: &Arc>, - samplers: &[Arc; SAMPLER_TYPES_COUNT], + samplers: &[Arc; SupportedSamplerTypes::COUNT], ) -> anyhow::Result { Ok(( Self::create_new_sampler_descriptor_set( @@ -978,21 +979,21 @@ impl Device { layouts, sampler_descr_pool, samplers, - ESupportedSamplerTypes::Repeat, + SupportedSamplerTypes::Repeat, )?, Self::create_new_sampler_descriptor_set( device, layouts, sampler_descr_pool, samplers, - ESupportedSamplerTypes::ClampToEdge, + SupportedSamplerTypes::ClampToEdge, )?, Self::create_new_sampler_descriptor_set( device, layouts, sampler_descr_pool, samplers, - ESupportedSamplerTypes::Texture2DArray, + SupportedSamplerTypes::Texture2dArray, )?, )) } diff --git a/lib/graphics-backend/src/backends/vulkan/vulkan_types.rs b/lib/graphics-backend/src/backends/vulkan/vulkan_types.rs index 43a8e7c5..d46939c7 100644 --- a/lib/graphics-backend/src/backends/vulkan/vulkan_types.rs +++ b/lib/graphics-backend/src/backends/vulkan/vulkan_types.rs @@ -8,6 +8,9 @@ use graphics_backend_traits::plugin::SamplerAddressMode; use graphics_types::commands::StreamDataMax; use hiarc::Hiarc; use num_derive::FromPrimitive; +use strum::EnumCount; + +use crate::backends::vulkan::render_group::{ColorWriteMaskType, StencilOpType}; use super::{ buffer::Buffer, @@ -23,7 +26,6 @@ use super::{ pipeline_manager::PipelineCreationAttributes, pipelines::Pipelines, render_fill_manager::RenderCommandExecuteBuffer, - render_group::{COLOR_MASK_TYPE_COUNT, STENCIL_OP_TYPE_COUNT}, render_pass::CanvasSetup, vulkan_allocator::VulkanAllocator, }; @@ -194,31 +196,26 @@ impl Drop for ShaderModule { } } -#[derive(FromPrimitive, Copy, Clone)] +#[derive(FromPrimitive, Copy, Clone, EnumCount)] #[repr(u32)] -pub enum EVulkanBackendAddressModes { +pub enum SupportedAddressModes { Repeat = 0, ClampEdges = 1, - - Count = 2, } -#[derive(Debug, Hiarc, FromPrimitive, Copy, Clone, PartialEq)] +#[derive(Debug, Hiarc, FromPrimitive, Copy, Clone, PartialEq, EnumCount)] #[repr(u32)] -pub enum EVulkanBackendBlendModes { +pub enum SupportedBlendModes { Alpha = 0, None = 1, Additive = 2, } -pub const BLEND_MODE_COUNT: usize = 3; -#[derive(Debug, Hiarc, FromPrimitive, Copy, Clone, PartialEq)] +#[derive(Debug, Hiarc, FromPrimitive, Copy, Clone, PartialEq, EnumCount)] #[repr(u32)] -pub enum EVulkanBackendClipModes { +pub enum CanvasClipModes { None = 0, DynamicScissorAndViewport = 1, - - Count = 2, } const MAX_TEXTURE_MODES: usize = 2; @@ -293,15 +290,15 @@ pub enum PipelineContainerCreateMode { OneByOne(PipelineCreationOneByOne), } -pub type PipelinesSampler = [PipelineContainerItem; SAMPLER_TYPES_COUNT]; -pub type PipelinesColorMasks = [PipelinesSampler; COLOR_MASK_TYPE_COUNT]; +pub type PipelinesSampler = [PipelineContainerItem; SupportedSamplerTypes::COUNT]; +pub type PipelinesColorMasks = [PipelinesSampler; ColorWriteMaskType::COUNT]; #[derive(Debug, Hiarc)] pub struct PipelineContainer { // 3 blend modes - 2 viewport & scissor modes - 2 texture modes - 4 stencil modes - 3 color mask types - 3 sampler modes pub pipelines: Box< - [[[[PipelinesColorMasks; STENCIL_OP_TYPE_COUNT]; MAX_TEXTURE_MODES]; - EVulkanBackendClipModes::Count as usize]; BLEND_MODE_COUNT], + [[[[PipelinesColorMasks; StencilOpType::COUNT]; MAX_TEXTURE_MODES]; CanvasClipModes::COUNT]; + SupportedBlendModes::COUNT], >, pub(crate) mode: PipelineContainerCreateMode, @@ -316,21 +313,20 @@ impl PipelineContainer { } } -#[derive(Debug, FromPrimitive, Copy, Clone, PartialEq)] +#[derive(Debug, FromPrimitive, Copy, Clone, PartialEq, EnumCount)] #[repr(u32)] -pub enum ESupportedSamplerTypes { +pub enum SupportedSamplerTypes { Repeat = 0, ClampToEdge, - Texture2DArray, + Texture2dArray, } -pub const SAMPLER_TYPES_COUNT: usize = 3; -impl From for SamplerAddressMode { - fn from(val: ESupportedSamplerTypes) -> Self { +impl From for SamplerAddressMode { + fn from(val: SupportedSamplerTypes) -> Self { match val { - ESupportedSamplerTypes::Repeat => SamplerAddressMode::Repeat, - ESupportedSamplerTypes::ClampToEdge => SamplerAddressMode::ClampToEdge, - ESupportedSamplerTypes::Texture2DArray => SamplerAddressMode::Texture2DArray, + SupportedSamplerTypes::Repeat => SamplerAddressMode::Repeat, + SupportedSamplerTypes::ClampToEdge => SamplerAddressMode::ClampToEdge, + SupportedSamplerTypes::Texture2dArray => SamplerAddressMode::Texture2dArray, } } } diff --git a/lib/graphics-backend/src/lib.rs b/lib/graphics-backend/src/lib.rs index 932f950a..35ffa5e1 100644 --- a/lib/graphics-backend/src/lib.rs +++ b/lib/graphics-backend/src/lib.rs @@ -19,6 +19,7 @@ pub mod backend_thread; mod backends; pub mod cache; pub mod checker; +pub mod utils; pub mod window; #[cfg(test)] diff --git a/lib/graphics-backend/src/utils.rs b/lib/graphics-backend/src/utils.rs new file mode 100644 index 00000000..c1261c3c --- /dev/null +++ b/lib/graphics-backend/src/utils.rs @@ -0,0 +1,249 @@ +use std::{ + ops::{Deref, DerefMut}, + path::PathBuf, +}; + +use config::config::{ConfigEngine, ConfigMonitor, ConfigWindow}; +use graphics::graphics::graphics::Graphics; +use native::{ + input::InputEventHandler, + native::{ + FromNativeImpl, NativeImpl, NativeWindowMonitorDetails, NativeWindowOptions, PhysicalSize, + WindowMode, + }, +}; + +use crate::{backend::GraphicsBackend, window::BackendWindow}; + +/// A helper function for clients to notify the graphics about the resize. +/// And update the config values properly. +pub fn client_graphics_resized_update_config( + graphics: &Graphics, + graphics_backend: &GraphicsBackend, + config: &mut ConfigEngine, + native: &mut dyn NativeImpl, + new_width: u32, + new_height: u32, +) { + let window_props = graphics_backend.resized( + &graphics.backend_handle.backend_cmds, + graphics.stream_handle.stream_data(), + native, + new_width, + new_height, + ); + graphics.resized(window_props); + // update config variables + let wnd = &mut config.wnd; + let window = native.borrow_window(); + if wnd.fullscreen { + wnd.fullscreen_width = new_width; + wnd.fullscreen_height = new_height; + } else { + let scale_factor = window.scale_factor(); + wnd.window_width = new_width as f64 / scale_factor; + wnd.window_height = new_height as f64 / scale_factor; + } + if let Some(monitor) = window.current_monitor() { + wnd.refresh_rate_mhz = monitor + .refresh_rate_millihertz() + .unwrap_or(wnd.refresh_rate_mhz); + } +} + +/// A helper function for clients to update the config values properly, +/// after the window props changed. +pub fn client_window_props_changed_update_config( + config: &mut ConfigEngine, + wnd: NativeWindowOptions, +) { + let config_wnd = &mut config.wnd; + config_wnd.fullscreen = wnd.mode.is_fullscreen(); + config_wnd.decorated = wnd.decorated; + config_wnd.maximized = wnd.maximized; + match wnd.mode { + WindowMode::Fullscreen { + resolution, + fallback_window, + } => { + if let Some(resolution) = resolution { + config_wnd.fullscreen_width = resolution.width; + config_wnd.fullscreen_height = resolution.height; + } + + config_wnd.window_width = fallback_window.width; + config_wnd.window_height = fallback_window.height; + } + WindowMode::Windowed(pixels) => { + config_wnd.window_width = pixels.width; + config_wnd.window_height = pixels.height; + } + } + config_wnd.refresh_rate_mhz = wnd.refresh_rate_milli_hertz; + config_wnd.monitor = wnd + .monitor + .map(|monitor| ConfigMonitor { + name: monitor.name, + width: monitor.size.width, + height: monitor.size.height, + }) + .unwrap_or_default(); +} + +pub fn client_graphics_window_created_ntfy( + graphics_backend: &GraphicsBackend, + native: &mut dyn NativeImpl, + config: &ConfigEngine, +) -> anyhow::Result<()> { + graphics_backend.window_created_ntfy( + BackendWindow::Winit { + window: native.borrow_window(), + }, + &config.dbg, + ) +} + +pub fn client_graphics_window_destroyed_ntfy( + graphics_backend: &GraphicsBackend, + _native: &mut dyn NativeImpl, +) -> anyhow::Result<()> { + graphics_backend.window_destroyed_ntfy() +} + +pub fn client_window_config_to_native_window_options(config: ConfigWindow) -> NativeWindowOptions { + let logical_pixels = native::native::Pixels { + width: config.window_width, + height: config.window_height, + }; + NativeWindowOptions { + mode: if config.fullscreen { + WindowMode::Fullscreen { + resolution: (config.fullscreen_width != 0 && config.fullscreen_height != 0) + .then_some(native::native::Pixels { + width: config.fullscreen_width, + height: config.fullscreen_height, + }), + fallback_window: logical_pixels, + } + } else { + WindowMode::Windowed(logical_pixels) + }, + decorated: config.decorated, + maximized: config.maximized, + refresh_rate_milli_hertz: config.refresh_rate_mhz, + monitor: (!config.monitor.name.is_empty() + && config.monitor.width != 0 + && config.monitor.height != 0) + .then_some(NativeWindowMonitorDetails { + name: config.monitor.name, + size: PhysicalSize { + width: config.monitor.width, + height: config.monitor.height, + }, + }), + } +} + +pub trait AppWithGraphics { + fn get_graphics_data(&mut self) -> (&Graphics, &GraphicsBackend, &mut ConfigEngine); + + // Copied from `FromNativeImpl` + fn run(&mut self, native: &mut dyn NativeImpl); + /// New width and height in pixels! + fn resized(&mut self, _native: &mut dyn NativeImpl, _new_width: u32, _new_height: u32) {} + /// The window options changed, usually the implementor does not need to do anything. + /// But if it wants to serialize the current options it can do so. + fn window_options_changed(&mut self, _wnd: NativeWindowOptions) {} + fn destroy(self); + + /// The app lost or gained focus. + fn focus_changed(&mut self, _focused: bool) {} + + /// File was dropped into the app. + fn file_dropped(&mut self, _file: PathBuf) {} + + /// None if hovered was ended. + fn file_hovered(&mut self, _file: Option) {} + + fn window_created_ntfy(&mut self, _native: &mut dyn NativeImpl) -> anyhow::Result<()> { + Ok(()) + } + fn window_destroyed_ntfy(&mut self, _native: &mut dyn NativeImpl) -> anyhow::Result<()> { + Ok(()) + } +} + +pub struct GraphicsApp(T); + +impl AsMut for GraphicsApp { + fn as_mut(&mut self) -> &mut dyn InputEventHandler { + &mut self.0 + } +} + +impl GraphicsApp { + pub fn new(inner: T) -> Self { + Self(inner) + } +} + +impl Deref for GraphicsApp { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for GraphicsApp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl FromNativeImpl for GraphicsApp { + fn run(&mut self, native: &mut dyn NativeImpl) { + self.0.run(native) + } + fn resized(&mut self, native: &mut dyn NativeImpl, new_width: u32, new_height: u32) { + let (graphics, graphics_backend, config) = self.0.get_graphics_data(); + client_graphics_resized_update_config( + graphics, + graphics_backend, + config, + native, + new_width, + new_height, + ); + self.0.resized(native, new_width, new_height) + } + fn window_options_changed(&mut self, wnd: NativeWindowOptions) { + let (_, _, config) = self.0.get_graphics_data(); + client_window_props_changed_update_config(config, wnd.clone()); + self.0.window_options_changed(wnd) + } + fn destroy(self) { + self.0.destroy() + } + + fn focus_changed(&mut self, focused: bool) { + self.0.focus_changed(focused) + } + fn file_dropped(&mut self, file: PathBuf) { + self.0.file_dropped(file) + } + fn file_hovered(&mut self, file: Option) { + self.0.file_hovered(file); + } + + fn window_created_ntfy(&mut self, native: &mut dyn NativeImpl) -> anyhow::Result<()> { + let (_, graphics_backend, config) = self.0.get_graphics_data(); + client_graphics_window_created_ntfy(graphics_backend, native, config)?; + self.0.window_created_ntfy(native) + } + fn window_destroyed_ntfy(&mut self, native: &mut dyn NativeImpl) -> anyhow::Result<()> { + let (_, graphics_backend, _) = self.0.get_graphics_data(); + client_graphics_window_destroyed_ntfy(graphics_backend, native)?; + self.0.window_destroyed_ntfy(native) + } +} diff --git a/lib/graphics-backend/src/window.rs b/lib/graphics-backend/src/window.rs index c6b18d30..5502f84f 100644 --- a/lib/graphics-backend/src/window.rs +++ b/lib/graphics-backend/src/window.rs @@ -128,7 +128,7 @@ impl BackendWindow<'_> { &self, entry: &ash::Entry, instance: &ash::Instance, - ) -> Result { + ) -> Result, vk::Result> { match self { BackendWindow::Winit { window } => { if let Ok((dh, wh)) = window diff --git a/lib/graphics-types/Cargo.toml b/lib/graphics-types/Cargo.toml index 4da36cc4..21dd54d6 100644 --- a/lib/graphics-types/Cargo.toml +++ b/lib/graphics-types/Cargo.toml @@ -10,6 +10,7 @@ hiarc = { path = "../hiarc", features = ["enable_hashlink"] } num-traits = "0.2.19" bitflags = { version = "2.9.0", features = ["serde"] } serde = "1.0.219" +strum = { version = "0.27.1", features = ["derive"] } [package.metadata.cargo-machete] ignored = ["num-traits"] diff --git a/lib/graphics-types/src/rendering.rs b/lib/graphics-types/src/rendering.rs index 34022c17..9cd3a7a5 100644 --- a/lib/graphics-types/src/rendering.rs +++ b/lib/graphics-types/src/rendering.rs @@ -1,8 +1,9 @@ use hiarc::Hiarc; use math::math::vector::{ubvec4, vec2, vec4, vec4_base}; use serde::{Deserialize, Serialize}; +use strum::EnumCount; -#[derive(Debug, Hiarc, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Hiarc, Default, Copy, Clone, PartialEq, Eq, EnumCount, Serialize, Deserialize)] pub enum BlendType { None = 0, #[default] @@ -10,7 +11,7 @@ pub enum BlendType { Additive, } -#[derive(Debug, Hiarc, Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[derive(Debug, Hiarc, Default, Copy, Clone, PartialEq, Eq, EnumCount, Serialize, Deserialize)] pub enum WrapType { #[default] Repeat = 0, diff --git a/lib/graphics/src/handles/stream.rs b/lib/graphics/src/handles/stream.rs index 0c9c3a6b..24cd2e6b 100644 --- a/lib/graphics/src/handles/stream.rs +++ b/lib/graphics/src/handles/stream.rs @@ -10,12 +10,13 @@ pub mod stream { rendering::{GlVertex, RenderMode, State}, types::DrawModes, }; - use hiarc::Hiarc; + use hiarc::{hi_closure, Hiarc}; use hiarc::{HiFnMut, HiFnMutBase, HiFnOnce}; use crate::handles::{ backend::backend::GraphicsBackendHandle, canvas::canvas::OffscreenCanvas, + stream_types::{StreamedLine, StreamedQuad, StreamedTriangle}, texture::texture::{TextureContainer, TextureType}, }; @@ -169,6 +170,10 @@ pub mod stream { self.render_mode = render_mode; } + pub fn set_texture_ty(&mut self, texture: TextureType) { + self.texture = texture; + } + pub fn set_texture(&mut self, tex_index: &TextureContainer) { self.texture = tex_index.into(); } @@ -178,8 +183,7 @@ pub mod stream { } pub fn set_offscreen_attachment_texture(&mut self, offscreen_canvas: &OffscreenCanvas) { - self.texture = - TextureType::ColorAttachmentOfOffscreen(offscreen_canvas.get_index_unsafe()); + self.texture = TextureType::ColorAttachmentOfOffscreen(offscreen_canvas.clone()); } } @@ -227,7 +231,7 @@ pub mod stream { } /// use [`graphics::handles::stream_types::StreamedLine`] to build a line - pub fn render_lines<'a, F>(&'a self, draw: F, state: State) + pub fn stream_lines<'a, F>(&'a self, draw: F, state: State) where F: HiFnOnce, ()>, { @@ -244,7 +248,7 @@ pub mod stream { } /// use [`graphics::handles::stream_types::StreamedQuad`] to build a quad - pub fn render_quads<'a, F>(&'a self, draw: F, state: State) + pub fn stream_quads<'a, F>(&'a self, draw: F, state: State) where F: HiFnOnce, ()>, { @@ -261,7 +265,7 @@ pub mod stream { } /// use [`graphics::handles::stream_types::StreamedTriangle`] to build a triangle - pub fn render_triangles<'a, F>(&'a self, draw: F, state: State) + pub fn stream_triangles<'a, F>(&'a self, draw: F, state: State) where F: HiFnOnce, ()>, { @@ -277,6 +281,65 @@ pub mod stream { draw.call_once(stream_handle); } + /// Render [`graphics::handles::stream_types::StreamedLine`]s. + pub fn render_lines(&self, lines: &[StreamedLine], state: State) { + self.stream_lines( + hi_closure!([ + lines: &[StreamedLine], + ], + |mut stream_handle: LinesStreamHandle<'_>| -> () { + for line in lines.iter().copied() { + stream_handle.add_vertices(line.into()); + } + } + ), + state, + ); + } + + /// Render [`graphics::handles::stream_types::StreamedQuad`]s. + pub fn render_quads(&self, quads: &[StreamedQuad], state: State, texture: TextureType) { + let texture = &texture; + self.stream_quads( + hi_closure!([ + quads: &[StreamedQuad], + texture: &TextureType, + ], + |mut stream_handle: QuadStreamHandle<'_>| -> () { + stream_handle.set_texture_ty(texture.clone()); + for quad in quads.iter().copied() { + stream_handle.add_vertices(quad.into()); + } + } + ), + state, + ); + } + + /// Render [`graphics::handles::stream_types::StreamedTriangle`]s. + pub fn render_triangles( + &self, + triangles: &[StreamedTriangle], + state: State, + texture: TextureType, + ) { + let texture = &texture; + self.stream_triangles( + hi_closure!([ + triangles: &[StreamedTriangle], + texture: &TextureType, + ], + |mut stream_handle: TriangleStreamHandle<'_>| -> () { + stream_handle.set_texture_ty(texture.clone()); + for triangle in triangles.iter().copied() { + stream_handle.add_vertices(triangle.into()); + } + } + ), + state, + ); + } + fn flush_vertices( &self, state: &State, diff --git a/lib/graphics/src/handles/stream_types.rs b/lib/graphics/src/handles/stream_types.rs index d074dd36..94f02117 100644 --- a/lib/graphics/src/handles/stream_types.rs +++ b/lib/graphics/src/handles/stream_types.rs @@ -48,7 +48,7 @@ impl From for [GlVertex; 2] { } } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Hiarc, Copy, Clone)] pub struct StreamedTriangle { vertices: [GlVertex; 3], } diff --git a/lib/graphics/src/handles/texture.rs b/lib/graphics/src/handles/texture.rs index 4f9aeb48..fc4f50eb 100644 --- a/lib/graphics/src/handles/texture.rs +++ b/lib/graphics/src/handles/texture.rs @@ -10,7 +10,9 @@ pub mod texture { }; use hiarc::{hiarc_safer_rc_refcell, Hiarc}; - use crate::handles::backend::backend::GraphicsBackendHandle; + use crate::handles::{ + backend::backend::GraphicsBackendHandle, canvas::canvas::OffscreenCanvas, + }; #[hiarc_safer_rc_refcell] #[derive(Debug, Hiarc)] @@ -163,7 +165,7 @@ pub mod texture { None, Texture(TextureContainer), ColorAttachmentOfPreviousPass, - ColorAttachmentOfOffscreen(u128), + ColorAttachmentOfOffscreen(OffscreenCanvas), } impl From for StateTexture { @@ -175,7 +177,7 @@ pub mod texture { StateTexture::ColorAttachmentOfPreviousPass } TextureType::ColorAttachmentOfOffscreen(offscreen_id) => { - StateTexture::ColorAttachmentOfOffscreen(offscreen_id) + StateTexture::ColorAttachmentOfOffscreen(offscreen_id.get_index_unsafe()) } } } diff --git a/lib/graphics/src/streaming.rs b/lib/graphics/src/streaming.rs index a03776f4..dbc71d8d 100644 --- a/lib/graphics/src/streaming.rs +++ b/lib/graphics/src/streaming.rs @@ -5,17 +5,23 @@ use graphics_types::{ use hiarc::Hiarc; use math::math::vector::{vec2, vec4}; -pub fn rotate(center: &vec2, rotation: f32, points: &mut [GlVertex]) { +#[inline(always)] +pub fn rotate_pos(center: &vec2, rotation: f32, point: vec2) -> vec2 { let c = rotation.cos(); let s = rotation.sin(); + let x = point.x - center.x; + let y = point.y - center.y; + vec2 { + x: x * c - y * s + center.x, + y: x * s + y * c + center.y, + } +} + +pub fn rotate(center: &vec2, rotation: f32, points: &mut [GlVertex]) { for point in points.iter_mut() { - let x = point.get_pos().x - center.x; - let y = point.get_pos().y - center.y; - point.set_pos(&vec2 { - x: x * c - y * s + center.x, - y: x * s + y * c + center.y, - }); + let pos = point.get_pos(); + point.set_pos(&rotate_pos(center, rotation, vec2::new(pos.x, pos.y))); } } diff --git a/lib/graphics/src/utils.rs b/lib/graphics/src/utils.rs index 57b4fb99..b3e2c543 100644 --- a/lib/graphics/src/utils.rs +++ b/lib/graphics/src/utils.rs @@ -9,6 +9,7 @@ use crate::handles::{ canvas::canvas::GraphicsCanvasHandle, stream::stream::{GraphicsStreamHandle, QuadStreamHandle}, stream_types::StreamedQuad, + texture::texture::TextureType, }; pub const DEFAULT_BLUR_RADIUS: f32 = 13.0; @@ -33,7 +34,7 @@ fn render_blur_impl( if is_first { state.set_color_mask(ColorMaskMode::WriteColorOnly); } - stream_handle.render_quads( + stream_handle.stream_quads( hi_closure!([is_hori: bool, blur_radius: f32, blur_mix_length: f32, is_last_iter: bool, blur_color: &vec4], |mut stream_handle: QuadStreamHandle<'_>| -> () { stream_handle.set_color_attachment_texture(); stream_handle.set_render_mode(RenderMode::Blur { @@ -75,22 +76,17 @@ fn render_blur_impl( state.set_color_mask(ColorMaskMode::WriteColorOnly); } stream_handle.render_quads( - hi_closure!([], |mut stream_handle: QuadStreamHandle<'_>| -> () { - stream_handle.set_color_attachment_texture(); - stream_handle.add_vertices( - StreamedQuad::default() - .from_pos_and_size(vec2::new(0.0, 0.0), vec2::new(1.0, 1.0)) - .tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ) - .colorf(vec4::new(1.0, 1.0, 1.0, 1.0)) - .into(), - ); - }), + &[StreamedQuad::default() + .from_pos_and_size(vec2::new(0.0, 0.0), vec2::new(1.0, 1.0)) + .tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + ) + .colorf(vec4::new(1.0, 1.0, 1.0, 1.0))], state, + TextureType::ColorAttachmentOfPreviousPass, ); backend_handle.next_switch_pass(); @@ -164,22 +160,17 @@ pub fn render_swapped_frame( state.blend(BlendType::None); stream_handle.render_quads( - hi_closure!([], |mut stream_handle: QuadStreamHandle<'_>| -> () { - stream_handle.set_color_attachment_texture(); - stream_handle.add_vertices( - StreamedQuad::default() - .from_pos_and_size(vec2::new(0.0, 0.0), vec2::new(1.0, 1.0)) - .tex_free_form( - vec2::new(0.0, 0.0), - vec2::new(1.0, 0.0), - vec2::new(1.0, 1.0), - vec2::new(0.0, 1.0), - ) - .colorf(vec4::new(1.0, 1.0, 1.0, 1.0)) - .into(), - ); - }), + &[StreamedQuad::default() + .from_pos_and_size(vec2::new(0.0, 0.0), vec2::new(1.0, 1.0)) + .tex_free_form( + vec2::new(0.0, 0.0), + vec2::new(1.0, 0.0), + vec2::new(1.0, 1.0), + vec2::new(0.0, 1.0), + ) + .colorf(vec4::new(1.0, 1.0, 1.0, 1.0))], state, + TextureType::ColorAttachmentOfPreviousPass, ); if let Some(dynamic_viewport) = dynamic_viewport { diff --git a/lib/hiarc-macro/src/hi_closure.rs b/lib/hiarc-macro/src/hi_closure.rs index 230f87de..d3a018ef 100644 --- a/lib/hiarc-macro/src/hi_closure.rs +++ b/lib/hiarc-macro/src/hi_closure.rs @@ -294,8 +294,8 @@ pub(crate) fn hi_closure_impl(item: TokenStream) -> TokenStream { Err(err) => { let err = err.to_string(); let err = format!( - "currently only typed arguments are allowed: {}, found: {}", - err, input + "currently only typed arguments are allowed: {err}, \ + found: {input}", ); ( quote!(), @@ -318,14 +318,13 @@ pub(crate) fn hi_closure_impl(item: TokenStream) -> TokenStream { ty.to_token_stream() } else { let err = output.to_token_stream().to_string(); - let err = format!("result type must be explicitly set: {}", err); + let err = format!("result type must be explicitly set: {err}"); quote!(compile_error!(#err)) } } Err(err) => { let err = format!( - "result type must be explicitly set: {} from {}", - err, + "result type must be explicitly set: {err} from {}", closure.output.clone() ); quote!(compile_error!(#err)) diff --git a/lib/hiarc/src/lib.rs b/lib/hiarc/src/lib.rs index d22439ad..4e297376 100644 --- a/lib/hiarc/src/lib.rs +++ b/lib/hiarc/src/lib.rs @@ -715,7 +715,7 @@ impl HiUnsafeRefCell { /// Even tho this function is not unsafe in the sense of memory safety, /// this function is only intended to be used by macros that know what they are doing. /// In case you call it, you risk panics. - pub unsafe fn hi_borrow_mut(&self) -> std::cell::RefMut { + pub unsafe fn hi_borrow_mut(&self) -> std::cell::RefMut<'_, T> { self.0.borrow_mut() } @@ -724,7 +724,7 @@ impl HiUnsafeRefCell { /// Even tho this function is not unsafe in the sense of memory safety, /// this function is only intended to be used by macros that know what they are doing. /// In case you call, it you risk panics. - pub unsafe fn hi_borrow(&self) -> std::cell::Ref { + pub unsafe fn hi_borrow(&self) -> std::cell::Ref<'_, T> { self.0.borrow() } @@ -756,7 +756,7 @@ impl HiUnsafeMutex { /// Even tho this function is not unsafe in the sense of memory safety, /// this function is only intended to be used by macros that know what they are doing. /// In case you call it, you risk panics. - pub unsafe fn hi_borrow_mut(&self) -> std::sync::MutexGuard { + pub unsafe fn hi_borrow_mut(&self) -> std::sync::MutexGuard<'_, T> { self.0.lock().unwrap() } @@ -765,7 +765,7 @@ impl HiUnsafeMutex { /// Even tho this function is not unsafe in the sense of memory safety, /// this function is only intended to be used by macros that know what they are doing. /// In case you call, it you risk panics. - pub unsafe fn hi_borrow(&self) -> std::sync::MutexGuard { + pub unsafe fn hi_borrow(&self) -> std::sync::MutexGuard<'_, T> { self.0.lock().unwrap() } diff --git a/lib/image-utils/src/png.rs b/lib/image-utils/src/png.rs index 84a2ea53..ba141456 100644 --- a/lib/image-utils/src/png.rs +++ b/lib/image-utils/src/png.rs @@ -99,7 +99,7 @@ where } img_data } - _ => return Err(io::Error::new(io::ErrorKind::Other, "uncovered color type")), + _ => return Err(io::Error::other("uncovered color type")), }; Ok(PngResult { diff --git a/lib/native/src/input/mod.rs b/lib/native/src/input/mod.rs index efa3a005..f3c4d87a 100644 --- a/lib/native/src/input/mod.rs +++ b/lib/native/src/input/mod.rs @@ -6,7 +6,7 @@ use winit::{ /// all functions (except [`InputEventHandler::raw_window_event`]) get a raw input, /// so repeated key events (holding a key down -> many keys are sent like in text editors) are __ignored__ -pub trait InputEventHandler { +pub trait InputEventHandler: 'static { fn key_down(&mut self, window: &winit::window::Window, device: &DeviceId, key: PhysicalKey); fn key_up(&mut self, window: &winit::window::Window, device: &DeviceId, key: PhysicalKey); fn mouse_down( diff --git a/lib/native/src/native/app.rs b/lib/native/src/native/app.rs index 218045f3..2e60c9b2 100644 --- a/lib/native/src/native/app.rs +++ b/lib/native/src/native/app.rs @@ -1,8 +1,21 @@ +// Operating systems that don't require app states get +// a small wrapper around nothing, +// that implements clone to match the native app +// behavior of other platforms. #[cfg(not(target_os = "android"))] -pub type NativeApp = (); +#[derive(Debug, Default, Clone)] +pub struct NativeApp; +#[cfg(not(target_os = "android"))] +pub(crate) type ApplicationHandlerType = (); +#[cfg(not(target_os = "android"))] +pub(crate) type NativeEventLoop = winit::event_loop::EventLoop<()>; #[cfg(target_os = "android")] pub use winit::platform::android::activity::AndroidApp as NativeApp; +#[cfg(target_os = "android")] +pub(crate) type ApplicationHandlerType = NativeApp; +#[cfg(target_os = "android")] +pub(crate) type NativeEventLoop = winit::event_loop::EventLoop; pub const MIN_WINDOW_WIDTH: u32 = 50; pub const MIN_WINDOW_HEIGHT: u32 = 50; diff --git a/lib/native/src/native/mod.rs b/lib/native/src/native/mod.rs index 1d76f999..644fbf5b 100644 --- a/lib/native/src/native/mod.rs +++ b/lib/native/src/native/mod.rs @@ -59,7 +59,7 @@ pub trait NativeImpl { fn start_arguments(&self) -> &Vec; } -pub trait FromNativeImpl: InputEventHandler { +pub trait FromNativeImpl: AsMut { fn run(&mut self, native: &mut dyn NativeImpl); /// New width and height in pixels! fn resized(&mut self, native: &mut dyn NativeImpl, new_width: u32, new_height: u32); @@ -92,13 +92,13 @@ where fn new(loading: L, native: &mut dyn NativeImpl) -> anyhow::Result; } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NativeWindowMonitorDetails { pub name: String, pub size: PhysicalSize, } -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub struct Pixels { pub width: T, pub height: T, @@ -107,7 +107,7 @@ pub struct Pixels { pub type PhysicalPixels = Pixels; pub type LogicalPixels = Pixels; -#[derive(Debug)] +#[derive(Debug, Clone, Copy)] pub enum WindowMode { Fullscreen { resolution: Option, @@ -127,7 +127,7 @@ impl WindowMode { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NativeWindowOptions { pub mode: WindowMode, /// if fullscreen is `false` & maximized is `true` & decorated is `false` diff --git a/lib/native/src/native/winit_wrapper.rs b/lib/native/src/native/winit_wrapper.rs index da8cc5f5..e9ab626d 100644 --- a/lib/native/src/native/winit_wrapper.rs +++ b/lib/native/src/native/winit_wrapper.rs @@ -13,7 +13,9 @@ use winit::{ window::{CursorGrabMode, Fullscreen, Window, WindowAttributes}, }; -use crate::native::app::{MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH}; +use crate::native::app::{ + ApplicationHandlerType, NativeEventLoop, MIN_WINDOW_HEIGHT, MIN_WINDOW_WIDTH, +}; use super::{ app::NativeApp, FromNativeImpl, FromNativeLoadingImpl, NativeCreateOptions, NativeImpl, @@ -387,7 +389,7 @@ impl WinitWrapper { native_options: &NativeCreateOptions, app: NativeApp, loading: &mut L, - ) -> anyhow::Result> + ) -> anyhow::Result where L: Sized, F: FromNativeLoadingImpl, @@ -457,7 +459,7 @@ impl WinitWrapper { None, } - impl ApplicationHandler for NativeUser<'_, F, L> + impl ApplicationHandler for NativeUser<'_, F, L> where F: FromNativeImpl + FromNativeLoadingImpl + 'static, { @@ -632,7 +634,10 @@ impl WinitWrapper { window, } = self { - if !native_user.raw_window_event(&window.window, &event) { + if !native_user + .as_mut() + .raw_window_event(&window.window, &event) + { match event { winit::event::WindowEvent::Resized(new_size) => { native_user.resized(window, new_size.width, new_size.height); @@ -695,17 +700,16 @@ impl WinitWrapper { } => { if !event.repeat { match event.state { - winit::event::ElementState::Pressed => native_user - .key_down( + winit::event::ElementState::Pressed => { + native_user.as_mut().key_down( &window.window, &device_id, event.physical_key, - ), - winit::event::ElementState::Released => native_user.key_up( - &window.window, - &device_id, - event.physical_key, - ), + ) + } + winit::event::ElementState::Released => native_user + .as_mut() + .key_up(&window.window, &device_id, event.physical_key), } } } @@ -716,7 +720,7 @@ impl WinitWrapper { position, } => { window.mouse.cursor_main_pos = (position.x, position.y); - native_user.mouse_move( + native_user.as_mut().mouse_move( &window.window, &device_id, position.x, @@ -732,7 +736,7 @@ impl WinitWrapper { delta, phase: _, .. - } => native_user.scroll( + } => native_user.as_mut().scroll( &window.window, &device_id, window.mouse.cursor_main_pos.0, @@ -744,20 +748,24 @@ impl WinitWrapper { state, button, } => match state { - winit::event::ElementState::Pressed => native_user.mouse_down( - &window.window, - &device_id, - window.mouse.cursor_main_pos.0, - window.mouse.cursor_main_pos.1, - &button, - ), - winit::event::ElementState::Released => native_user.mouse_up( - &window.window, - &device_id, - window.mouse.cursor_main_pos.0, - window.mouse.cursor_main_pos.1, - &button, - ), + winit::event::ElementState::Pressed => { + native_user.as_mut().mouse_down( + &window.window, + &device_id, + window.mouse.cursor_main_pos.0, + window.mouse.cursor_main_pos.1, + &button, + ) + } + winit::event::ElementState::Released => { + native_user.as_mut().mouse_up( + &window.window, + &device_id, + window.mouse.cursor_main_pos.0, + window.mouse.cursor_main_pos.1, + &button, + ) + } }, winit::event::WindowEvent::TouchpadPressure { device_id: _, @@ -770,14 +778,14 @@ impl WinitWrapper { value: _, } => {} winit::event::WindowEvent::Touch(touch) => { - native_user.mouse_down( + native_user.as_mut().mouse_down( &window.window, &touch.device_id, touch.location.x, touch.location.y, &winit::event::MouseButton::Left, ); - native_user.mouse_up( + native_user.as_mut().mouse_up( &window.window, &touch.device_id, touch.location.x, @@ -879,7 +887,7 @@ impl WinitWrapper { } winit::event::DeviceEvent::MouseMotion { delta: (delta_x, delta_y), - } => native_user.mouse_move( + } => native_user.as_mut().mouse_move( &window.window, &device_id, window.mouse.cursor_main_pos.0, diff --git a/lib/network/src/network/connection_ban.rs b/lib/network/src/network/connection_ban.rs index 10303853..ce282996 100644 --- a/lib/network/src/network/connection_ban.rs +++ b/lib/network/src/network/connection_ban.rs @@ -65,13 +65,11 @@ pub struct ConnectionBans { #[async_trait] impl NetworkPluginConnection for ConnectionBans { - #[must_use] async fn on_incoming(&self, _remote_addr: &SocketAddr) -> bool { // This plugin prefers proper error messages instead // of ignoring connections true } - #[must_use] async fn on_connect( &self, id: &NetworkConnectionId, diff --git a/lib/network/src/network/connection_limit.rs b/lib/network/src/network/connection_limit.rs index 9df1942c..202f2c1d 100644 --- a/lib/network/src/network/connection_limit.rs +++ b/lib/network/src/network/connection_limit.rs @@ -26,13 +26,11 @@ impl MaxConnections { #[async_trait] impl NetworkPluginConnection for MaxConnections { - #[must_use] async fn on_incoming(&self, _remote_addr: &SocketAddr) -> bool { // This plugin prefers proper error messages instead // of ignoring connections true } - #[must_use] async fn on_connect( &self, _id: &NetworkConnectionId, diff --git a/lib/network/src/network/connection_per_ip.rs b/lib/network/src/network/connection_per_ip.rs index 3f17f6aa..e950233b 100644 --- a/lib/network/src/network/connection_per_ip.rs +++ b/lib/network/src/network/connection_per_ip.rs @@ -35,13 +35,11 @@ impl ConnectionLimitPerIp { #[async_trait] impl NetworkPluginConnection for ConnectionLimitPerIp { - #[must_use] async fn on_incoming(&self, _remote_addr: &SocketAddr) -> bool { // This plugin prefers proper error messages instead // of ignoring connections true } - #[must_use] async fn on_connect( &self, _id: &NetworkConnectionId, diff --git a/lib/network/src/network/connections.rs b/lib/network/src/network/connections.rs index 0e67a5d2..9c7cbda7 100644 --- a/lib/network/src/network/connections.rs +++ b/lib/network/src/network/connections.rs @@ -382,7 +382,7 @@ impl, ) -> tokio::task::JoinHandle<()> { let remote_addr = conn.remote_addr(); - log::debug!("handling connecting request for {:?}", remote_addr); + log::debug!("handling connecting request for {remote_addr:?}"); let connections_clone = connections.clone(); let mut game_event_generator_clone = game_event_generator.clone(); diff --git a/lib/network/src/network/tungstenite_network.rs b/lib/network/src/network/tungstenite_network.rs index 6b298fbf..8e3fdff9 100644 --- a/lib/network/src/network/tungstenite_network.rs +++ b/lib/network/src/network/tungstenite_network.rs @@ -237,7 +237,7 @@ impl NetworkConnectionInterface for TungsteniteNetworkConnectionWrapper { } enum ConnectingTypes { - Client(std::future::Ready>>), + Client(Box>>>), Server( Pin< Box< @@ -344,7 +344,7 @@ impl _server_name: &str, ) -> anyhow::Result { let (res, _) = tokio_tungstenite::connect_async_tls_with_config( - &format!("ws://{}", addr), + &format!("ws://{addr}"), None, false, Some(tokio_tungstenite::Connector::Plain), @@ -352,7 +352,7 @@ impl .block_on() .map_err(|err| NetworkEventConnectingFailed::Other(err.to_string()))?; Ok(TungsteniteNetworkConnectingWrapper { - connecting: ConnectingTypes::Client(std::future::ready(res)), + connecting: ConnectingTypes::Client(Box::new(std::future::ready(res))), addr, }) } diff --git a/lib/ui-base/src/ui_render.rs b/lib/ui-base/src/ui_render.rs index 092c0921..8b4bfb92 100644 --- a/lib/ui-base/src/ui_render.rs +++ b/lib/ui-base/src/ui_render.rs @@ -212,7 +212,7 @@ fn render_ui_prepared( state.blend(BlendType::Additive); state.wrap(WrapType::Clamp); - stream_handle.render_triangles(hi_closure!( + stream_handle.stream_triangles(hi_closure!( [ textures: &mut HashMap, mesh: &egui::Mesh diff --git a/lib/ui-wasm-manager/src/lib.rs b/lib/ui-wasm-manager/src/lib.rs index bfc153d9..572c7317 100644 --- a/lib/ui-wasm-manager/src/lib.rs +++ b/lib/ui-wasm-manager/src/lib.rs @@ -63,7 +63,7 @@ impl UiWasmPageEntry { } enum UiPageEntry { - Wasm(UiWasmPageEntry), + Wasm(Box), Native(Box>), } @@ -315,8 +315,10 @@ where .unwrap(); let mut entry = UiWasmPageEntry { wasm_runtime }; entry.call_new(&self.fonts).unwrap(); - self.ui_paths - .insert(path.to_string(), UiPageEntry::Wasm(entry)); + self.ui_paths.insert( + path.to_string(), + UiPageEntry::Wasm(Box::new(entry)), + ); self.run_ui_path( path, io, graphics, backend, sound, pipe, inp, blur, ) diff --git a/lib/wasm-runtime/src/lib.rs b/lib/wasm-runtime/src/lib.rs index 054d4783..493494cd 100644 --- a/lib/wasm-runtime/src/lib.rs +++ b/lib/wasm-runtime/src/lib.rs @@ -77,7 +77,7 @@ impl WasmManager { 0, ); - println!("{}", text); + println!("{text}"); } // We then create an import object so that the `Module`'s imports can be satisfied. diff --git a/src/assets-server/src/upload/verify.rs b/src/assets-server/src/upload/verify.rs index c4b62b69..b701cca3 100644 --- a/src/assets-server/src/upload/verify.rs +++ b/src/assets-server/src/upload/verify.rs @@ -1,7 +1,7 @@ use std::{collections::HashMap, fmt::Debug}; use assets_base::{ - loader::read_tar, + tar::read_tar_files, verify::{ogg_vorbis::verify_ogg_vorbis, txt::verify_txt}, AssetUploadResponse, }; @@ -95,7 +95,7 @@ pub(crate) fn verify_resource( }) else { return AssetUploadResponse::UnsupportedFileType; }; - match read_tar(file) { + match read_tar_files(file.into()) { Ok(files) => { let allowed_resources: Vec<_> = allowed_resources .iter() diff --git a/src/client/client.rs b/src/client/client.rs index d94d60d6..b1181d47 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -15,6 +15,7 @@ use base_fs::filesys::FileSystem; use base_http::http::HttpClient; use base_io::io::{Io, IoFileSys}; use binds::binds::{BindActionsHotkey, BindActionsLocalPlayer}; +use camera::Camera; use client_accounts::accounts::{Accounts, AccountsLoading}; use client_console::console::{ console::{ConsoleEvents, ConsoleRenderPipe}, @@ -23,7 +24,6 @@ use client_console::console::{ }; use client_containers::{ container::ContainerLoadOptions, - entities::{EntitiesContainer, ENTITIES_CONTAINER_PATH}, skins::{SkinContainer, SKIN_CONTAINER_PATH}, }; use client_demo::{DemoVideoEncodeProperties, DemoViewer, DemoViewerSettings, EncoderSettings}; @@ -33,7 +33,7 @@ use client_render_base::{ map::{ map::RenderMap, map_pipeline::MapPipeline, - render_pipe::{Camera, GameTimeInfo, RenderPipeline, RenderPipelineBase}, + render_pipe::{GameTimeInfo, RenderPipeline, RenderPipelineBase}, }, render::tee::RenderTee, }; @@ -71,7 +71,7 @@ use client_ui::{ utils::render_tee_for_ui, }; use command_parser::parser::ParserCache; -use config::config::{ConfigEngine, ConfigMonitor}; +use config::config::ConfigEngine; use demo::recorder::DemoRecorder; use editor::editor::{EditorInterface, EditorResult}; use egui::{CursorIcon, FontDefinitions}; @@ -81,6 +81,10 @@ use graphics_backend::{ backend::{ GraphicsBackend, GraphicsBackendBase, GraphicsBackendIoLoading, GraphicsBackendLoading, }, + utils::{ + client_window_config_to_native_window_options, client_window_props_changed_update_config, + AppWithGraphics, GraphicsApp, + }, window::BackendWindow, }; @@ -118,9 +122,8 @@ use math::math::{ use native::{ input::InputEventHandler, native::{ - app::NativeApp, FromNativeImpl, FromNativeLoadingImpl, KeyCode, Native, - NativeCreateOptions, NativeDisplayBackend, NativeImpl, NativeWindowMonitorDetails, - NativeWindowOptions, PhysicalKey, PhysicalSize, WindowEvent, WindowMode, + app::NativeApp, FromNativeLoadingImpl, KeyCode, Native, NativeCreateOptions, + NativeDisplayBackend, NativeImpl, PhysicalKey, WindowEvent, }, }; use network::network::types::{NetworkInOrderChannel, NetworkServerCertModeResult}; @@ -154,7 +157,7 @@ use game_base::{ assets_url::HTTP_RESOURCE_URL, connecting_log::{ConnectModes, ConnectingLog}, game_types::{intra_tick_time, intra_tick_time_to_ratio, is_next_tick, time_until_tick}, - local_server_info::{LocalServerInfo, LocalServerState}, + local_server_info::{LocalServerInfo, LocalServerState, LocalServerStateReady}, network::messages::{GameModification, MsgClAddLocalPlayer, MsgClChatMsg, MsgClLoadVotes}, player_input::PlayerInput, server_browser::ServerBrowserData, @@ -215,9 +218,9 @@ pub fn ddnet_main( &mut res, true, ); - log::debug!("{}", res); + log::debug!("{res}"); if !cur_cmds_succeeded { - log::error!("{}", res); + log::error!("{res}"); } let mut has_events = true; let mut count = 0; @@ -234,11 +237,11 @@ pub fn ddnet_main( &local_console_builder.entries, &local_console_builder.parser_cache, |err| { - log::error!("{}", err); + log::error!("{err}"); has_startup_errors = true; }, |msg| { - log::info!("{}", msg); + log::info!("{msg}"); }, ); @@ -285,11 +288,7 @@ pub fn ddnet_main( local_console_builder, has_startup_errors, }; - let logical_pixels = native::native::Pixels { - width: config_wnd.window_width, - height: config_wnd.window_height, - }; - Native::run_loop::( + Native::run_loop::, _>( client, app, NativeCreateOptions { @@ -298,34 +297,7 @@ pub fn ddnet_main( sys: &sys_time, dbg_input, start_arguments, - window: native::native::NativeWindowOptions { - mode: if config_wnd.fullscreen { - WindowMode::Fullscreen { - resolution: (config_wnd.fullscreen_width != 0 - && config_wnd.fullscreen_height != 0) - .then_some(native::native::Pixels { - width: config_wnd.fullscreen_width, - height: config_wnd.fullscreen_height, - }), - fallback_window: logical_pixels, - } - } else { - WindowMode::Windowed(logical_pixels) - }, - decorated: config_wnd.decorated, - maximized: config_wnd.maximized, - refresh_rate_milli_hertz: config_wnd.refresh_rate_mhz, - monitor: (!config_wnd.monitor.name.is_empty() - && config_wnd.monitor.width != 0 - && config_wnd.monitor.height != 0) - .then_some(NativeWindowMonitorDetails { - name: config_wnd.monitor.name, - size: PhysicalSize { - width: config_wnd.monitor.width, - height: config_wnd.monitor.height, - }, - }), - }, + window: client_window_config_to_native_window_options(config_wnd), }, )?; Ok(()) @@ -416,7 +388,6 @@ struct ClientNativeImpl { editor: EditorState, - entities_container: EntitiesContainer, skin_container: SkinContainer, render_tee: RenderTee, @@ -459,23 +430,26 @@ impl ClientNativeImpl { state: &mut LocalServerState, notifications: &mut ClientNotifications, ) -> anyhow::Result<()> { - match state { + let thread = match state { LocalServerState::None => { // ignore + None } - LocalServerState::Starting { thread, .. } | LocalServerState::Ready { thread, .. } => { - if thread.thread.is_finished() { - match thread.thread.try_join() { - Err(err) | Ok(Some(Err(err))) => { - notifications.add_err( - format!("Failed to start local server: {err}"), - Duration::from_secs(10), - ); - return Err(err); - } - Ok(Some(Ok(_))) | Ok(None) => { - // ignore - } + LocalServerState::Starting { thread, .. } => Some(thread), + LocalServerState::Ready(ready) => Some(&mut ready.thread), + }; + if let Some(thread) = thread { + if thread.thread.is_finished() { + match thread.thread.try_join() { + Err(err) | Ok(Some(Err(err))) => { + notifications.add_err( + format!("Failed to start local server: {err}"), + Duration::from_secs(10), + ); + return Err(err); + } + Ok(Some(Ok(_))) | Ok(None) => { + // ignore } } } @@ -493,7 +467,8 @@ impl ClientNativeImpl { ConnectLocalServerResult::ErrOrNotLocalServerAddr { addresses } } else if addresses.iter().any(|addr| addr.ip().is_loopback()) { let mut state = self.shared_info.state.lock().unwrap(); - if let LocalServerState::Ready { connect_info, .. } = &mut *state { + if let LocalServerState::Ready(ready) = &mut *state { + let LocalServerStateReady { connect_info, .. } = ready.as_mut(); let rcon_secret = Some(connect_info.rcon_secret); let server_cert = ServerCertMode::Hash(connect_info.server_cert_hash); let addr = match connect_info.sock_addr { @@ -542,38 +517,9 @@ impl ClientNativeImpl { fn on_window_change(&mut self, native: &mut dyn NativeImpl) { let config_wnd = &self.config.engine.wnd; - let logical_pixels = native::native::Pixels { - width: config_wnd.window_width, - height: config_wnd.window_height, - }; - if let Err(err) = native.set_window_config(native::native::NativeWindowOptions { - mode: if config_wnd.fullscreen { - WindowMode::Fullscreen { - resolution: (config_wnd.fullscreen_width != 0 - && config_wnd.fullscreen_height != 0) - .then_some(native::native::Pixels { - width: config_wnd.fullscreen_width, - height: config_wnd.fullscreen_height, - }), - fallback_window: logical_pixels, - } - } else { - WindowMode::Windowed(logical_pixels) - }, - decorated: config_wnd.decorated, - maximized: config_wnd.maximized, - refresh_rate_milli_hertz: config_wnd.refresh_rate_mhz, - monitor: (!config_wnd.monitor.name.is_empty() - && config_wnd.monitor.width != 0 - && config_wnd.monitor.height != 0) - .then_some(NativeWindowMonitorDetails { - name: config_wnd.monitor.name.clone(), - size: PhysicalSize { - width: config_wnd.monitor.width, - height: config_wnd.monitor.height, - }, - }), - }) { + if let Err(err) = native.set_window_config(client_window_config_to_native_window_options( + config_wnd.clone(), + )) { log::warn!("Failed to apply window settings: {err}"); self.notifications .add_err(err.to_string(), Duration::from_secs(10)); @@ -598,7 +544,7 @@ impl ClientNativeImpl { let render = render.try_get().unwrap(); render.render.render_full_design( &render.data.buffered_map.map_visual, - &mut RenderPipeline { + &RenderPipeline { base: RenderPipelineBase { map: &render.data.buffered_map.map_visual, config: &ConfigMap::default(), @@ -609,15 +555,7 @@ impl ClientNativeImpl { &intra_tick_time, ), include_last_anim_point: false, - camera: &Camera { - pos: vec2::new(21.0, 15.0), - zoom: 1.0, - parallax_aware_zoom: true, - forced_aspect_ratio: None, - }, - entities_container: &mut self.entities_container, - entities_key: None, - physics_group_name: "vanilla", + camera: &Camera::new(vec2::new(21.0, 15.0), 1.0, None, true), map_sound_volume: self.config.game.snd.render.map_sound_volume * self.config.game.snd.global_volume, }, @@ -1528,7 +1466,7 @@ impl ClientNativeImpl { name.as_ref(), self.font_data.clone(), Some(DemoVideoEncodeProperties { - file_name: format!("videos/{}.mp4", video_name).into(), + file_name: format!("videos/{video_name}.mp4").into(), pixels_per_point: self.config.game.cl.recorder.pixels_per_point, encoder_settings: EncoderSettings { fps: self.config.game.cl.recorder.fps, @@ -2297,7 +2235,7 @@ impl ClientNativeImpl { let mut res = String::default(); let cur_cmds_succeeded = run_commands(&cmds, entries, config_engine, config_game, &mut res, true); - log::debug!("{}", res); + log::debug!("{res}"); if !cur_cmds_succeeded { on_log(res); } @@ -2636,7 +2574,7 @@ impl ClientNativeImpl { } } -impl FromNativeLoadingImpl for ClientNativeImpl { +impl FromNativeLoadingImpl for GraphicsApp { fn new( mut loading: ClientNativeLoadingImpl, native: &mut dyn NativeImpl, @@ -2676,42 +2614,13 @@ impl FromNativeLoadingImpl for ClientNativeImpl { // read window props let wnd = native.window_options(); - let config_wnd = &mut loading.config_engine.wnd; - config_wnd.fullscreen = wnd.mode.is_fullscreen(); - config_wnd.decorated = wnd.decorated; - config_wnd.maximized = wnd.maximized; - match wnd.mode { - WindowMode::Fullscreen { - resolution, - fallback_window, - } => { - if let Some(resolution) = resolution { - config_wnd.fullscreen_width = resolution.width; - config_wnd.fullscreen_height = resolution.height; - } - - config_wnd.window_width = fallback_window.width; - config_wnd.window_height = fallback_window.height; - } - WindowMode::Windowed(pixels) => { - config_wnd.window_width = pixels.width; - config_wnd.window_height = pixels.height; - } - } - config_wnd.refresh_rate_mhz = wnd.refresh_rate_milli_hertz; - config_wnd.monitor = wnd - .monitor - .map(|monitor| ConfigMonitor { - name: monitor.name, - width: monitor.size.width, - height: monitor.size.height, - }) - .unwrap_or_default(); + let refresh_rate_milli_hertz = wnd.refresh_rate_milli_hertz; + client_window_props_changed_update_config(&mut loading.config_engine, wnd); // do first time setup if first_time_setup { - loading.config_game.cl.refresh_rate = if wnd.refresh_rate_milli_hertz != 0 { - (wnd.refresh_rate_milli_hertz as u64) * 4 / 1000 + loading.config_game.cl.refresh_rate = if refresh_rate_milli_hertz != 0 { + (refresh_rate_milli_hertz as u64) * 4 / 1000 } else { let fallback_refresh_rate = native_monitors .iter() @@ -2809,24 +2718,6 @@ impl FromNativeLoadingImpl for ClientNativeImpl { benchmark.bench("init of graphics"); let scene = sound.scene_handle.create(Default::default()); - let default_entities = - EntitiesContainer::load_default(&io, ENTITIES_CONTAINER_PATH.as_ref()); - let entities_container = EntitiesContainer::new( - io.clone(), - thread_pool.clone(), - default_entities, - None, - None, - "entities-container", - &graphics, - &sound, - &scene, - ENTITIES_CONTAINER_PATH.as_ref(), - ContainerLoadOptions { - assume_unused: true, - ..Default::default() - }, - ); let default_skin = SkinContainer::load_default(&io, SKIN_CONTAINER_PATH.as_ref()); let skin_container = SkinContainer::new( io.clone(), @@ -3056,7 +2947,7 @@ impl FromNativeLoadingImpl for ClientNativeImpl { local_console.ui.ui_state.is_ui_open = false; - let mut client = Self { + let mut client = GraphicsApp::new(ClientNativeImpl { menu_map, cur_time, @@ -3064,7 +2955,6 @@ impl FromNativeLoadingImpl for ClientNativeImpl { shared_info: loading.shared_info, client_info, - entities_container, skin_container, render_tee, @@ -3115,7 +3005,7 @@ impl FromNativeLoadingImpl for ClientNativeImpl { // pools & helpers string_pool: Pool::with_sized(256, || String::with_capacity(256)), // TODO: random values rn - }; + }); client.handle_console_events(native); benchmark.bench("finish init of client"); @@ -3224,7 +3114,15 @@ impl InputEventHandler for ClientNativeImpl { } } -impl FromNativeImpl for ClientNativeImpl { +impl AppWithGraphics for ClientNativeImpl { + fn get_graphics_data(&mut self) -> (&Graphics, &GraphicsBackend, &mut ConfigEngine) { + ( + &self.graphics, + &self.graphics_backend, + &mut self.config.engine, + ) + } + fn run(&mut self, native: &mut dyn NativeImpl) { self.inp_manager.collect_events(); @@ -3267,7 +3165,7 @@ impl FromNativeImpl for ClientNativeImpl { let mut legacy_proxy = self.legacy_proxy_thread.take().unwrap(); if let Err(err) = legacy_proxy.thread.try_join() { self.notifications.add_err( - format!("Legacy proxy crashed: {}", err), + format!("Legacy proxy crashed: {err}"), Duration::from_secs(10), ); self.connecting_log.log(format!("Legacy proxy died: {err}")); @@ -3717,67 +3615,6 @@ impl FromNativeImpl for ClientNativeImpl { self.inp_manager.new_frame(); } - fn resized(&mut self, native: &mut dyn NativeImpl, new_width: u32, new_height: u32) { - let window_props = self.graphics_backend.resized( - &self.graphics.backend_handle.backend_cmds, - self.graphics.stream_handle.stream_data(), - native, - new_width, - new_height, - ); - self.graphics.resized(window_props); - // update config variables - let wnd = &mut self.config.engine.wnd; - let window = native.borrow_window(); - if wnd.fullscreen { - wnd.fullscreen_width = new_width; - wnd.fullscreen_height = new_height; - } else { - let scale_factor = window.scale_factor(); - wnd.window_width = new_width as f64 / scale_factor; - wnd.window_height = new_height as f64 / scale_factor; - } - if let Some(monitor) = window.current_monitor() { - wnd.refresh_rate_mhz = monitor - .refresh_rate_millihertz() - .unwrap_or(wnd.refresh_rate_mhz); - } - } - - fn window_options_changed(&mut self, wnd: NativeWindowOptions) { - let config_wnd = &mut self.config.engine.wnd; - config_wnd.fullscreen = wnd.mode.is_fullscreen(); - config_wnd.decorated = wnd.decorated; - config_wnd.maximized = wnd.maximized; - match wnd.mode { - WindowMode::Fullscreen { - resolution, - fallback_window, - } => { - if let Some(resolution) = resolution { - config_wnd.fullscreen_width = resolution.width; - config_wnd.fullscreen_height = resolution.height; - } - - config_wnd.window_width = fallback_window.width; - config_wnd.window_height = fallback_window.height; - } - WindowMode::Windowed(pixels) => { - config_wnd.window_width = pixels.width; - config_wnd.window_height = pixels.height; - } - } - config_wnd.refresh_rate_mhz = wnd.refresh_rate_milli_hertz; - config_wnd.monitor = wnd - .monitor - .map(|monitor| ConfigMonitor { - name: monitor.name, - width: monitor.size.width, - height: monitor.size.height, - }) - .unwrap_or_default(); - } - fn destroy(mut self) { #[cfg(feature = "alloc_track")] track_report(); @@ -3787,8 +3624,8 @@ impl FromNativeImpl for ClientNativeImpl { } // destroy everything - config_fs::save(&self.config.engine, &self.io); - game_config_fs::fs::save(&self.config.game, &self.io); + config_fs::save(&self.config.engine, &self.io.clone().into()); + game_config_fs::fs::save(&self.config.game, &self.io.clone().into()); } fn focus_changed(&mut self, _focused: bool) { @@ -3807,17 +3644,4 @@ impl FromNativeImpl for ClientNativeImpl { editor.file_hovered(file); } } - - fn window_created_ntfy(&mut self, native: &mut dyn NativeImpl) -> anyhow::Result<()> { - self.graphics_backend.window_created_ntfy( - BackendWindow::Winit { - window: native.borrow_window(), - }, - &self.config.engine.dbg, - ) - } - - fn window_destroyed_ntfy(&mut self, _native: &mut dyn NativeImpl) -> anyhow::Result<()> { - self.graphics_backend.window_destroyed_ntfy() - } } diff --git a/src/client/game.rs b/src/client/game.rs index fc6a7b68..a9e0be2a 100644 --- a/src/client/game.rs +++ b/src/client/game.rs @@ -125,7 +125,7 @@ pub enum Game { /// the game is connecting Connecting(ConnectingGame), /// the game is loading - Loading(LoadingGame), + Loading(Box), WaitingForFirstSnapshot(Box), Active(Box), Err(anyhow::Error), @@ -168,12 +168,7 @@ impl Game { let servers = MainMenuUi::download_server_list(&http).await?; let server = servers .iter() - .find(|server| { - server - .addresses - .iter() - .any(|server_addr| *server_addr == addr) - }) + .find(|server| server.addresses.contains(&addr)) .ok_or_else(|| anyhow!("Server was not found in the server list")); match (server_cert, server) { (ServerCertMode::Unknown, Err(err)) => Err(err), @@ -337,7 +332,7 @@ impl Game { in_memory: None, }; - Self::Loading(LoadingGame { + Self::Loading(Box::new(LoadingGame { network, resource_download_server: props.resource_download_server.clone(), map: ClientMapLoading::new( @@ -375,7 +370,7 @@ impl Game { }, send_input_every_tick, server_options, - }) + })) } pub fn player_net_infos( @@ -555,22 +550,23 @@ impl Game { }) } } - Game::Loading(LoadingGame { - network, - mut map, - ping, - prediction_timer, - hint_start_camera_pos, - demo_recorder_props, - spatial_world, - auto_cleanup, - connect, - base, - resource_download_server, - local, - send_input_every_tick, - server_options, - }) => { + Game::Loading(loading) => { + let LoadingGame { + network, + mut map, + ping, + prediction_timer, + hint_start_camera_pos, + demo_recorder_props, + spatial_world, + auto_cleanup, + connect, + base, + resource_download_server, + local, + send_input_every_tick, + server_options, + } = *loading; if map.is_fully_loaded() { network.send_unordered_to_server(&ClientToServerMessage::Ready(MsgClReady { players: Self::player_net_infos(&local.expected_local_players, config_game), @@ -610,7 +606,7 @@ impl Game { .log("Map fully loaded, waiting for first snapshot from server now."); Self::WaitingForFirstSnapshot(Box::new(ActiveGame { network, - map, + map: *map, auto_demo_recorder: Some(auto_demo_recorder), demo_recorder_props, @@ -664,7 +660,7 @@ impl Game { .log .set_mode(ConnectModes::ConnectingErr { msg: err }); } - Self::Loading(LoadingGame { + Self::Loading(Box::new(LoadingGame { network, map, ping, @@ -682,7 +678,7 @@ impl Game { local, send_input_every_tick, server_options, - }) + })) } } Game::Err(err) => { diff --git a/src/client/game/active.rs b/src/client/game/active.rs index 3de5fbff..d71e8623 100644 --- a/src/client/game/active.rs +++ b/src/client/game/active.rs @@ -315,7 +315,10 @@ impl ActiveGame { (snapshot, snap_id, game_monotonic_tick) } Err(err) => { - log::debug!(target: "network_logic", "had to drop a snapshot from the server with diff_id {:?}: {err}", diff_id); + log::debug!( + target: "network_logic", + "had to drop a snapshot from the server with diff_id {diff_id:?}: {err}" + ); return; } }; diff --git a/src/client/input/input_handling.rs b/src/client/input/input_handling.rs index a41b93c1..2e92ee5f 100644 --- a/src/client/input/input_handling.rs +++ b/src/client/input/input_handling.rs @@ -5,7 +5,7 @@ use binds::binds::{ gen_local_player_action_hash_map, BindAction, BindActionsCharacter, BindActionsHotkey, BindActionsLocalPlayer, }; -use client_render_base::map::render_tools::{CanvasType, RenderTools}; +use camera::{Camera, CameraInterface}; use client_types::console::ConsoleEntry; use client_ui::chat::user_data::ChatMode; use client_ui::console::utils::run_command; @@ -25,6 +25,7 @@ use game_interface::types::input::{ use game_interface::types::render::character::{PlayerCameraMode, TeeEye}; use game_interface::types::weapons::WeaponType; use graphics::graphics::graphics::{Graphics, ScreenshotCb}; +use graphics_types::rendering::State; use math::math::{length, normalize_pre_length, vector::dvec2}; use input_binds::binds::{BindKey, Binds, MouseExtra}; @@ -862,23 +863,21 @@ impl InputHandling { return false; }; if local_player.chat_input_active.is_none() { - let canvas = if config_game.cl.render.use_ingame_aspect_ratio { - CanvasType::Custom { - aspect_ratio: config_game.cl.render.ingame_aspect_ratio as f32, - } - } else { - CanvasType::Handle(&graphics.canvas_handle) - }; - let points = RenderTools::canvas_points_of_group( - canvas, - 0.0, - 0.0, - None, + let mut fake_state = State::default(); + Camera::new( + Default::default(), local_player.zoom, + config_game + .cl + .render + .use_ingame_aspect_ratio + .then_some(config_game.cl.render.ingame_aspect_ratio as f32), true, - ); - let vp_width = points[2] as f64 - points[0] as f64; - let vp_height = points[3] as f64 - points[1] as f64; + ) + .project(&graphics.canvas_handle, &mut fake_state, None); + let (tl_x, tl_y, br_x, br_y) = fake_state.get_canvas_mapping(); + let vp_width = br_x as f64 - tl_x as f64; + let vp_height = br_y as f64 - tl_y as f64; match ev { InputEv::Key(key_ev) => match &key_ev.key { BindKey::Key(_) | BindKey::Mouse(_) => { diff --git a/src/client/spatial_chat/spatial_chat.rs b/src/client/spatial_chat/spatial_chat.rs index 15f9cdb4..661d5cff 100644 --- a/src/client/spatial_chat/spatial_chat.rs +++ b/src/client/spatial_chat/spatial_chat.rs @@ -171,12 +171,10 @@ impl SpatialChat { let hosts = self.microphone.hosts(); let host = if config.cl.spatial_chat.host.is_empty() { hosts.default.clone() + } else if hosts.hosts.contains(&config.cl.spatial_chat.host) { + config.cl.spatial_chat.host.clone() } else { - hosts - .hosts - .contains(&config.cl.spatial_chat.host) - .then(|| config.cl.spatial_chat.host.clone()) - .unwrap_or_else(|| hosts.default.clone()) + hosts.default.clone() }; let devices = self.microphone.devices(&host).ok(); let device = if config.cl.spatial_chat.device.is_empty() { @@ -186,11 +184,11 @@ impl SpatialChat { } else { devices .map(|devices| { - devices - .devices - .contains(&config.cl.spatial_chat.device) - .then(|| config.cl.spatial_chat.device.clone()) - .unwrap_or_else(|| devices.default.clone().unwrap_or_default()) + if devices.devices.contains(&config.cl.spatial_chat.device) { + config.cl.spatial_chat.device.clone() + } else { + devices.default.clone().unwrap_or_default() + } }) .unwrap_or_default() }; @@ -500,7 +498,7 @@ impl SpatialChat { .cl .spatial_chat .account_players - .get(&format!("acc_{}", account_id)), + .get(&format!("acc_{account_id}")), PlayerUniqueId::CertFingerprint(hash) => { if !config.cl.spatial_chat.from_non_account_users { return None; diff --git a/src/client/ui/pages/editor/tee.rs b/src/client/ui/pages/editor/tee.rs index 276f4430..1b41adee 100644 --- a/src/client/ui/pages/editor/tee.rs +++ b/src/client/ui/pages/editor/tee.rs @@ -448,13 +448,7 @@ impl TeeEditor { );*/ // render all animation frames as plot lines to visualize their animation path - stream_handle.render_lines( - hi_closure!([], |mut stream_handle: LinesStreamHandle<'_>| -> () { - stream_handle.add_vertices(StreamedLine::new().into()); - stream_handle.add_vertices(StreamedLine::new().into()); - }), - State::new(), - ); + stream_handle.render_lines(&[StreamedLine::new(), StreamedLine::new()], State::new()); if item.atoms.selected_body_part.get() != "" { // add an overlay to show the current frames interpolated data(pos, scale, rotation) diff --git a/src/emoticon-convert/Cargo.toml b/src/emoticon-convert/Cargo.toml index 4f212363..8b98213a 100644 --- a/src/emoticon-convert/Cargo.toml +++ b/src/emoticon-convert/Cargo.toml @@ -4,9 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -client-extra = { path = "../../game/client-extra" } +assets-base = { path = "../../game/assets-base" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } clap = { version = "4.5.37", features = ["derive"] } -tar = "0.4.44" diff --git a/src/emoticon-convert/src/main.rs b/src/emoticon-convert/src/main.rs index 34c4948f..6083e18e 100644 --- a/src/emoticon-convert/src/main.rs +++ b/src/emoticon-convert/src/main.rs @@ -3,9 +3,9 @@ use std::{ path::{Path, PathBuf}, }; +use assets_base::tar::{new_tar, tar_add_file, TarBuilder}; +use assets_splitting::emoticon_split::Emoticon06Part; use clap::Parser; -use client_extra::emoticon_split::Emoticon06Part; -use tar::Header; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,7 +20,7 @@ struct Args { } struct TarFile { - file: tar::Builder>, + file: TarBuilder, } enum WriteMode<'a> { @@ -28,33 +28,15 @@ enum WriteMode<'a> { Disk, } -fn new_tar() -> TarFile { - let mut builder = tar::Builder::new(Vec::new()); - builder.mode(tar::HeaderMode::Deterministic); - TarFile { file: builder } -} - fn write_part(write_mode: &mut WriteMode<'_>, part: Emoticon06Part, output: &Path, name: &str) { let png = image_utils::png::save_png_image(&part.data, part.width, part.height).unwrap(); match write_mode { WriteMode::Tar(files) => { let tar = files .entry(output.to_string_lossy().to_string()) - .or_insert_with(new_tar); + .or_insert_with(|| TarFile { file: new_tar() }); - let mut header = Header::new_gnu(); - header.set_cksum(); - header.set_size(png.len() as u64); - header.set_mode(0o644); - header.set_uid(1000); - header.set_gid(1000); - tar.file - .append_data( - &mut header, - format!("{name}.png"), - std::io::Cursor::new(&png), - ) - .unwrap(); + tar_add_file(&mut tar.file, format!("{name}.png"), &png); } WriteMode::Disk => { std::fs::write(output.join(format!("{name}.png")), png).unwrap(); @@ -74,7 +56,8 @@ fn main() { }) .unwrap(); let converted = - client_extra::emoticon_split::split_06_emoticon(img.data, img.width, img.height).unwrap(); + assets_splitting::emoticon_split::split_06_emoticon(img.data, img.width, img.height) + .unwrap(); let mut tar_files: HashMap = Default::default(); let mut write_mode = if args.tar { diff --git a/src/extra-convert/Cargo.toml b/src/extra-convert/Cargo.toml index 3cd54107..b28a8a4a 100644 --- a/src/extra-convert/Cargo.toml +++ b/src/extra-convert/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -client-extra = { path = "../../game/client-extra" } +assets-base = { path = "../../game/assets-base" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } clap = { version = "4.5.37", features = ["derive"] } -tar = "0.4.44" diff --git a/src/extra-convert/src/main.rs b/src/extra-convert/src/main.rs index bcdbc014..aa93d4d1 100644 --- a/src/extra-convert/src/main.rs +++ b/src/extra-convert/src/main.rs @@ -3,9 +3,9 @@ use std::{ path::{Path, PathBuf}, }; +use assets_base::tar::{new_tar, tar_add_file, TarBuilder}; +use assets_splitting::extra_split::Extras06Part; use clap::Parser; -use client_extra::extra_split::Extras06Part; -use tar::Header; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,7 +20,7 @@ struct Args { } struct TarFile { - file: tar::Builder>, + file: TarBuilder, } enum WriteMode<'a> { @@ -28,33 +28,15 @@ enum WriteMode<'a> { Disk, } -fn new_tar() -> TarFile { - let mut builder = tar::Builder::new(Vec::new()); - builder.mode(tar::HeaderMode::Deterministic); - TarFile { file: builder } -} - fn write_part(write_mode: &mut WriteMode<'_>, part: Extras06Part, output: &Path, name: &str) { let png = image_utils::png::save_png_image(&part.data, part.width, part.height).unwrap(); match write_mode { WriteMode::Tar(files) => { let tar = files .entry(output.to_string_lossy().to_string()) - .or_insert_with(new_tar); + .or_insert_with(|| TarFile { file: new_tar() }); - let mut header = Header::new_gnu(); - header.set_cksum(); - header.set_size(png.len() as u64); - header.set_mode(0o644); - header.set_uid(1000); - header.set_gid(1000); - tar.file - .append_data( - &mut header, - format!("{name}.png"), - std::io::Cursor::new(&png), - ) - .unwrap(); + tar_add_file(&mut tar.file, format!("{name}.png"), &png); } WriteMode::Disk => { std::fs::write(output.join(format!("{name}.png")), png).unwrap(); @@ -74,7 +56,7 @@ fn main() { }) .unwrap(); let converted = - client_extra::extra_split::split_06_extras(img.data, img.width, img.height).unwrap(); + assets_splitting::extra_split::split_06_extras(img.data, img.width, img.height).unwrap(); let mut tar_files: HashMap = Default::default(); let mut write_mode = if args.tar { diff --git a/src/game-convert/Cargo.toml b/src/game-convert/Cargo.toml index a8a20246..44a804a8 100644 --- a/src/game-convert/Cargo.toml +++ b/src/game-convert/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -client-extra = { path = "../../game/client-extra" } +assets-base = { path = "../../game/assets-base" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } clap = { version = "4.5.37", features = ["derive"] } -tar = "0.4.44" diff --git a/src/game-convert/src/main.rs b/src/game-convert/src/main.rs index ffa0eea2..719f8f97 100644 --- a/src/game-convert/src/main.rs +++ b/src/game-convert/src/main.rs @@ -3,9 +3,9 @@ use std::{ path::{Path, PathBuf}, }; +use assets_base::tar::{new_tar, tar_add_file, TarBuilder}; +use assets_splitting::game_split::Game06Part; use clap::Parser; -use client_extra::game_split::Game06Part; -use tar::Header; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,7 +20,7 @@ struct Args { } struct TarFile { - file: tar::Builder>, + file: TarBuilder, } enum WriteMode<'a> { @@ -28,12 +28,6 @@ enum WriteMode<'a> { Disk, } -fn new_tar() -> TarFile { - let mut builder = tar::Builder::new(Vec::new()); - builder.mode(tar::HeaderMode::Deterministic); - TarFile { file: builder } -} - fn write_part( write_mode: &mut WriteMode<'_>, part: Game06Part, @@ -44,21 +38,11 @@ fn write_part( let png = image_utils::png::save_png_image(&part.data, part.width, part.height).unwrap(); match write_mode { WriteMode::Tar(files) => { - let tar = files.entry(base_path.to_string()).or_insert_with(new_tar); + let tar = files + .entry(base_path.to_string()) + .or_insert_with(|| TarFile { file: new_tar() }); - let mut header = Header::new_gnu(); - header.set_cksum(); - header.set_size(png.len() as u64); - header.set_mode(0o644); - header.set_uid(1000); - header.set_gid(1000); - tar.file - .append_data( - &mut header, - format!("{name}.png"), - std::io::Cursor::new(&png), - ) - .unwrap(); + tar_add_file(&mut tar.file, format!("{name}.png"), &png); } WriteMode::Disk => { std::fs::write(output.join(base_path).join(format!("{name}.png")), png) @@ -79,7 +63,7 @@ fn main() { }) .unwrap(); let converted = - client_extra::game_split::split_06_game(img.data, img.width, img.height).unwrap(); + assets_splitting::game_split::split_06_game(img.data, img.width, img.height).unwrap(); let mut tar_files: HashMap = Default::default(); let mut write_mode = if args.tar { diff --git a/src/hud-convert/Cargo.toml b/src/hud-convert/Cargo.toml index 7982d82e..f90e5b9d 100644 --- a/src/hud-convert/Cargo.toml +++ b/src/hud-convert/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -client-extra = { path = "../../game/client-extra" } +assets-base = { path = "../../game/assets-base" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } clap = { version = "4.5.37", features = ["derive"] } -tar = "0.4.44" diff --git a/src/hud-convert/src/main.rs b/src/hud-convert/src/main.rs index aadb2755..7ea40510 100644 --- a/src/hud-convert/src/main.rs +++ b/src/hud-convert/src/main.rs @@ -3,9 +3,9 @@ use std::{ path::{Path, PathBuf}, }; +use assets_base::tar::{new_tar, tar_add_file, TarBuilder}; +use assets_splitting::ddrace_hud_split::DdraceHudPart; use clap::Parser; -use client_extra::ddrace_hud_split::DdraceHudPart; -use tar::Header; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,7 +20,7 @@ struct Args { } struct TarFile { - file: tar::Builder>, + file: TarBuilder, } enum WriteMode<'a> { @@ -28,33 +28,15 @@ enum WriteMode<'a> { Disk, } -fn new_tar() -> TarFile { - let mut builder = tar::Builder::new(Vec::new()); - builder.mode(tar::HeaderMode::Deterministic); - TarFile { file: builder } -} - fn write_part(write_mode: &mut WriteMode<'_>, part: DdraceHudPart, output: &Path, name: &str) { let png = image_utils::png::save_png_image(&part.data, part.width, part.height).unwrap(); match write_mode { WriteMode::Tar(files) => { let tar = files .entry(output.to_string_lossy().to_string()) - .or_insert_with(new_tar); + .or_insert_with(|| TarFile { file: new_tar() }); - let mut header = Header::new_gnu(); - header.set_cksum(); - header.set_size(png.len() as u64); - header.set_mode(0o644); - header.set_uid(1000); - header.set_gid(1000); - tar.file - .append_data( - &mut header, - format!("{name}.png"), - std::io::Cursor::new(&png), - ) - .unwrap(); + tar_add_file(&mut tar.file, format!("{name}.png"), &png); } WriteMode::Disk => { std::fs::write(output.join(format!("{name}.png")), png).unwrap(); @@ -74,7 +56,8 @@ fn main() { }) .unwrap(); let converted = - client_extra::ddrace_hud_split::split_ddrace_hud(img.data, img.width, img.height).unwrap(); + assets_splitting::ddrace_hud_split::split_ddrace_hud(img.data, img.width, img.height) + .unwrap(); let mut tar_files: HashMap = Default::default(); let mut write_mode = if args.tar { diff --git a/src/lib.rs b/src/lib.rs index a4123b88..9857098d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -70,6 +70,11 @@ fn main_impl(app: NativeApp) { let err_msg = format!("Fatal error:\n{message}\n{loc}"); println!("{err_msg}"); + + // Try to generate and print backtrace + let backtrace = std::backtrace::Backtrace::force_capture(); + println!("Backtrace:\n{backtrace}"); + if thread_id != std::thread::current().id() { return; } @@ -94,7 +99,7 @@ fn main() { } env_logger::init(); #[cfg(not(target_os = "android"))] - main_impl(()) + main_impl(Default::default()) } #[allow(dead_code)] diff --git a/src/map-convert/src/main.rs b/src/map-convert/src/main.rs index b3741424..abdcf760 100644 --- a/src/map-convert/src/main.rs +++ b/src/map-convert/src/main.rs @@ -9,7 +9,7 @@ use base::{benchmark::Benchmark, hash::fmt_hash}; use base_fs::filesys::FileSystem; use base_io::io::IoFileSys; use clap::Parser; -use map::map::Map; +use map::{file::MapFileReader, map::Map, utils::file_ext_or_twmap_tar}; use map_convert_lib::{legacy_to_new::legacy_to_new, new_to_legacy::new_to_legacy}; #[derive(Parser, Debug)] @@ -22,7 +22,7 @@ struct Args { /// optimize PNGs with oxipng (default: on). This option only has an effect when converting legacy maps to new ones #[arg(short, long, default_value_t = true, action = clap::ArgAction::Set)] optimize: bool, - /// export as json (only works for .twmap maps) + /// export as json (only works for .twmap.tar maps) #[arg(short, long, default_value_t = false)] json: bool, } @@ -61,8 +61,7 @@ fn main() { // write map let benchmark = Benchmark::new(true); - let mut file: Vec = Default::default(); - output.map.write(&mut file, &thread_pool).unwrap(); + let file = output.map.write(&thread_pool).unwrap(); benchmark.bench("serializing & compressing map"); let fs = io.fs.clone(); let output_dir = args.output.clone(); @@ -72,7 +71,7 @@ fn main() { let map_path = PathBuf::from(&output_dir).join("map/maps/"); fs.create_dir(&map_path).await?; let map_path = map_path.join(format!( - "{}.twmap", + "{}.twmap.tar", file_path.file_stem().unwrap().to_str().unwrap(), )); fs.write_file(&map_path, file).await?; @@ -114,7 +113,7 @@ fn main() { }) } // new to legacy or json - else if file_path.extension().is_some_and(|e| e == "twmap") { + else if file_ext_or_twmap_tar(&file_path).is_some_and(|e| e == "twmap.tar") { if args.json { let fs = io.fs.clone(); let tp = thread_pool.clone(); @@ -124,7 +123,7 @@ fn main() { .read_file(path) .await .map_err(|err| anyhow!("loading map file failed: {err}"))?; - let map = Map::read(&map, &tp) + let map = Map::read(&MapFileReader::new(map)?, &tp) .map_err(|err| anyhow!("loading map from file failed: {err}"))?; Ok(map) diff --git a/src/part-convert/Cargo.toml b/src/part-convert/Cargo.toml index 94721c96..fe9b600f 100644 --- a/src/part-convert/Cargo.toml +++ b/src/part-convert/Cargo.toml @@ -4,8 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -client-extra = { path = "../../game/client-extra" } +assets-base = { path = "../../game/assets-base" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } clap = { version = "4.5.37", features = ["derive"] } -tar = "0.4.44" diff --git a/src/part-convert/src/main.rs b/src/part-convert/src/main.rs index fd86545d..a44cba4e 100644 --- a/src/part-convert/src/main.rs +++ b/src/part-convert/src/main.rs @@ -3,9 +3,9 @@ use std::{ path::{Path, PathBuf}, }; +use assets_base::tar::{new_tar, tar_add_file, TarBuilder}; +use assets_splitting::particles_split::Particles06Part; use clap::Parser; -use client_extra::particles_split::Particles06Part; -use tar::Header; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -20,7 +20,7 @@ struct Args { } struct TarFile { - file: tar::Builder>, + file: TarBuilder, } enum WriteMode<'a> { @@ -28,33 +28,15 @@ enum WriteMode<'a> { Disk, } -fn new_tar() -> TarFile { - let mut builder = tar::Builder::new(Vec::new()); - builder.mode(tar::HeaderMode::Deterministic); - TarFile { file: builder } -} - fn write_part(write_mode: &mut WriteMode<'_>, part: Particles06Part, output: &Path, name: &str) { let png = image_utils::png::save_png_image(&part.data, part.width, part.height).unwrap(); match write_mode { WriteMode::Tar(files) => { let tar = files .entry(output.to_string_lossy().to_string()) - .or_insert_with(new_tar); - - let mut header = Header::new_gnu(); - header.set_cksum(); - header.set_size(png.len() as u64); - header.set_mode(0o644); - header.set_uid(1000); - header.set_gid(1000); - tar.file - .append_data( - &mut header, - format!("{name}.png"), - std::io::Cursor::new(&png), - ) - .unwrap(); + .or_insert_with(|| TarFile { file: new_tar() }); + + tar_add_file(&mut tar.file, format!("{name}.png"), &png); } WriteMode::Disk => { std::fs::write(output.join(format!("{name}.png")), png).unwrap(); @@ -74,7 +56,8 @@ fn main() { }) .unwrap(); let converted = - client_extra::particles_split::split_06_particles(img.data, img.width, img.height).unwrap(); + assets_splitting::particles_split::split_06_particles(img.data, img.width, img.height) + .unwrap(); let mut tar_files: HashMap = Default::default(); let mut write_mode = if args.tar { diff --git a/src/skin-convert/Cargo.toml b/src/skin-convert/Cargo.toml index 1b49fd64..81b72e61 100644 --- a/src/skin-convert/Cargo.toml +++ b/src/skin-convert/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] clap = { version = "4.5.37", features = ["derive"] } -client-extra = { path = "../../game/client-extra" } +assets-splitting = { path = "../../game/assets-splitting" } image-utils = { path = "../../lib/image-utils" } diff --git a/src/skin-convert/src/main.rs b/src/skin-convert/src/main.rs index d2153480..064e45e9 100644 --- a/src/skin-convert/src/main.rs +++ b/src/skin-convert/src/main.rs @@ -1,5 +1,5 @@ +use assets_splitting::skin_split::Skin06Part; use clap::Parser; -use client_extra::skin_split::Skin06Part; #[derive(Parser, Debug)] #[command(author, version, about, long_about = None)] @@ -27,7 +27,7 @@ fn main() { }) .unwrap(); let converted = - client_extra::skin_split::split_06_skin(img.data, img.width, img.height).unwrap(); + assets_splitting::skin_split::split_06_skin(img.data, img.width, img.height).unwrap(); std::fs::create_dir_all(&args.output).unwrap(); std::fs::create_dir_all(&(args.output.clone() + "/eyes_left")).unwrap(); diff --git a/src/tests/ingame.rs b/src/tests/ingame.rs index 93ecb400..0dcd918b 100644 --- a/src/tests/ingame.rs +++ b/src/tests/ingame.rs @@ -1,10 +1,10 @@ use std::time::Duration; use base::{linked_hash_map_view::FxLinkedHashMap, network_string::PoolNetworkString}; +use camera::Camera; use client_containers::utils::RenderGameContainers; use client_render_base::{ - map::render_pipe::{Camera, GameTimeInfo}, - render::particle_manager::ParticleManager, + map::render_pipe::GameTimeInfo, render::particle_manager::ParticleManager, }; use client_render_game::components::{ game_objects::{GameObjectsRender, GameObjectsRenderPipe}, @@ -105,12 +105,7 @@ pub fn test_ingame( ticks_per_second: 50.try_into().unwrap(), intra_tick_time: Default::default(), }; - let camera = Camera { - pos: Default::default(), - zoom: 1.0, - parallax_aware_zoom: true, - forced_aspect_ratio: None, - }; + let camera = Camera::new(Default::default(), 1.0, None, true); game_objects.render(&mut GameObjectsRenderPipe { particle_manager: &mut particles, @@ -144,7 +139,7 @@ pub fn test_ingame( emoticons: &mut containers.emoticons_container, particle_manager: &mut particles, collision: &Collision::new( - &MapGroupPhysics { + MapGroupPhysics { attr: MapGroupPhysicsAttr { width: 1u16.try_into().unwrap(), height: 1u16.try_into().unwrap(), @@ -282,7 +277,7 @@ pub fn test_ingame_skins( emoticons: &mut containers.emoticons_container, particle_manager: &mut particles, collision: &Collision::new( - &MapGroupPhysics { + MapGroupPhysics { attr: MapGroupPhysicsAttr { width: 1u16.try_into().unwrap(), height: 1u16.try_into().unwrap(),